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));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
397 flags &= ~F_ALL_CASTLE_OK;
405 FILE *gameFileFP, *debugFP;
408 [AS] Note: sometimes, the sscanf() function is used to parse the input
409 into a fixed-size buffer. Because of this, we must be prepared to
410 receive strings as long as the size of the input buffer, which is currently
411 set to 4K for Windows and 8K for the rest.
412 So, we must either allocate sufficiently large buffers here, or
413 reduce the size of the input buffer in the input reading part.
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
420 ChessProgramState first, second;
422 /* premove variables */
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
463 int have_sent_ICS_logon = 0;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating(str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine(ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions(ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
744 InitEngine(ChessProgramState *cps, int n)
745 { // [HGM] all engine initialiation put in a function that does one engine
749 cps->which = engineNames[n];
750 cps->maybeThinking = FALSE;
754 cps->sendDrawOffers = 1;
756 cps->program = appData.chessProgram[n];
757 cps->host = appData.host[n];
758 cps->dir = appData.directory[n];
759 cps->initString = appData.engInitString[n];
760 cps->computerString = appData.computerString[n];
761 cps->useSigint = TRUE;
762 cps->useSigterm = TRUE;
763 cps->reuse = appData.reuse[n];
764 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
765 cps->useSetboard = FALSE;
767 cps->usePing = FALSE;
770 cps->usePlayother = FALSE;
771 cps->useColors = TRUE;
772 cps->useUsermove = FALSE;
773 cps->sendICS = FALSE;
774 cps->sendName = appData.icsActive;
775 cps->sdKludge = FALSE;
776 cps->stKludge = FALSE;
777 TidyProgramName(cps->program, cps->host, cps->tidy);
779 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780 cps->analysisSupport = 2; /* detect */
781 cps->analyzing = FALSE;
782 cps->initDone = FALSE;
784 /* New features added by Tord: */
785 cps->useFEN960 = FALSE;
786 cps->useOOCastle = TRUE;
787 /* End of new features added by Tord. */
788 cps->fenOverride = appData.fenOverride[n];
790 /* [HGM] time odds: set factor for each machine */
791 cps->timeOdds = appData.timeOdds[n];
793 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794 cps->accumulateTC = appData.accumulateTC[n];
795 cps->maxNrOfSessions = 1;
799 cps->supportsNPS = UNKNOWN;
802 cps->optionSettings = appData.engOptions[n];
804 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805 cps->isUCI = appData.isUCI[n]; /* [AS] */
806 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808 if (appData.protocolVersion[n] > PROTOVER
809 || appData.protocolVersion[n] < 1)
814 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815 appData.protocolVersion[n]);
816 if( (len > MSG_SIZ) && appData.debugMode )
817 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819 DisplayFatalError(buf, 0, 2);
823 cps->protocolVersion = appData.protocolVersion[n];
826 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ChessProgramState *savCps;
835 if(WaitForEngine(savCps, LoadEngine)) return;
836 CommonEngineInit(); // recalculate time odds
837 if(gameInfo.variant != StringToVariant(appData.variant)) {
838 // we changed variant when loading the engine; this forces us to reset
839 Reset(TRUE, savCps != &first);
840 EditGameEvent(); // for consistency with other path, as Reset changes mode
842 InitChessProgram(savCps, FALSE);
843 SendToProgram("force\n", savCps);
844 DisplayMessage("", "");
845 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852 ReplaceEngine(ChessProgramState *cps, int n)
856 appData.noChessProgram = FALSE;
857 appData.clockMode = TRUE;
859 if(n) return; // only startup first engine immediately; second can wait
860 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
867 static char resetOptions[] =
868 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
869 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
872 Load(ChessProgramState *cps, int i)
874 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
875 if(engineLine[0]) { // an engine was selected from the combo box
876 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
877 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
878 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
879 ParseArgsFromString(buf);
881 ReplaceEngine(cps, i);
885 while(q = strchr(p, SLASH)) p = q+1;
886 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
887 if(engineDir[0] != NULLCHAR)
888 appData.directory[i] = engineDir;
889 else if(p != engineName) { // derive directory from engine path, when not given
891 appData.directory[i] = strdup(engineName);
893 } else appData.directory[i] = ".";
895 snprintf(command, MSG_SIZ, "%s %s", p, params);
898 appData.chessProgram[i] = strdup(p);
899 appData.isUCI[i] = isUCI;
900 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
901 appData.hasOwnBookUCI[i] = hasBook;
902 if(!nickName[0]) useNick = FALSE;
903 if(useNick) ASSIGN(appData.pgnName[i], nickName);
906 q = firstChessProgramNames;
907 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
908 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
909 useNick ? " -fn \"" : "",
910 useNick ? nickName : "",
912 v1 ? " -firstProtocolVersion 1" : "",
913 hasBook ? "" : " -fNoOwnBookUCI",
914 isUCI ? " -fUCI" : "",
915 storeVariant ? " -variant " : "",
916 storeVariant ? VariantName(gameInfo.variant) : "");
917 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
918 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
921 ReplaceEngine(cps, i);
927 int matched, min, sec;
929 * Parse timeControl resource
931 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
932 appData.movesPerSession)) {
934 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
935 DisplayFatalError(buf, 0, 2);
939 * Parse searchTime resource
941 if (*appData.searchTime != NULLCHAR) {
942 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
944 searchTime = min * 60;
945 } else if (matched == 2) {
946 searchTime = min * 60 + sec;
949 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
950 DisplayFatalError(buf, 0, 2);
959 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
960 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
962 GetTimeMark(&programStartTime);
963 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
964 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
967 programStats.ok_to_send = 1;
968 programStats.seen_stat = 0;
971 * Initialize game list
977 * Internet chess server status
979 if (appData.icsActive) {
980 appData.matchMode = FALSE;
981 appData.matchGames = 0;
983 appData.noChessProgram = !appData.zippyPlay;
985 appData.zippyPlay = FALSE;
986 appData.zippyTalk = FALSE;
987 appData.noChessProgram = TRUE;
989 if (*appData.icsHelper != NULLCHAR) {
990 appData.useTelnet = TRUE;
991 appData.telnetProgram = appData.icsHelper;
994 appData.zippyTalk = appData.zippyPlay = FALSE;
997 /* [AS] Initialize pv info list [HGM] and game state */
1001 for( i=0; i<=framePtr; i++ ) {
1002 pvInfoList[i].depth = -1;
1003 boards[i][EP_STATUS] = EP_NONE;
1004 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1010 /* [AS] Adjudication threshold */
1011 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1013 InitEngine(&first, 0);
1014 InitEngine(&second, 1);
1017 if (appData.icsActive) {
1018 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1019 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1020 appData.clockMode = FALSE;
1021 first.sendTime = second.sendTime = 0;
1025 /* Override some settings from environment variables, for backward
1026 compatibility. Unfortunately it's not feasible to have the env
1027 vars just set defaults, at least in xboard. Ugh.
1029 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1034 if (!appData.icsActive) {
1038 /* Check for variants that are supported only in ICS mode,
1039 or not at all. Some that are accepted here nevertheless
1040 have bugs; see comments below.
1042 VariantClass variant = StringToVariant(appData.variant);
1044 case VariantBughouse: /* need four players and two boards */
1045 case VariantKriegspiel: /* need to hide pieces and move details */
1046 /* case VariantFischeRandom: (Fabien: moved below) */
1047 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1048 if( (len > MSG_SIZ) && appData.debugMode )
1049 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1051 DisplayFatalError(buf, 0, 2);
1054 case VariantUnknown:
1055 case VariantLoadable:
1065 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1066 if( (len > MSG_SIZ) && appData.debugMode )
1067 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1069 DisplayFatalError(buf, 0, 2);
1072 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1073 case VariantFairy: /* [HGM] TestLegality definitely off! */
1074 case VariantGothic: /* [HGM] should work */
1075 case VariantCapablanca: /* [HGM] should work */
1076 case VariantCourier: /* [HGM] initial forced moves not implemented */
1077 case VariantShogi: /* [HGM] could still mate with pawn drop */
1078 case VariantKnightmate: /* [HGM] should work */
1079 case VariantCylinder: /* [HGM] untested */
1080 case VariantFalcon: /* [HGM] untested */
1081 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1082 offboard interposition not understood */
1083 case VariantNormal: /* definitely works! */
1084 case VariantWildCastle: /* pieces not automatically shuffled */
1085 case VariantNoCastle: /* pieces not automatically shuffled */
1086 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1087 case VariantLosers: /* should work except for win condition,
1088 and doesn't know captures are mandatory */
1089 case VariantSuicide: /* should work except for win condition,
1090 and doesn't know captures are mandatory */
1091 case VariantGiveaway: /* should work except for win condition,
1092 and doesn't know captures are mandatory */
1093 case VariantTwoKings: /* should work */
1094 case VariantAtomic: /* should work except for win condition */
1095 case Variant3Check: /* should work except for win condition */
1096 case VariantShatranj: /* should work except for all win conditions */
1097 case VariantMakruk: /* should work except for daw countdown */
1098 case VariantBerolina: /* might work if TestLegality is off */
1099 case VariantCapaRandom: /* should work */
1100 case VariantJanus: /* should work */
1101 case VariantSuper: /* experimental */
1102 case VariantGreat: /* experimental, requires legality testing to be off */
1103 case VariantSChess: /* S-Chess, should work */
1104 case VariantSpartan: /* should work */
1111 int NextIntegerFromString( char ** str, long * value )
1116 while( *s == ' ' || *s == '\t' ) {
1122 if( *s >= '0' && *s <= '9' ) {
1123 while( *s >= '0' && *s <= '9' ) {
1124 *value = *value * 10 + (*s - '0');
1136 int NextTimeControlFromString( char ** str, long * value )
1139 int result = NextIntegerFromString( str, &temp );
1142 *value = temp * 60; /* Minutes */
1143 if( **str == ':' ) {
1145 result = NextIntegerFromString( str, &temp );
1146 *value += temp; /* Seconds */
1153 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1154 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1155 int result = -1, type = 0; long temp, temp2;
1157 if(**str != ':') return -1; // old params remain in force!
1159 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1160 if( NextIntegerFromString( str, &temp ) ) return -1;
1161 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1164 /* time only: incremental or sudden-death time control */
1165 if(**str == '+') { /* increment follows; read it */
1167 if(**str == '!') type = *(*str)++; // Bronstein TC
1168 if(result = NextIntegerFromString( str, &temp2)) return -1;
1169 *inc = temp2 * 1000;
1170 if(**str == '.') { // read fraction of increment
1171 char *start = ++(*str);
1172 if(result = NextIntegerFromString( str, &temp2)) return -1;
1174 while(start++ < *str) temp2 /= 10;
1178 *moves = 0; *tc = temp * 1000; *incType = type;
1182 (*str)++; /* classical time control */
1183 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1194 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1195 { /* [HGM] get time to add from the multi-session time-control string */
1196 int incType, moves=1; /* kludge to force reading of first session */
1197 long time, increment;
1200 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1201 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1203 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1204 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1205 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1206 if(movenr == -1) return time; /* last move before new session */
1207 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1208 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1209 if(!moves) return increment; /* current session is incremental */
1210 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1211 } while(movenr >= -1); /* try again for next session */
1213 return 0; // no new time quota on this move
1217 ParseTimeControl(tc, ti, mps)
1224 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1227 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1228 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1229 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1233 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1235 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1238 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1240 snprintf(buf, MSG_SIZ, ":%s", mytc);
1242 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1244 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1249 /* Parse second time control */
1252 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1260 timeControl_2 = tc2 * 1000;
1270 timeControl = tc1 * 1000;
1273 timeIncrement = ti * 1000; /* convert to ms */
1274 movesPerSession = 0;
1277 movesPerSession = mps;
1285 if (appData.debugMode) {
1286 fprintf(debugFP, "%s\n", programVersion);
1289 set_cont_sequence(appData.wrapContSeq);
1290 if (appData.matchGames > 0) {
1291 appData.matchMode = TRUE;
1292 } else if (appData.matchMode) {
1293 appData.matchGames = 1;
1295 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1296 appData.matchGames = appData.sameColorGames;
1297 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1298 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1299 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1302 if (appData.noChessProgram || first.protocolVersion == 1) {
1305 /* kludge: allow timeout for initial "feature" commands */
1307 DisplayMessage("", _("Starting chess program"));
1308 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1313 CalculateIndex(int index, int gameNr)
1314 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1316 if(index > 0) return index; // fixed nmber
1317 if(index == 0) return 1;
1318 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1319 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1324 LoadGameOrPosition(int gameNr)
1325 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1326 if (*appData.loadGameFile != NULLCHAR) {
1327 if (!LoadGameFromFile(appData.loadGameFile,
1328 CalculateIndex(appData.loadGameIndex, gameNr),
1329 appData.loadGameFile, FALSE)) {
1330 DisplayFatalError(_("Bad game file"), 0, 1);
1333 } else if (*appData.loadPositionFile != NULLCHAR) {
1334 if (!LoadPositionFromFile(appData.loadPositionFile,
1335 CalculateIndex(appData.loadPositionIndex, gameNr),
1336 appData.loadPositionFile)) {
1337 DisplayFatalError(_("Bad position file"), 0, 1);
1345 ReserveGame(int gameNr, char resChar)
1347 FILE *tf = fopen(appData.tourneyFile, "r+");
1348 char *p, *q, c, buf[MSG_SIZ];
1349 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1350 safeStrCpy(buf, lastMsg, MSG_SIZ);
1351 DisplayMessage(_("Pick new game"), "");
1352 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1353 ParseArgsFromFile(tf);
1354 p = q = appData.results;
1355 if(appData.debugMode) {
1356 char *r = appData.participants;
1357 fprintf(debugFP, "results = '%s'\n", p);
1358 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1359 fprintf(debugFP, "\n");
1361 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1363 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1364 safeStrCpy(q, p, strlen(p) + 2);
1365 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1366 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1367 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1368 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1371 fseek(tf, -(strlen(p)+4), SEEK_END);
1373 if(c != '"') // depending on DOS or Unix line endings we can be one off
1374 fseek(tf, -(strlen(p)+2), SEEK_END);
1375 else fseek(tf, -(strlen(p)+3), SEEK_END);
1376 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1377 DisplayMessage(buf, "");
1378 free(p); appData.results = q;
1379 if(nextGame <= appData.matchGames && resChar != ' ' &&
1380 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1381 UnloadEngine(&first); // next game belongs to other pairing;
1382 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1387 MatchEvent(int mode)
1388 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1390 if(matchMode) { // already in match mode: switch it off
1392 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1393 ModeHighlight(); // kludgey way to remove checkmark...
1396 // if(gameMode != BeginningOfGame) {
1397 // DisplayError(_("You can only start a match from the initial position."), 0);
1401 appData.matchGames = appData.defaultMatchGames;
1402 /* Set up machine vs. machine match */
1404 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1405 if(appData.tourneyFile[0]) {
1407 if(nextGame > appData.matchGames) {
1409 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1410 DisplayError(buf, 0);
1411 appData.tourneyFile[0] = 0;
1415 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1416 DisplayFatalError(_("Can't have a match with no chess programs"),
1421 matchGame = roundNr = 1;
1422 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1427 InitBackEnd3 P((void))
1429 GameMode initialMode;
1433 InitChessProgram(&first, startedFromSetupPosition);
1435 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1436 free(programVersion);
1437 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1438 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1441 if (appData.icsActive) {
1443 /* [DM] Make a console window if needed [HGM] merged ifs */
1449 if (*appData.icsCommPort != NULLCHAR)
1450 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1451 appData.icsCommPort);
1453 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1454 appData.icsHost, appData.icsPort);
1456 if( (len > MSG_SIZ) && appData.debugMode )
1457 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1459 DisplayFatalError(buf, err, 1);
1464 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1466 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1467 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1468 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 } else if (appData.noChessProgram) {
1475 if (*appData.cmailGameName != NULLCHAR) {
1477 OpenLoopback(&cmailPR);
1479 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1483 DisplayMessage("", "");
1484 if (StrCaseCmp(appData.initialMode, "") == 0) {
1485 initialMode = BeginningOfGame;
1486 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1487 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1488 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1489 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1492 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1493 initialMode = TwoMachinesPlay;
1494 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1495 initialMode = AnalyzeFile;
1496 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1497 initialMode = AnalyzeMode;
1498 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1499 initialMode = MachinePlaysWhite;
1500 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1501 initialMode = MachinePlaysBlack;
1502 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1503 initialMode = EditGame;
1504 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1505 initialMode = EditPosition;
1506 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1507 initialMode = Training;
1509 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1510 if( (len > MSG_SIZ) && appData.debugMode )
1511 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1513 DisplayFatalError(buf, 0, 2);
1517 if (appData.matchMode) {
1518 if(appData.tourneyFile[0]) { // start tourney from command line
1520 if(f = fopen(appData.tourneyFile, "r")) {
1521 ParseArgsFromFile(f); // make sure tourney parmeters re known
1523 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1526 } else if (*appData.cmailGameName != NULLCHAR) {
1527 /* Set up cmail mode */
1528 ReloadCmailMsgEvent(TRUE);
1530 /* Set up other modes */
1531 if (initialMode == AnalyzeFile) {
1532 if (*appData.loadGameFile == NULLCHAR) {
1533 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1537 if (*appData.loadGameFile != NULLCHAR) {
1538 (void) LoadGameFromFile(appData.loadGameFile,
1539 appData.loadGameIndex,
1540 appData.loadGameFile, TRUE);
1541 } else if (*appData.loadPositionFile != NULLCHAR) {
1542 (void) LoadPositionFromFile(appData.loadPositionFile,
1543 appData.loadPositionIndex,
1544 appData.loadPositionFile);
1545 /* [HGM] try to make self-starting even after FEN load */
1546 /* to allow automatic setup of fairy variants with wtm */
1547 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1548 gameMode = BeginningOfGame;
1549 setboardSpoiledMachineBlack = 1;
1551 /* [HGM] loadPos: make that every new game uses the setup */
1552 /* from file as long as we do not switch variant */
1553 if(!blackPlaysFirst) {
1554 startedFromPositionFile = TRUE;
1555 CopyBoard(filePosition, boards[0]);
1558 if (initialMode == AnalyzeMode) {
1559 if (appData.noChessProgram) {
1560 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1563 if (appData.icsActive) {
1564 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1568 } else if (initialMode == AnalyzeFile) {
1569 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1570 ShowThinkingEvent();
1572 AnalysisPeriodicEvent(1);
1573 } else if (initialMode == MachinePlaysWhite) {
1574 if (appData.noChessProgram) {
1575 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1579 if (appData.icsActive) {
1580 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1584 MachineWhiteEvent();
1585 } else if (initialMode == MachinePlaysBlack) {
1586 if (appData.noChessProgram) {
1587 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1591 if (appData.icsActive) {
1592 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1596 MachineBlackEvent();
1597 } else if (initialMode == TwoMachinesPlay) {
1598 if (appData.noChessProgram) {
1599 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1603 if (appData.icsActive) {
1604 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1609 } else if (initialMode == EditGame) {
1611 } else if (initialMode == EditPosition) {
1612 EditPositionEvent();
1613 } else if (initialMode == Training) {
1614 if (*appData.loadGameFile == NULLCHAR) {
1615 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1624 * Establish will establish a contact to a remote host.port.
1625 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1626 * used to talk to the host.
1627 * Returns 0 if okay, error code if not.
1634 if (*appData.icsCommPort != NULLCHAR) {
1635 /* Talk to the host through a serial comm port */
1636 return OpenCommPort(appData.icsCommPort, &icsPR);
1638 } else if (*appData.gateway != NULLCHAR) {
1639 if (*appData.remoteShell == NULLCHAR) {
1640 /* Use the rcmd protocol to run telnet program on a gateway host */
1641 snprintf(buf, sizeof(buf), "%s %s %s",
1642 appData.telnetProgram, appData.icsHost, appData.icsPort);
1643 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1646 /* Use the rsh program to run telnet program on a gateway host */
1647 if (*appData.remoteUser == NULLCHAR) {
1648 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1649 appData.gateway, appData.telnetProgram,
1650 appData.icsHost, appData.icsPort);
1652 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1653 appData.remoteShell, appData.gateway,
1654 appData.remoteUser, appData.telnetProgram,
1655 appData.icsHost, appData.icsPort);
1657 return StartChildProcess(buf, "", &icsPR);
1660 } else if (appData.useTelnet) {
1661 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1664 /* TCP socket interface differs somewhat between
1665 Unix and NT; handle details in the front end.
1667 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1671 void EscapeExpand(char *p, char *q)
1672 { // [HGM] initstring: routine to shape up string arguments
1673 while(*p++ = *q++) if(p[-1] == '\\')
1675 case 'n': p[-1] = '\n'; break;
1676 case 'r': p[-1] = '\r'; break;
1677 case 't': p[-1] = '\t'; break;
1678 case '\\': p[-1] = '\\'; break;
1679 case 0: *p = 0; return;
1680 default: p[-1] = q[-1]; break;
1685 show_bytes(fp, buf, count)
1691 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1692 fprintf(fp, "\\%03o", *buf & 0xff);
1701 /* Returns an errno value */
1703 OutputMaybeTelnet(pr, message, count, outError)
1709 char buf[8192], *p, *q, *buflim;
1710 int left, newcount, outcount;
1712 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1713 *appData.gateway != NULLCHAR) {
1714 if (appData.debugMode) {
1715 fprintf(debugFP, ">ICS: ");
1716 show_bytes(debugFP, message, count);
1717 fprintf(debugFP, "\n");
1719 return OutputToProcess(pr, message, count, outError);
1722 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1729 if (appData.debugMode) {
1730 fprintf(debugFP, ">ICS: ");
1731 show_bytes(debugFP, buf, newcount);
1732 fprintf(debugFP, "\n");
1734 outcount = OutputToProcess(pr, buf, newcount, outError);
1735 if (outcount < newcount) return -1; /* to be sure */
1742 } else if (((unsigned char) *p) == TN_IAC) {
1743 *q++ = (char) TN_IAC;
1750 if (appData.debugMode) {
1751 fprintf(debugFP, ">ICS: ");
1752 show_bytes(debugFP, buf, newcount);
1753 fprintf(debugFP, "\n");
1755 outcount = OutputToProcess(pr, buf, newcount, outError);
1756 if (outcount < newcount) return -1; /* to be sure */
1761 read_from_player(isr, closure, message, count, error)
1768 int outError, outCount;
1769 static int gotEof = 0;
1771 /* Pass data read from player on to ICS */
1774 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1775 if (outCount < count) {
1776 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1778 } else if (count < 0) {
1779 RemoveInputSource(isr);
1780 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1781 } else if (gotEof++ > 0) {
1782 RemoveInputSource(isr);
1783 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1789 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1790 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1791 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1792 SendToICS("date\n");
1793 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1796 /* added routine for printf style output to ics */
1797 void ics_printf(char *format, ...)
1799 char buffer[MSG_SIZ];
1802 va_start(args, format);
1803 vsnprintf(buffer, sizeof(buffer), format, args);
1804 buffer[sizeof(buffer)-1] = '\0';
1813 int count, outCount, outError;
1815 if (icsPR == NULL) return;
1818 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1819 if (outCount < count) {
1820 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1824 /* This is used for sending logon scripts to the ICS. Sending
1825 without a delay causes problems when using timestamp on ICC
1826 (at least on my machine). */
1828 SendToICSDelayed(s,msdelay)
1832 int count, outCount, outError;
1834 if (icsPR == NULL) return;
1837 if (appData.debugMode) {
1838 fprintf(debugFP, ">ICS: ");
1839 show_bytes(debugFP, s, count);
1840 fprintf(debugFP, "\n");
1842 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1844 if (outCount < count) {
1845 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1850 /* Remove all highlighting escape sequences in s
1851 Also deletes any suffix starting with '('
1854 StripHighlightAndTitle(s)
1857 static char retbuf[MSG_SIZ];
1860 while (*s != NULLCHAR) {
1861 while (*s == '\033') {
1862 while (*s != NULLCHAR && !isalpha(*s)) s++;
1863 if (*s != NULLCHAR) s++;
1865 while (*s != NULLCHAR && *s != '\033') {
1866 if (*s == '(' || *s == '[') {
1877 /* Remove all highlighting escape sequences in s */
1882 static char retbuf[MSG_SIZ];
1885 while (*s != NULLCHAR) {
1886 while (*s == '\033') {
1887 while (*s != NULLCHAR && !isalpha(*s)) s++;
1888 if (*s != NULLCHAR) s++;
1890 while (*s != NULLCHAR && *s != '\033') {
1898 char *variantNames[] = VARIANT_NAMES;
1903 return variantNames[v];
1907 /* Identify a variant from the strings the chess servers use or the
1908 PGN Variant tag names we use. */
1915 VariantClass v = VariantNormal;
1916 int i, found = FALSE;
1922 /* [HGM] skip over optional board-size prefixes */
1923 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1924 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1925 while( *e++ != '_');
1928 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1932 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1933 if (StrCaseStr(e, variantNames[i])) {
1934 v = (VariantClass) i;
1941 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1942 || StrCaseStr(e, "wild/fr")
1943 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1944 v = VariantFischeRandom;
1945 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1946 (i = 1, p = StrCaseStr(e, "w"))) {
1948 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1955 case 0: /* FICS only, actually */
1957 /* Castling legal even if K starts on d-file */
1958 v = VariantWildCastle;
1963 /* Castling illegal even if K & R happen to start in
1964 normal positions. */
1965 v = VariantNoCastle;
1978 /* Castling legal iff K & R start in normal positions */
1984 /* Special wilds for position setup; unclear what to do here */
1985 v = VariantLoadable;
1988 /* Bizarre ICC game */
1989 v = VariantTwoKings;
1992 v = VariantKriegspiel;
1998 v = VariantFischeRandom;
2001 v = VariantCrazyhouse;
2004 v = VariantBughouse;
2010 /* Not quite the same as FICS suicide! */
2011 v = VariantGiveaway;
2017 v = VariantShatranj;
2020 /* Temporary names for future ICC types. The name *will* change in
2021 the next xboard/WinBoard release after ICC defines it. */
2059 v = VariantCapablanca;
2062 v = VariantKnightmate;
2068 v = VariantCylinder;
2074 v = VariantCapaRandom;
2077 v = VariantBerolina;
2089 /* Found "wild" or "w" in the string but no number;
2090 must assume it's normal chess. */
2094 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2095 if( (len > MSG_SIZ) && appData.debugMode )
2096 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2098 DisplayError(buf, 0);
2104 if (appData.debugMode) {
2105 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2106 e, wnum, VariantName(v));
2111 static int leftover_start = 0, leftover_len = 0;
2112 char star_match[STAR_MATCH_N][MSG_SIZ];
2114 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2115 advance *index beyond it, and set leftover_start to the new value of
2116 *index; else return FALSE. If pattern contains the character '*', it
2117 matches any sequence of characters not containing '\r', '\n', or the
2118 character following the '*' (if any), and the matched sequence(s) are
2119 copied into star_match.
2122 looking_at(buf, index, pattern)
2127 char *bufp = &buf[*index], *patternp = pattern;
2129 char *matchp = star_match[0];
2132 if (*patternp == NULLCHAR) {
2133 *index = leftover_start = bufp - buf;
2137 if (*bufp == NULLCHAR) return FALSE;
2138 if (*patternp == '*') {
2139 if (*bufp == *(patternp + 1)) {
2141 matchp = star_match[++star_count];
2145 } else if (*bufp == '\n' || *bufp == '\r') {
2147 if (*patternp == NULLCHAR)
2152 *matchp++ = *bufp++;
2156 if (*patternp != *bufp) return FALSE;
2163 SendToPlayer(data, length)
2167 int error, outCount;
2168 outCount = OutputToProcess(NoProc, data, length, &error);
2169 if (outCount < length) {
2170 DisplayFatalError(_("Error writing to display"), error, 1);
2175 PackHolding(packed, holding)
2187 switch (runlength) {
2198 sprintf(q, "%d", runlength);
2210 /* Telnet protocol requests from the front end */
2212 TelnetRequest(ddww, option)
2213 unsigned char ddww, option;
2215 unsigned char msg[3];
2216 int outCount, outError;
2218 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2220 if (appData.debugMode) {
2221 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2237 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2246 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2249 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2254 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2256 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2263 if (!appData.icsActive) return;
2264 TelnetRequest(TN_DO, TN_ECHO);
2270 if (!appData.icsActive) return;
2271 TelnetRequest(TN_DONT, TN_ECHO);
2275 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2277 /* put the holdings sent to us by the server on the board holdings area */
2278 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2282 if(gameInfo.holdingsWidth < 2) return;
2283 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2284 return; // prevent overwriting by pre-board holdings
2286 if( (int)lowestPiece >= BlackPawn ) {
2289 holdingsStartRow = BOARD_HEIGHT-1;
2292 holdingsColumn = BOARD_WIDTH-1;
2293 countsColumn = BOARD_WIDTH-2;
2294 holdingsStartRow = 0;
2298 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2299 board[i][holdingsColumn] = EmptySquare;
2300 board[i][countsColumn] = (ChessSquare) 0;
2302 while( (p=*holdings++) != NULLCHAR ) {
2303 piece = CharToPiece( ToUpper(p) );
2304 if(piece == EmptySquare) continue;
2305 /*j = (int) piece - (int) WhitePawn;*/
2306 j = PieceToNumber(piece);
2307 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2308 if(j < 0) continue; /* should not happen */
2309 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2310 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2311 board[holdingsStartRow+j*direction][countsColumn]++;
2317 VariantSwitch(Board board, VariantClass newVariant)
2319 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2320 static Board oldBoard;
2322 startedFromPositionFile = FALSE;
2323 if(gameInfo.variant == newVariant) return;
2325 /* [HGM] This routine is called each time an assignment is made to
2326 * gameInfo.variant during a game, to make sure the board sizes
2327 * are set to match the new variant. If that means adding or deleting
2328 * holdings, we shift the playing board accordingly
2329 * This kludge is needed because in ICS observe mode, we get boards
2330 * of an ongoing game without knowing the variant, and learn about the
2331 * latter only later. This can be because of the move list we requested,
2332 * in which case the game history is refilled from the beginning anyway,
2333 * but also when receiving holdings of a crazyhouse game. In the latter
2334 * case we want to add those holdings to the already received position.
2338 if (appData.debugMode) {
2339 fprintf(debugFP, "Switch board from %s to %s\n",
2340 VariantName(gameInfo.variant), VariantName(newVariant));
2341 setbuf(debugFP, NULL);
2343 shuffleOpenings = 0; /* [HGM] shuffle */
2344 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2348 newWidth = 9; newHeight = 9;
2349 gameInfo.holdingsSize = 7;
2350 case VariantBughouse:
2351 case VariantCrazyhouse:
2352 newHoldingsWidth = 2; break;
2356 newHoldingsWidth = 2;
2357 gameInfo.holdingsSize = 8;
2360 case VariantCapablanca:
2361 case VariantCapaRandom:
2364 newHoldingsWidth = gameInfo.holdingsSize = 0;
2367 if(newWidth != gameInfo.boardWidth ||
2368 newHeight != gameInfo.boardHeight ||
2369 newHoldingsWidth != gameInfo.holdingsWidth ) {
2371 /* shift position to new playing area, if needed */
2372 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2373 for(i=0; i<BOARD_HEIGHT; i++)
2374 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2375 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2377 for(i=0; i<newHeight; i++) {
2378 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2379 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2381 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2382 for(i=0; i<BOARD_HEIGHT; i++)
2383 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2384 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2387 gameInfo.boardWidth = newWidth;
2388 gameInfo.boardHeight = newHeight;
2389 gameInfo.holdingsWidth = newHoldingsWidth;
2390 gameInfo.variant = newVariant;
2391 InitDrawingSizes(-2, 0);
2392 } else gameInfo.variant = newVariant;
2393 CopyBoard(oldBoard, board); // remember correctly formatted board
2394 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2395 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2398 static int loggedOn = FALSE;
2400 /*-- Game start info cache: --*/
2402 char gs_kind[MSG_SIZ];
2403 static char player1Name[128] = "";
2404 static char player2Name[128] = "";
2405 static char cont_seq[] = "\n\\ ";
2406 static int player1Rating = -1;
2407 static int player2Rating = -1;
2408 /*----------------------------*/
2410 ColorClass curColor = ColorNormal;
2411 int suppressKibitz = 0;
2414 Boolean soughtPending = FALSE;
2415 Boolean seekGraphUp;
2416 #define MAX_SEEK_ADS 200
2418 char *seekAdList[MAX_SEEK_ADS];
2419 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2420 float tcList[MAX_SEEK_ADS];
2421 char colorList[MAX_SEEK_ADS];
2422 int nrOfSeekAds = 0;
2423 int minRating = 1010, maxRating = 2800;
2424 int hMargin = 10, vMargin = 20, h, w;
2425 extern int squareSize, lineGap;
2430 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2431 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2432 if(r < minRating+100 && r >=0 ) r = minRating+100;
2433 if(r > maxRating) r = maxRating;
2434 if(tc < 1.) tc = 1.;
2435 if(tc > 95.) tc = 95.;
2436 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2437 y = ((double)r - minRating)/(maxRating - minRating)
2438 * (h-vMargin-squareSize/8-1) + vMargin;
2439 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2440 if(strstr(seekAdList[i], " u ")) color = 1;
2441 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2442 !strstr(seekAdList[i], "bullet") &&
2443 !strstr(seekAdList[i], "blitz") &&
2444 !strstr(seekAdList[i], "standard") ) color = 2;
2445 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2446 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2450 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2452 char buf[MSG_SIZ], *ext = "";
2453 VariantClass v = StringToVariant(type);
2454 if(strstr(type, "wild")) {
2455 ext = type + 4; // append wild number
2456 if(v == VariantFischeRandom) type = "chess960"; else
2457 if(v == VariantLoadable) type = "setup"; else
2458 type = VariantName(v);
2460 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2461 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2462 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2463 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2464 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2465 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2466 seekNrList[nrOfSeekAds] = nr;
2467 zList[nrOfSeekAds] = 0;
2468 seekAdList[nrOfSeekAds++] = StrSave(buf);
2469 if(plot) PlotSeekAd(nrOfSeekAds-1);
2476 int x = xList[i], y = yList[i], d=squareSize/4, k;
2477 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2478 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2479 // now replot every dot that overlapped
2480 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2481 int xx = xList[k], yy = yList[k];
2482 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2483 DrawSeekDot(xx, yy, colorList[k]);
2488 RemoveSeekAd(int nr)
2491 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2493 if(seekAdList[i]) free(seekAdList[i]);
2494 seekAdList[i] = seekAdList[--nrOfSeekAds];
2495 seekNrList[i] = seekNrList[nrOfSeekAds];
2496 ratingList[i] = ratingList[nrOfSeekAds];
2497 colorList[i] = colorList[nrOfSeekAds];
2498 tcList[i] = tcList[nrOfSeekAds];
2499 xList[i] = xList[nrOfSeekAds];
2500 yList[i] = yList[nrOfSeekAds];
2501 zList[i] = zList[nrOfSeekAds];
2502 seekAdList[nrOfSeekAds] = NULL;
2508 MatchSoughtLine(char *line)
2510 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2511 int nr, base, inc, u=0; char dummy;
2513 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2514 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2516 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2517 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2518 // match: compact and save the line
2519 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2529 if(!seekGraphUp) return FALSE;
2530 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2531 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2533 DrawSeekBackground(0, 0, w, h);
2534 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2535 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2536 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2537 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2539 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2542 snprintf(buf, MSG_SIZ, "%d", i);
2543 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2546 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2547 for(i=1; i<100; i+=(i<10?1:5)) {
2548 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2549 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2550 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2552 snprintf(buf, MSG_SIZ, "%d", i);
2553 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2556 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2560 int SeekGraphClick(ClickType click, int x, int y, int moving)
2562 static int lastDown = 0, displayed = 0, lastSecond;
2563 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2564 if(click == Release || moving) return FALSE;
2566 soughtPending = TRUE;
2567 SendToICS(ics_prefix);
2568 SendToICS("sought\n"); // should this be "sought all"?
2569 } else { // issue challenge based on clicked ad
2570 int dist = 10000; int i, closest = 0, second = 0;
2571 for(i=0; i<nrOfSeekAds; i++) {
2572 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2573 if(d < dist) { dist = d; closest = i; }
2574 second += (d - zList[i] < 120); // count in-range ads
2575 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2579 second = (second > 1);
2580 if(displayed != closest || second != lastSecond) {
2581 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2582 lastSecond = second; displayed = closest;
2584 if(click == Press) {
2585 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2588 } // on press 'hit', only show info
2589 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2590 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2591 SendToICS(ics_prefix);
2593 return TRUE; // let incoming board of started game pop down the graph
2594 } else if(click == Release) { // release 'miss' is ignored
2595 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2596 if(moving == 2) { // right up-click
2597 nrOfSeekAds = 0; // refresh graph
2598 soughtPending = TRUE;
2599 SendToICS(ics_prefix);
2600 SendToICS("sought\n"); // should this be "sought all"?
2603 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2604 // press miss or release hit 'pop down' seek graph
2605 seekGraphUp = FALSE;
2606 DrawPosition(TRUE, NULL);
2612 read_from_ics(isr, closure, data, count, error)
2619 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2620 #define STARTED_NONE 0
2621 #define STARTED_MOVES 1
2622 #define STARTED_BOARD 2
2623 #define STARTED_OBSERVE 3
2624 #define STARTED_HOLDINGS 4
2625 #define STARTED_CHATTER 5
2626 #define STARTED_COMMENT 6
2627 #define STARTED_MOVES_NOHIDE 7
2629 static int started = STARTED_NONE;
2630 static char parse[20000];
2631 static int parse_pos = 0;
2632 static char buf[BUF_SIZE + 1];
2633 static int firstTime = TRUE, intfSet = FALSE;
2634 static ColorClass prevColor = ColorNormal;
2635 static int savingComment = FALSE;
2636 static int cmatch = 0; // continuation sequence match
2643 int backup; /* [DM] For zippy color lines */
2645 char talker[MSG_SIZ]; // [HGM] chat
2648 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2650 if (appData.debugMode) {
2652 fprintf(debugFP, "<ICS: ");
2653 show_bytes(debugFP, data, count);
2654 fprintf(debugFP, "\n");
2658 if (appData.debugMode) { int f = forwardMostMove;
2659 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2660 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2661 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2664 /* If last read ended with a partial line that we couldn't parse,
2665 prepend it to the new read and try again. */
2666 if (leftover_len > 0) {
2667 for (i=0; i<leftover_len; i++)
2668 buf[i] = buf[leftover_start + i];
2671 /* copy new characters into the buffer */
2672 bp = buf + leftover_len;
2673 buf_len=leftover_len;
2674 for (i=0; i<count; i++)
2677 if (data[i] == '\r')
2680 // join lines split by ICS?
2681 if (!appData.noJoin)
2684 Joining just consists of finding matches against the
2685 continuation sequence, and discarding that sequence
2686 if found instead of copying it. So, until a match
2687 fails, there's nothing to do since it might be the
2688 complete sequence, and thus, something we don't want
2691 if (data[i] == cont_seq[cmatch])
2694 if (cmatch == strlen(cont_seq))
2696 cmatch = 0; // complete match. just reset the counter
2699 it's possible for the ICS to not include the space
2700 at the end of the last word, making our [correct]
2701 join operation fuse two separate words. the server
2702 does this when the space occurs at the width setting.
2704 if (!buf_len || buf[buf_len-1] != ' ')
2715 match failed, so we have to copy what matched before
2716 falling through and copying this character. In reality,
2717 this will only ever be just the newline character, but
2718 it doesn't hurt to be precise.
2720 strncpy(bp, cont_seq, cmatch);
2732 buf[buf_len] = NULLCHAR;
2733 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2738 while (i < buf_len) {
2739 /* Deal with part of the TELNET option negotiation
2740 protocol. We refuse to do anything beyond the
2741 defaults, except that we allow the WILL ECHO option,
2742 which ICS uses to turn off password echoing when we are
2743 directly connected to it. We reject this option
2744 if localLineEditing mode is on (always on in xboard)
2745 and we are talking to port 23, which might be a real
2746 telnet server that will try to keep WILL ECHO on permanently.
2748 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2749 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2750 unsigned char option;
2752 switch ((unsigned char) buf[++i]) {
2754 if (appData.debugMode)
2755 fprintf(debugFP, "\n<WILL ");
2756 switch (option = (unsigned char) buf[++i]) {
2758 if (appData.debugMode)
2759 fprintf(debugFP, "ECHO ");
2760 /* Reply only if this is a change, according
2761 to the protocol rules. */
2762 if (remoteEchoOption) break;
2763 if (appData.localLineEditing &&
2764 atoi(appData.icsPort) == TN_PORT) {
2765 TelnetRequest(TN_DONT, TN_ECHO);
2768 TelnetRequest(TN_DO, TN_ECHO);
2769 remoteEchoOption = TRUE;
2773 if (appData.debugMode)
2774 fprintf(debugFP, "%d ", option);
2775 /* Whatever this is, we don't want it. */
2776 TelnetRequest(TN_DONT, option);
2781 if (appData.debugMode)
2782 fprintf(debugFP, "\n<WONT ");
2783 switch (option = (unsigned char) buf[++i]) {
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ECHO ");
2787 /* Reply only if this is a change, according
2788 to the protocol rules. */
2789 if (!remoteEchoOption) break;
2791 TelnetRequest(TN_DONT, TN_ECHO);
2792 remoteEchoOption = FALSE;
2795 if (appData.debugMode)
2796 fprintf(debugFP, "%d ", (unsigned char) option);
2797 /* Whatever this is, it must already be turned
2798 off, because we never agree to turn on
2799 anything non-default, so according to the
2800 protocol rules, we don't reply. */
2805 if (appData.debugMode)
2806 fprintf(debugFP, "\n<DO ");
2807 switch (option = (unsigned char) buf[++i]) {
2809 /* Whatever this is, we refuse to do it. */
2810 if (appData.debugMode)
2811 fprintf(debugFP, "%d ", option);
2812 TelnetRequest(TN_WONT, option);
2817 if (appData.debugMode)
2818 fprintf(debugFP, "\n<DONT ");
2819 switch (option = (unsigned char) buf[++i]) {
2821 if (appData.debugMode)
2822 fprintf(debugFP, "%d ", option);
2823 /* Whatever this is, we are already not doing
2824 it, because we never agree to do anything
2825 non-default, so according to the protocol
2826 rules, we don't reply. */
2831 if (appData.debugMode)
2832 fprintf(debugFP, "\n<IAC ");
2833 /* Doubled IAC; pass it through */
2837 if (appData.debugMode)
2838 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2839 /* Drop all other telnet commands on the floor */
2842 if (oldi > next_out)
2843 SendToPlayer(&buf[next_out], oldi - next_out);
2849 /* OK, this at least will *usually* work */
2850 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2854 if (loggedOn && !intfSet) {
2855 if (ics_type == ICS_ICC) {
2856 snprintf(str, MSG_SIZ,
2857 "/set-quietly interface %s\n/set-quietly style 12\n",
2859 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2860 strcat(str, "/set-2 51 1\n/set seek 1\n");
2861 } else if (ics_type == ICS_CHESSNET) {
2862 snprintf(str, MSG_SIZ, "/style 12\n");
2864 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2865 strcat(str, programVersion);
2866 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2867 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2868 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2870 strcat(str, "$iset nohighlight 1\n");
2872 strcat(str, "$iset lock 1\n$style 12\n");
2875 NotifyFrontendLogin();
2879 if (started == STARTED_COMMENT) {
2880 /* Accumulate characters in comment */
2881 parse[parse_pos++] = buf[i];
2882 if (buf[i] == '\n') {
2883 parse[parse_pos] = NULLCHAR;
2884 if(chattingPartner>=0) {
2886 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2887 OutputChatMessage(chattingPartner, mess);
2888 chattingPartner = -1;
2889 next_out = i+1; // [HGM] suppress printing in ICS window
2891 if(!suppressKibitz) // [HGM] kibitz
2892 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2893 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2894 int nrDigit = 0, nrAlph = 0, j;
2895 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2896 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2897 parse[parse_pos] = NULLCHAR;
2898 // try to be smart: if it does not look like search info, it should go to
2899 // ICS interaction window after all, not to engine-output window.
2900 for(j=0; j<parse_pos; j++) { // count letters and digits
2901 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2902 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2903 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2905 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2906 int depth=0; float score;
2907 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2908 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2909 pvInfoList[forwardMostMove-1].depth = depth;
2910 pvInfoList[forwardMostMove-1].score = 100*score;
2912 OutputKibitz(suppressKibitz, parse);
2915 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2916 SendToPlayer(tmp, strlen(tmp));
2918 next_out = i+1; // [HGM] suppress printing in ICS window
2920 started = STARTED_NONE;
2922 /* Don't match patterns against characters in comment */
2927 if (started == STARTED_CHATTER) {
2928 if (buf[i] != '\n') {
2929 /* Don't match patterns against characters in chatter */
2933 started = STARTED_NONE;
2934 if(suppressKibitz) next_out = i+1;
2937 /* Kludge to deal with rcmd protocol */
2938 if (firstTime && looking_at(buf, &i, "\001*")) {
2939 DisplayFatalError(&buf[1], 0, 1);
2945 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2948 if (appData.debugMode)
2949 fprintf(debugFP, "ics_type %d\n", ics_type);
2952 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2953 ics_type = ICS_FICS;
2955 if (appData.debugMode)
2956 fprintf(debugFP, "ics_type %d\n", ics_type);
2959 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2960 ics_type = ICS_CHESSNET;
2962 if (appData.debugMode)
2963 fprintf(debugFP, "ics_type %d\n", ics_type);
2968 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2969 looking_at(buf, &i, "Logging you in as \"*\"") ||
2970 looking_at(buf, &i, "will be \"*\""))) {
2971 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2975 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2977 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2978 DisplayIcsInteractionTitle(buf);
2979 have_set_title = TRUE;
2982 /* skip finger notes */
2983 if (started == STARTED_NONE &&
2984 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2985 (buf[i] == '1' && buf[i+1] == '0')) &&
2986 buf[i+2] == ':' && buf[i+3] == ' ') {
2987 started = STARTED_CHATTER;
2993 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2994 if(appData.seekGraph) {
2995 if(soughtPending && MatchSoughtLine(buf+i)) {
2996 i = strstr(buf+i, "rated") - buf;
2997 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2998 next_out = leftover_start = i;
2999 started = STARTED_CHATTER;
3000 suppressKibitz = TRUE;
3003 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3004 && looking_at(buf, &i, "* ads displayed")) {
3005 soughtPending = FALSE;
3010 if(appData.autoRefresh) {
3011 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3012 int s = (ics_type == ICS_ICC); // ICC format differs
3014 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3015 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3016 looking_at(buf, &i, "*% "); // eat prompt
3017 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3018 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019 next_out = i; // suppress
3022 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3023 char *p = star_match[0];
3025 if(seekGraphUp) RemoveSeekAd(atoi(p));
3026 while(*p && *p++ != ' '); // next
3028 looking_at(buf, &i, "*% "); // eat prompt
3029 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3036 /* skip formula vars */
3037 if (started == STARTED_NONE &&
3038 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3039 started = STARTED_CHATTER;
3044 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3045 if (appData.autoKibitz && started == STARTED_NONE &&
3046 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3047 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3048 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3049 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3050 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3051 suppressKibitz = TRUE;
3052 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3055 && (gameMode == IcsPlayingWhite)) ||
3056 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3057 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3058 started = STARTED_CHATTER; // own kibitz we simply discard
3060 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3061 parse_pos = 0; parse[0] = NULLCHAR;
3062 savingComment = TRUE;
3063 suppressKibitz = gameMode != IcsObserving ? 2 :
3064 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3068 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3069 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3070 && atoi(star_match[0])) {
3071 // suppress the acknowledgements of our own autoKibitz
3073 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3075 SendToPlayer(star_match[0], strlen(star_match[0]));
3076 if(looking_at(buf, &i, "*% ")) // eat prompt
3077 suppressKibitz = FALSE;
3081 } // [HGM] kibitz: end of patch
3083 // [HGM] chat: intercept tells by users for which we have an open chat window
3085 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3086 looking_at(buf, &i, "* whispers:") ||
3087 looking_at(buf, &i, "* kibitzes:") ||
3088 looking_at(buf, &i, "* shouts:") ||
3089 looking_at(buf, &i, "* c-shouts:") ||
3090 looking_at(buf, &i, "--> * ") ||
3091 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3092 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3093 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3094 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3096 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3097 chattingPartner = -1;
3099 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3100 for(p=0; p<MAX_CHAT; p++) {
3101 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3102 talker[0] = '['; strcat(talker, "] ");
3103 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3104 chattingPartner = p; break;
3107 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3108 for(p=0; p<MAX_CHAT; p++) {
3109 if(!strcmp("kibitzes", chatPartner[p])) {
3110 talker[0] = '['; strcat(talker, "] ");
3111 chattingPartner = p; break;
3114 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3115 for(p=0; p<MAX_CHAT; p++) {
3116 if(!strcmp("whispers", chatPartner[p])) {
3117 talker[0] = '['; strcat(talker, "] ");
3118 chattingPartner = p; break;
3121 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3122 if(buf[i-8] == '-' && buf[i-3] == 't')
3123 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3124 if(!strcmp("c-shouts", chatPartner[p])) {
3125 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3126 chattingPartner = p; break;
3129 if(chattingPartner < 0)
3130 for(p=0; p<MAX_CHAT; p++) {
3131 if(!strcmp("shouts", chatPartner[p])) {
3132 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3133 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3134 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3135 chattingPartner = p; break;
3139 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3140 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3141 talker[0] = 0; Colorize(ColorTell, FALSE);
3142 chattingPartner = p; break;
3144 if(chattingPartner<0) i = oldi; else {
3145 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3146 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3147 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3148 started = STARTED_COMMENT;
3149 parse_pos = 0; parse[0] = NULLCHAR;
3150 savingComment = 3 + chattingPartner; // counts as TRUE
3151 suppressKibitz = TRUE;
3154 } // [HGM] chat: end of patch
3157 if (appData.zippyTalk || appData.zippyPlay) {
3158 /* [DM] Backup address for color zippy lines */
3160 if (loggedOn == TRUE)
3161 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3162 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3164 } // [DM] 'else { ' deleted
3166 /* Regular tells and says */
3167 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3168 looking_at(buf, &i, "* (your partner) tells you: ") ||
3169 looking_at(buf, &i, "* says: ") ||
3170 /* Don't color "message" or "messages" output */
3171 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3172 looking_at(buf, &i, "*. * at *:*: ") ||
3173 looking_at(buf, &i, "--* (*:*): ") ||
3174 /* Message notifications (same color as tells) */
3175 looking_at(buf, &i, "* has left a message ") ||
3176 looking_at(buf, &i, "* just sent you a message:\n") ||
3177 /* Whispers and kibitzes */
3178 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3179 looking_at(buf, &i, "* kibitzes: ") ||
3181 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3183 if (tkind == 1 && strchr(star_match[0], ':')) {
3184 /* Avoid "tells you:" spoofs in channels */
3187 if (star_match[0][0] == NULLCHAR ||
3188 strchr(star_match[0], ' ') ||
3189 (tkind == 3 && strchr(star_match[1], ' '))) {
3190 /* Reject bogus matches */
3193 if (appData.colorize) {
3194 if (oldi > next_out) {
3195 SendToPlayer(&buf[next_out], oldi - next_out);
3200 Colorize(ColorTell, FALSE);
3201 curColor = ColorTell;
3204 Colorize(ColorKibitz, FALSE);
3205 curColor = ColorKibitz;
3208 p = strrchr(star_match[1], '(');
3215 Colorize(ColorChannel1, FALSE);
3216 curColor = ColorChannel1;
3218 Colorize(ColorChannel, FALSE);
3219 curColor = ColorChannel;
3223 curColor = ColorNormal;
3227 if (started == STARTED_NONE && appData.autoComment &&
3228 (gameMode == IcsObserving ||
3229 gameMode == IcsPlayingWhite ||
3230 gameMode == IcsPlayingBlack)) {
3231 parse_pos = i - oldi;
3232 memcpy(parse, &buf[oldi], parse_pos);
3233 parse[parse_pos] = NULLCHAR;
3234 started = STARTED_COMMENT;
3235 savingComment = TRUE;
3237 started = STARTED_CHATTER;
3238 savingComment = FALSE;
3245 if (looking_at(buf, &i, "* s-shouts: ") ||
3246 looking_at(buf, &i, "* c-shouts: ")) {
3247 if (appData.colorize) {
3248 if (oldi > next_out) {
3249 SendToPlayer(&buf[next_out], oldi - next_out);
3252 Colorize(ColorSShout, FALSE);
3253 curColor = ColorSShout;
3256 started = STARTED_CHATTER;
3260 if (looking_at(buf, &i, "--->")) {
3265 if (looking_at(buf, &i, "* shouts: ") ||
3266 looking_at(buf, &i, "--> ")) {
3267 if (appData.colorize) {
3268 if (oldi > next_out) {
3269 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorShout, FALSE);
3273 curColor = ColorShout;
3276 started = STARTED_CHATTER;
3280 if (looking_at( buf, &i, "Challenge:")) {
3281 if (appData.colorize) {
3282 if (oldi > next_out) {
3283 SendToPlayer(&buf[next_out], oldi - next_out);
3286 Colorize(ColorChallenge, FALSE);
3287 curColor = ColorChallenge;
3293 if (looking_at(buf, &i, "* offers you") ||
3294 looking_at(buf, &i, "* offers to be") ||
3295 looking_at(buf, &i, "* would like to") ||
3296 looking_at(buf, &i, "* requests to") ||
3297 looking_at(buf, &i, "Your opponent offers") ||
3298 looking_at(buf, &i, "Your opponent requests")) {
3300 if (appData.colorize) {
3301 if (oldi > next_out) {
3302 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorRequest, FALSE);
3306 curColor = ColorRequest;
3311 if (looking_at(buf, &i, "* (*) seeking")) {
3312 if (appData.colorize) {
3313 if (oldi > next_out) {
3314 SendToPlayer(&buf[next_out], oldi - next_out);
3317 Colorize(ColorSeek, FALSE);
3318 curColor = ColorSeek;
3323 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3325 if (looking_at(buf, &i, "\\ ")) {
3326 if (prevColor != ColorNormal) {
3327 if (oldi > next_out) {
3328 SendToPlayer(&buf[next_out], oldi - next_out);
3331 Colorize(prevColor, TRUE);
3332 curColor = prevColor;
3334 if (savingComment) {
3335 parse_pos = i - oldi;
3336 memcpy(parse, &buf[oldi], parse_pos);
3337 parse[parse_pos] = NULLCHAR;
3338 started = STARTED_COMMENT;
3339 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3340 chattingPartner = savingComment - 3; // kludge to remember the box
3342 started = STARTED_CHATTER;
3347 if (looking_at(buf, &i, "Black Strength :") ||
3348 looking_at(buf, &i, "<<< style 10 board >>>") ||
3349 looking_at(buf, &i, "<10>") ||
3350 looking_at(buf, &i, "#@#")) {
3351 /* Wrong board style */
3353 SendToICS(ics_prefix);
3354 SendToICS("set style 12\n");
3355 SendToICS(ics_prefix);
3356 SendToICS("refresh\n");
3360 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3362 have_sent_ICS_logon = 1;
3366 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3367 (looking_at(buf, &i, "\n<12> ") ||
3368 looking_at(buf, &i, "<12> "))) {
3370 if (oldi > next_out) {
3371 SendToPlayer(&buf[next_out], oldi - next_out);
3374 started = STARTED_BOARD;
3379 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3380 looking_at(buf, &i, "<b1> ")) {
3381 if (oldi > next_out) {
3382 SendToPlayer(&buf[next_out], oldi - next_out);
3385 started = STARTED_HOLDINGS;
3390 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3392 /* Header for a move list -- first line */
3394 switch (ics_getting_history) {
3398 case BeginningOfGame:
3399 /* User typed "moves" or "oldmoves" while we
3400 were idle. Pretend we asked for these
3401 moves and soak them up so user can step
3402 through them and/or save them.
3405 gameMode = IcsObserving;
3408 ics_getting_history = H_GOT_UNREQ_HEADER;
3410 case EditGame: /*?*/
3411 case EditPosition: /*?*/
3412 /* Should above feature work in these modes too? */
3413 /* For now it doesn't */
3414 ics_getting_history = H_GOT_UNWANTED_HEADER;
3417 ics_getting_history = H_GOT_UNWANTED_HEADER;
3422 /* Is this the right one? */
3423 if (gameInfo.white && gameInfo.black &&
3424 strcmp(gameInfo.white, star_match[0]) == 0 &&
3425 strcmp(gameInfo.black, star_match[2]) == 0) {
3427 ics_getting_history = H_GOT_REQ_HEADER;
3430 case H_GOT_REQ_HEADER:
3431 case H_GOT_UNREQ_HEADER:
3432 case H_GOT_UNWANTED_HEADER:
3433 case H_GETTING_MOVES:
3434 /* Should not happen */
3435 DisplayError(_("Error gathering move list: two headers"), 0);
3436 ics_getting_history = H_FALSE;
3440 /* Save player ratings into gameInfo if needed */
3441 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3442 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3443 (gameInfo.whiteRating == -1 ||
3444 gameInfo.blackRating == -1)) {
3446 gameInfo.whiteRating = string_to_rating(star_match[1]);
3447 gameInfo.blackRating = string_to_rating(star_match[3]);
3448 if (appData.debugMode)
3449 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3450 gameInfo.whiteRating, gameInfo.blackRating);
3455 if (looking_at(buf, &i,
3456 "* * match, initial time: * minute*, increment: * second")) {
3457 /* Header for a move list -- second line */
3458 /* Initial board will follow if this is a wild game */
3459 if (gameInfo.event != NULL) free(gameInfo.event);
3460 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3461 gameInfo.event = StrSave(str);
3462 /* [HGM] we switched variant. Translate boards if needed. */
3463 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3467 if (looking_at(buf, &i, "Move ")) {
3468 /* Beginning of a move list */
3469 switch (ics_getting_history) {
3471 /* Normally should not happen */
3472 /* Maybe user hit reset while we were parsing */
3475 /* Happens if we are ignoring a move list that is not
3476 * the one we just requested. Common if the user
3477 * tries to observe two games without turning off
3480 case H_GETTING_MOVES:
3481 /* Should not happen */
3482 DisplayError(_("Error gathering move list: nested"), 0);
3483 ics_getting_history = H_FALSE;
3485 case H_GOT_REQ_HEADER:
3486 ics_getting_history = H_GETTING_MOVES;
3487 started = STARTED_MOVES;
3489 if (oldi > next_out) {
3490 SendToPlayer(&buf[next_out], oldi - next_out);
3493 case H_GOT_UNREQ_HEADER:
3494 ics_getting_history = H_GETTING_MOVES;
3495 started = STARTED_MOVES_NOHIDE;
3498 case H_GOT_UNWANTED_HEADER:
3499 ics_getting_history = H_FALSE;
3505 if (looking_at(buf, &i, "% ") ||
3506 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3507 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3508 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3509 soughtPending = FALSE;
3513 if(suppressKibitz) next_out = i;
3514 savingComment = FALSE;
3518 case STARTED_MOVES_NOHIDE:
3519 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3520 parse[parse_pos + i - oldi] = NULLCHAR;
3521 ParseGameHistory(parse);
3523 if (appData.zippyPlay && first.initDone) {
3524 FeedMovesToProgram(&first, forwardMostMove);
3525 if (gameMode == IcsPlayingWhite) {
3526 if (WhiteOnMove(forwardMostMove)) {
3527 if (first.sendTime) {
3528 if (first.useColors) {
3529 SendToProgram("black\n", &first);
3531 SendTimeRemaining(&first, TRUE);
3533 if (first.useColors) {
3534 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3536 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3537 first.maybeThinking = TRUE;
3539 if (first.usePlayother) {
3540 if (first.sendTime) {
3541 SendTimeRemaining(&first, TRUE);
3543 SendToProgram("playother\n", &first);
3549 } else if (gameMode == IcsPlayingBlack) {
3550 if (!WhiteOnMove(forwardMostMove)) {
3551 if (first.sendTime) {
3552 if (first.useColors) {
3553 SendToProgram("white\n", &first);
3555 SendTimeRemaining(&first, FALSE);
3557 if (first.useColors) {
3558 SendToProgram("black\n", &first);
3560 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3561 first.maybeThinking = TRUE;
3563 if (first.usePlayother) {
3564 if (first.sendTime) {
3565 SendTimeRemaining(&first, FALSE);
3567 SendToProgram("playother\n", &first);
3576 if (gameMode == IcsObserving && ics_gamenum == -1) {
3577 /* Moves came from oldmoves or moves command
3578 while we weren't doing anything else.
3580 currentMove = forwardMostMove;
3581 ClearHighlights();/*!!could figure this out*/
3582 flipView = appData.flipView;
3583 DrawPosition(TRUE, boards[currentMove]);
3584 DisplayBothClocks();
3585 snprintf(str, MSG_SIZ, "%s vs. %s",
3586 gameInfo.white, gameInfo.black);
3590 /* Moves were history of an active game */
3591 if (gameInfo.resultDetails != NULL) {
3592 free(gameInfo.resultDetails);
3593 gameInfo.resultDetails = NULL;
3596 HistorySet(parseList, backwardMostMove,
3597 forwardMostMove, currentMove-1);
3598 DisplayMove(currentMove - 1);
3599 if (started == STARTED_MOVES) next_out = i;
3600 started = STARTED_NONE;
3601 ics_getting_history = H_FALSE;
3604 case STARTED_OBSERVE:
3605 started = STARTED_NONE;
3606 SendToICS(ics_prefix);
3607 SendToICS("refresh\n");
3613 if(bookHit) { // [HGM] book: simulate book reply
3614 static char bookMove[MSG_SIZ]; // a bit generous?
3616 programStats.nodes = programStats.depth = programStats.time =
3617 programStats.score = programStats.got_only_move = 0;
3618 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3620 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3621 strcat(bookMove, bookHit);
3622 HandleMachineMove(bookMove, &first);
3627 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3628 started == STARTED_HOLDINGS ||
3629 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3630 /* Accumulate characters in move list or board */
3631 parse[parse_pos++] = buf[i];
3634 /* Start of game messages. Mostly we detect start of game
3635 when the first board image arrives. On some versions
3636 of the ICS, though, we need to do a "refresh" after starting
3637 to observe in order to get the current board right away. */
3638 if (looking_at(buf, &i, "Adding game * to observation list")) {
3639 started = STARTED_OBSERVE;
3643 /* Handle auto-observe */
3644 if (appData.autoObserve &&
3645 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3646 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3648 /* Choose the player that was highlighted, if any. */
3649 if (star_match[0][0] == '\033' ||
3650 star_match[1][0] != '\033') {
3651 player = star_match[0];
3653 player = star_match[2];
3655 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3656 ics_prefix, StripHighlightAndTitle(player));
3659 /* Save ratings from notify string */
3660 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3661 player1Rating = string_to_rating(star_match[1]);
3662 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3663 player2Rating = string_to_rating(star_match[3]);
3665 if (appData.debugMode)
3667 "Ratings from 'Game notification:' %s %d, %s %d\n",
3668 player1Name, player1Rating,
3669 player2Name, player2Rating);
3674 /* Deal with automatic examine mode after a game,
3675 and with IcsObserving -> IcsExamining transition */
3676 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3677 looking_at(buf, &i, "has made you an examiner of game *")) {
3679 int gamenum = atoi(star_match[0]);
3680 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3681 gamenum == ics_gamenum) {
3682 /* We were already playing or observing this game;
3683 no need to refetch history */
3684 gameMode = IcsExamining;
3686 pauseExamForwardMostMove = forwardMostMove;
3687 } else if (currentMove < forwardMostMove) {
3688 ForwardInner(forwardMostMove);
3691 /* I don't think this case really can happen */
3692 SendToICS(ics_prefix);
3693 SendToICS("refresh\n");
3698 /* Error messages */
3699 // if (ics_user_moved) {
3700 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3701 if (looking_at(buf, &i, "Illegal move") ||
3702 looking_at(buf, &i, "Not a legal move") ||
3703 looking_at(buf, &i, "Your king is in check") ||
3704 looking_at(buf, &i, "It isn't your turn") ||
3705 looking_at(buf, &i, "It is not your move")) {
3707 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3708 currentMove = forwardMostMove-1;
3709 DisplayMove(currentMove - 1); /* before DMError */
3710 DrawPosition(FALSE, boards[currentMove]);
3711 SwitchClocks(forwardMostMove-1); // [HGM] race
3712 DisplayBothClocks();
3714 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3720 if (looking_at(buf, &i, "still have time") ||
3721 looking_at(buf, &i, "not out of time") ||
3722 looking_at(buf, &i, "either player is out of time") ||
3723 looking_at(buf, &i, "has timeseal; checking")) {
3724 /* We must have called his flag a little too soon */
3725 whiteFlag = blackFlag = FALSE;
3729 if (looking_at(buf, &i, "added * seconds to") ||
3730 looking_at(buf, &i, "seconds were added to")) {
3731 /* Update the clocks */
3732 SendToICS(ics_prefix);
3733 SendToICS("refresh\n");
3737 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3738 ics_clock_paused = TRUE;
3743 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3744 ics_clock_paused = FALSE;
3749 /* Grab player ratings from the Creating: message.
3750 Note we have to check for the special case when
3751 the ICS inserts things like [white] or [black]. */
3752 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3753 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3755 0 player 1 name (not necessarily white)
3757 2 empty, white, or black (IGNORED)
3758 3 player 2 name (not necessarily black)
3761 The names/ratings are sorted out when the game
3762 actually starts (below).
3764 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3765 player1Rating = string_to_rating(star_match[1]);
3766 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3767 player2Rating = string_to_rating(star_match[4]);
3769 if (appData.debugMode)
3771 "Ratings from 'Creating:' %s %d, %s %d\n",
3772 player1Name, player1Rating,
3773 player2Name, player2Rating);
3778 /* Improved generic start/end-of-game messages */
3779 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3780 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3781 /* If tkind == 0: */
3782 /* star_match[0] is the game number */
3783 /* [1] is the white player's name */
3784 /* [2] is the black player's name */
3785 /* For end-of-game: */
3786 /* [3] is the reason for the game end */
3787 /* [4] is a PGN end game-token, preceded by " " */
3788 /* For start-of-game: */
3789 /* [3] begins with "Creating" or "Continuing" */
3790 /* [4] is " *" or empty (don't care). */
3791 int gamenum = atoi(star_match[0]);
3792 char *whitename, *blackname, *why, *endtoken;
3793 ChessMove endtype = EndOfFile;
3796 whitename = star_match[1];
3797 blackname = star_match[2];
3798 why = star_match[3];
3799 endtoken = star_match[4];
3801 whitename = star_match[1];
3802 blackname = star_match[3];
3803 why = star_match[5];
3804 endtoken = star_match[6];
3807 /* Game start messages */
3808 if (strncmp(why, "Creating ", 9) == 0 ||
3809 strncmp(why, "Continuing ", 11) == 0) {
3810 gs_gamenum = gamenum;
3811 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3812 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3814 if (appData.zippyPlay) {
3815 ZippyGameStart(whitename, blackname);
3818 partnerBoardValid = FALSE; // [HGM] bughouse
3822 /* Game end messages */
3823 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3824 ics_gamenum != gamenum) {
3827 while (endtoken[0] == ' ') endtoken++;
3828 switch (endtoken[0]) {
3831 endtype = GameUnfinished;
3834 endtype = BlackWins;
3837 if (endtoken[1] == '/')
3838 endtype = GameIsDrawn;
3840 endtype = WhiteWins;
3843 GameEnds(endtype, why, GE_ICS);
3845 if (appData.zippyPlay && first.initDone) {
3846 ZippyGameEnd(endtype, why);
3847 if (first.pr == NULL) {
3848 /* Start the next process early so that we'll
3849 be ready for the next challenge */
3850 StartChessProgram(&first);
3852 /* Send "new" early, in case this command takes
3853 a long time to finish, so that we'll be ready
3854 for the next challenge. */
3855 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3859 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3863 if (looking_at(buf, &i, "Removing game * from observation") ||
3864 looking_at(buf, &i, "no longer observing game *") ||
3865 looking_at(buf, &i, "Game * (*) has no examiners")) {
3866 if (gameMode == IcsObserving &&
3867 atoi(star_match[0]) == ics_gamenum)
3869 /* icsEngineAnalyze */
3870 if (appData.icsEngineAnalyze) {
3877 ics_user_moved = FALSE;
3882 if (looking_at(buf, &i, "no longer examining game *")) {
3883 if (gameMode == IcsExamining &&
3884 atoi(star_match[0]) == ics_gamenum)
3888 ics_user_moved = FALSE;
3893 /* Advance leftover_start past any newlines we find,
3894 so only partial lines can get reparsed */
3895 if (looking_at(buf, &i, "\n")) {
3896 prevColor = curColor;
3897 if (curColor != ColorNormal) {
3898 if (oldi > next_out) {
3899 SendToPlayer(&buf[next_out], oldi - next_out);
3902 Colorize(ColorNormal, FALSE);
3903 curColor = ColorNormal;
3905 if (started == STARTED_BOARD) {
3906 started = STARTED_NONE;
3907 parse[parse_pos] = NULLCHAR;
3908 ParseBoard12(parse);
3911 /* Send premove here */
3912 if (appData.premove) {
3914 if (currentMove == 0 &&
3915 gameMode == IcsPlayingWhite &&
3916 appData.premoveWhite) {
3917 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3918 if (appData.debugMode)
3919 fprintf(debugFP, "Sending premove:\n");
3921 } else if (currentMove == 1 &&
3922 gameMode == IcsPlayingBlack &&
3923 appData.premoveBlack) {
3924 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3925 if (appData.debugMode)
3926 fprintf(debugFP, "Sending premove:\n");
3928 } else if (gotPremove) {
3930 ClearPremoveHighlights();
3931 if (appData.debugMode)
3932 fprintf(debugFP, "Sending premove:\n");
3933 UserMoveEvent(premoveFromX, premoveFromY,
3934 premoveToX, premoveToY,
3939 /* Usually suppress following prompt */
3940 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3941 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3942 if (looking_at(buf, &i, "*% ")) {
3943 savingComment = FALSE;
3948 } else if (started == STARTED_HOLDINGS) {
3950 char new_piece[MSG_SIZ];
3951 started = STARTED_NONE;
3952 parse[parse_pos] = NULLCHAR;
3953 if (appData.debugMode)
3954 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3955 parse, currentMove);
3956 if (sscanf(parse, " game %d", &gamenum) == 1) {
3957 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3958 if (gameInfo.variant == VariantNormal) {
3959 /* [HGM] We seem to switch variant during a game!
3960 * Presumably no holdings were displayed, so we have
3961 * to move the position two files to the right to
3962 * create room for them!
3964 VariantClass newVariant;
3965 switch(gameInfo.boardWidth) { // base guess on board width
3966 case 9: newVariant = VariantShogi; break;
3967 case 10: newVariant = VariantGreat; break;
3968 default: newVariant = VariantCrazyhouse; break;
3970 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3971 /* Get a move list just to see the header, which
3972 will tell us whether this is really bug or zh */
3973 if (ics_getting_history == H_FALSE) {
3974 ics_getting_history = H_REQUESTED;
3975 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3979 new_piece[0] = NULLCHAR;
3980 sscanf(parse, "game %d white [%s black [%s <- %s",
3981 &gamenum, white_holding, black_holding,
3983 white_holding[strlen(white_holding)-1] = NULLCHAR;
3984 black_holding[strlen(black_holding)-1] = NULLCHAR;
3985 /* [HGM] copy holdings to board holdings area */
3986 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3987 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3988 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3990 if (appData.zippyPlay && first.initDone) {
3991 ZippyHoldings(white_holding, black_holding,
3995 if (tinyLayout || smallLayout) {
3996 char wh[16], bh[16];
3997 PackHolding(wh, white_holding);
3998 PackHolding(bh, black_holding);
3999 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4000 gameInfo.white, gameInfo.black);
4002 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4003 gameInfo.white, white_holding,
4004 gameInfo.black, black_holding);
4006 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4007 DrawPosition(FALSE, boards[currentMove]);
4009 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4010 sscanf(parse, "game %d white [%s black [%s <- %s",
4011 &gamenum, white_holding, black_holding,
4013 white_holding[strlen(white_holding)-1] = NULLCHAR;
4014 black_holding[strlen(black_holding)-1] = NULLCHAR;
4015 /* [HGM] copy holdings to partner-board holdings area */
4016 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4017 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4018 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4019 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4020 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4023 /* Suppress following prompt */
4024 if (looking_at(buf, &i, "*% ")) {
4025 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4026 savingComment = FALSE;
4034 i++; /* skip unparsed character and loop back */
4037 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4038 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4039 // SendToPlayer(&buf[next_out], i - next_out);
4040 started != STARTED_HOLDINGS && leftover_start > next_out) {
4041 SendToPlayer(&buf[next_out], leftover_start - next_out);
4045 leftover_len = buf_len - leftover_start;
4046 /* if buffer ends with something we couldn't parse,
4047 reparse it after appending the next read */
4049 } else if (count == 0) {
4050 RemoveInputSource(isr);
4051 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4053 DisplayFatalError(_("Error reading from ICS"), error, 1);
4058 /* Board style 12 looks like this:
4060 <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
4062 * The "<12> " is stripped before it gets to this routine. The two
4063 * trailing 0's (flip state and clock ticking) are later addition, and
4064 * some chess servers may not have them, or may have only the first.
4065 * Additional trailing fields may be added in the future.
4068 #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"
4070 #define RELATION_OBSERVING_PLAYED 0
4071 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4072 #define RELATION_PLAYING_MYMOVE 1
4073 #define RELATION_PLAYING_NOTMYMOVE -1
4074 #define RELATION_EXAMINING 2
4075 #define RELATION_ISOLATED_BOARD -3
4076 #define RELATION_STARTING_POSITION -4 /* FICS only */
4079 ParseBoard12(string)
4082 GameMode newGameMode;
4083 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4084 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4085 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4086 char to_play, board_chars[200];
4087 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4088 char black[32], white[32];
4090 int prevMove = currentMove;
4093 int fromX, fromY, toX, toY;
4095 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4096 char *bookHit = NULL; // [HGM] book
4097 Boolean weird = FALSE, reqFlag = FALSE;
4099 fromX = fromY = toX = toY = -1;
4103 if (appData.debugMode)
4104 fprintf(debugFP, _("Parsing board: %s\n"), string);
4106 move_str[0] = NULLCHAR;
4107 elapsed_time[0] = NULLCHAR;
4108 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4110 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4111 if(string[i] == ' ') { ranks++; files = 0; }
4113 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4116 for(j = 0; j <i; j++) board_chars[j] = string[j];
4117 board_chars[i] = '\0';
4120 n = sscanf(string, PATTERN, &to_play, &double_push,
4121 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4122 &gamenum, white, black, &relation, &basetime, &increment,
4123 &white_stren, &black_stren, &white_time, &black_time,
4124 &moveNum, str, elapsed_time, move_str, &ics_flip,
4128 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4129 DisplayError(str, 0);
4133 /* Convert the move number to internal form */
4134 moveNum = (moveNum - 1) * 2;
4135 if (to_play == 'B') moveNum++;
4136 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4137 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4143 case RELATION_OBSERVING_PLAYED:
4144 case RELATION_OBSERVING_STATIC:
4145 if (gamenum == -1) {
4146 /* Old ICC buglet */
4147 relation = RELATION_OBSERVING_STATIC;
4149 newGameMode = IcsObserving;
4151 case RELATION_PLAYING_MYMOVE:
4152 case RELATION_PLAYING_NOTMYMOVE:
4154 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4155 IcsPlayingWhite : IcsPlayingBlack;
4157 case RELATION_EXAMINING:
4158 newGameMode = IcsExamining;
4160 case RELATION_ISOLATED_BOARD:
4162 /* Just display this board. If user was doing something else,
4163 we will forget about it until the next board comes. */
4164 newGameMode = IcsIdle;
4166 case RELATION_STARTING_POSITION:
4167 newGameMode = gameMode;
4171 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4172 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4173 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4175 for (k = 0; k < ranks; k++) {
4176 for (j = 0; j < files; j++)
4177 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4178 if(gameInfo.holdingsWidth > 1) {
4179 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4180 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4183 CopyBoard(partnerBoard, board);
4184 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4185 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4186 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4187 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4188 if(toSqr = strchr(str, '-')) {
4189 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4190 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4191 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4192 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4193 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4194 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4195 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4196 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4197 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4198 DisplayMessage(partnerStatus, "");
4199 partnerBoardValid = TRUE;
4203 /* Modify behavior for initial board display on move listing
4206 switch (ics_getting_history) {
4210 case H_GOT_REQ_HEADER:
4211 case H_GOT_UNREQ_HEADER:
4212 /* This is the initial position of the current game */
4213 gamenum = ics_gamenum;
4214 moveNum = 0; /* old ICS bug workaround */
4215 if (to_play == 'B') {
4216 startedFromSetupPosition = TRUE;
4217 blackPlaysFirst = TRUE;
4219 if (forwardMostMove == 0) forwardMostMove = 1;
4220 if (backwardMostMove == 0) backwardMostMove = 1;
4221 if (currentMove == 0) currentMove = 1;
4223 newGameMode = gameMode;
4224 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4226 case H_GOT_UNWANTED_HEADER:
4227 /* This is an initial board that we don't want */
4229 case H_GETTING_MOVES:
4230 /* Should not happen */
4231 DisplayError(_("Error gathering move list: extra board"), 0);
4232 ics_getting_history = H_FALSE;
4236 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4237 weird && (int)gameInfo.variant < (int)VariantShogi) {
4238 /* [HGM] We seem to have switched variant unexpectedly
4239 * Try to guess new variant from board size
4241 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4242 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4243 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4244 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4245 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4246 if(!weird) newVariant = VariantNormal;
4247 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4248 /* Get a move list just to see the header, which
4249 will tell us whether this is really bug or zh */
4250 if (ics_getting_history == H_FALSE) {
4251 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4252 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4257 /* Take action if this is the first board of a new game, or of a
4258 different game than is currently being displayed. */
4259 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4260 relation == RELATION_ISOLATED_BOARD) {
4262 /* Forget the old game and get the history (if any) of the new one */
4263 if (gameMode != BeginningOfGame) {
4267 if (appData.autoRaiseBoard) BoardToTop();
4269 if (gamenum == -1) {
4270 newGameMode = IcsIdle;
4271 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4272 appData.getMoveList && !reqFlag) {
4273 /* Need to get game history */
4274 ics_getting_history = H_REQUESTED;
4275 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4279 /* Initially flip the board to have black on the bottom if playing
4280 black or if the ICS flip flag is set, but let the user change
4281 it with the Flip View button. */
4282 flipView = appData.autoFlipView ?
4283 (newGameMode == IcsPlayingBlack) || ics_flip :
4286 /* Done with values from previous mode; copy in new ones */
4287 gameMode = newGameMode;
4289 ics_gamenum = gamenum;
4290 if (gamenum == gs_gamenum) {
4291 int klen = strlen(gs_kind);
4292 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4293 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4294 gameInfo.event = StrSave(str);
4296 gameInfo.event = StrSave("ICS game");
4298 gameInfo.site = StrSave(appData.icsHost);
4299 gameInfo.date = PGNDate();
4300 gameInfo.round = StrSave("-");
4301 gameInfo.white = StrSave(white);
4302 gameInfo.black = StrSave(black);
4303 timeControl = basetime * 60 * 1000;
4305 timeIncrement = increment * 1000;
4306 movesPerSession = 0;
4307 gameInfo.timeControl = TimeControlTagValue();
4308 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4309 if (appData.debugMode) {
4310 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4311 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4312 setbuf(debugFP, NULL);
4315 gameInfo.outOfBook = NULL;
4317 /* Do we have the ratings? */
4318 if (strcmp(player1Name, white) == 0 &&
4319 strcmp(player2Name, black) == 0) {
4320 if (appData.debugMode)
4321 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4322 player1Rating, player2Rating);
4323 gameInfo.whiteRating = player1Rating;
4324 gameInfo.blackRating = player2Rating;
4325 } else if (strcmp(player2Name, white) == 0 &&
4326 strcmp(player1Name, black) == 0) {
4327 if (appData.debugMode)
4328 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4329 player2Rating, player1Rating);
4330 gameInfo.whiteRating = player2Rating;
4331 gameInfo.blackRating = player1Rating;
4333 player1Name[0] = player2Name[0] = NULLCHAR;
4335 /* Silence shouts if requested */
4336 if (appData.quietPlay &&
4337 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4338 SendToICS(ics_prefix);
4339 SendToICS("set shout 0\n");
4343 /* Deal with midgame name changes */
4345 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4346 if (gameInfo.white) free(gameInfo.white);
4347 gameInfo.white = StrSave(white);
4349 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4350 if (gameInfo.black) free(gameInfo.black);
4351 gameInfo.black = StrSave(black);
4355 /* Throw away game result if anything actually changes in examine mode */
4356 if (gameMode == IcsExamining && !newGame) {
4357 gameInfo.result = GameUnfinished;
4358 if (gameInfo.resultDetails != NULL) {
4359 free(gameInfo.resultDetails);
4360 gameInfo.resultDetails = NULL;
4364 /* In pausing && IcsExamining mode, we ignore boards coming
4365 in if they are in a different variation than we are. */
4366 if (pauseExamInvalid) return;
4367 if (pausing && gameMode == IcsExamining) {
4368 if (moveNum <= pauseExamForwardMostMove) {
4369 pauseExamInvalid = TRUE;
4370 forwardMostMove = pauseExamForwardMostMove;
4375 if (appData.debugMode) {
4376 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4378 /* Parse the board */
4379 for (k = 0; k < ranks; k++) {
4380 for (j = 0; j < files; j++)
4381 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4382 if(gameInfo.holdingsWidth > 1) {
4383 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4384 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4387 CopyBoard(boards[moveNum], board);
4388 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4390 startedFromSetupPosition =
4391 !CompareBoards(board, initialPosition);
4392 if(startedFromSetupPosition)
4393 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4396 /* [HGM] Set castling rights. Take the outermost Rooks,
4397 to make it also work for FRC opening positions. Note that board12
4398 is really defective for later FRC positions, as it has no way to
4399 indicate which Rook can castle if they are on the same side of King.
4400 For the initial position we grant rights to the outermost Rooks,
4401 and remember thos rights, and we then copy them on positions
4402 later in an FRC game. This means WB might not recognize castlings with
4403 Rooks that have moved back to their original position as illegal,
4404 but in ICS mode that is not its job anyway.
4406 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4407 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4409 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4410 if(board[0][i] == WhiteRook) j = i;
4411 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4413 if(board[0][i] == WhiteRook) j = i;
4414 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4415 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4416 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4417 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4419 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4420 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4422 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4423 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4424 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4425 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4426 if(board[BOARD_HEIGHT-1][k] == bKing)
4427 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4428 if(gameInfo.variant == VariantTwoKings) {
4429 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4430 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4431 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4434 r = boards[moveNum][CASTLING][0] = initialRights[0];
4435 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4436 r = boards[moveNum][CASTLING][1] = initialRights[1];
4437 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4438 r = boards[moveNum][CASTLING][3] = initialRights[3];
4439 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4440 r = boards[moveNum][CASTLING][4] = initialRights[4];
4441 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4442 /* wildcastle kludge: always assume King has rights */
4443 r = boards[moveNum][CASTLING][2] = initialRights[2];
4444 r = boards[moveNum][CASTLING][5] = initialRights[5];
4446 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4447 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4450 if (ics_getting_history == H_GOT_REQ_HEADER ||
4451 ics_getting_history == H_GOT_UNREQ_HEADER) {
4452 /* This was an initial position from a move list, not
4453 the current position */
4457 /* Update currentMove and known move number limits */
4458 newMove = newGame || moveNum > forwardMostMove;
4461 forwardMostMove = backwardMostMove = currentMove = moveNum;
4462 if (gameMode == IcsExamining && moveNum == 0) {
4463 /* Workaround for ICS limitation: we are not told the wild
4464 type when starting to examine a game. But if we ask for
4465 the move list, the move list header will tell us */
4466 ics_getting_history = H_REQUESTED;
4467 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4470 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4471 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4473 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4474 /* [HGM] applied this also to an engine that is silently watching */
4475 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4476 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4477 gameInfo.variant == currentlyInitializedVariant) {
4478 takeback = forwardMostMove - moveNum;
4479 for (i = 0; i < takeback; i++) {
4480 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4481 SendToProgram("undo\n", &first);
4486 forwardMostMove = moveNum;
4487 if (!pausing || currentMove > forwardMostMove)
4488 currentMove = forwardMostMove;
4490 /* New part of history that is not contiguous with old part */
4491 if (pausing && gameMode == IcsExamining) {
4492 pauseExamInvalid = TRUE;
4493 forwardMostMove = pauseExamForwardMostMove;
4496 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4498 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4499 // [HGM] when we will receive the move list we now request, it will be
4500 // fed to the engine from the first move on. So if the engine is not
4501 // in the initial position now, bring it there.
4502 InitChessProgram(&first, 0);
4505 ics_getting_history = H_REQUESTED;
4506 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4509 forwardMostMove = backwardMostMove = currentMove = moveNum;
4512 /* Update the clocks */
4513 if (strchr(elapsed_time, '.')) {
4515 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4516 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4518 /* Time is in seconds */
4519 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4520 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4525 if (appData.zippyPlay && newGame &&
4526 gameMode != IcsObserving && gameMode != IcsIdle &&
4527 gameMode != IcsExamining)
4528 ZippyFirstBoard(moveNum, basetime, increment);
4531 /* Put the move on the move list, first converting
4532 to canonical algebraic form. */
4534 if (appData.debugMode) {
4535 if (appData.debugMode) { int f = forwardMostMove;
4536 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4537 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4538 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4540 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4541 fprintf(debugFP, "moveNum = %d\n", moveNum);
4542 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4543 setbuf(debugFP, NULL);
4545 if (moveNum <= backwardMostMove) {
4546 /* We don't know what the board looked like before
4548 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4549 strcat(parseList[moveNum - 1], " ");
4550 strcat(parseList[moveNum - 1], elapsed_time);
4551 moveList[moveNum - 1][0] = NULLCHAR;
4552 } else if (strcmp(move_str, "none") == 0) {
4553 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4554 /* Again, we don't know what the board looked like;
4555 this is really the start of the game. */
4556 parseList[moveNum - 1][0] = NULLCHAR;
4557 moveList[moveNum - 1][0] = NULLCHAR;
4558 backwardMostMove = moveNum;
4559 startedFromSetupPosition = TRUE;
4560 fromX = fromY = toX = toY = -1;
4562 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4563 // So we parse the long-algebraic move string in stead of the SAN move
4564 int valid; char buf[MSG_SIZ], *prom;
4566 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4567 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4568 // str looks something like "Q/a1-a2"; kill the slash
4570 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4571 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4572 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4573 strcat(buf, prom); // long move lacks promo specification!
4574 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4575 if(appData.debugMode)
4576 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4577 safeStrCpy(move_str, buf, MSG_SIZ);
4579 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4580 &fromX, &fromY, &toX, &toY, &promoChar)
4581 || ParseOneMove(buf, moveNum - 1, &moveType,
4582 &fromX, &fromY, &toX, &toY, &promoChar);
4583 // end of long SAN patch
4585 (void) CoordsToAlgebraic(boards[moveNum - 1],
4586 PosFlags(moveNum - 1),
4587 fromY, fromX, toY, toX, promoChar,
4588 parseList[moveNum-1]);
4589 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4595 if(gameInfo.variant != VariantShogi)
4596 strcat(parseList[moveNum - 1], "+");
4599 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4600 strcat(parseList[moveNum - 1], "#");
4603 strcat(parseList[moveNum - 1], " ");
4604 strcat(parseList[moveNum - 1], elapsed_time);
4605 /* currentMoveString is set as a side-effect of ParseOneMove */
4606 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4607 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4608 strcat(moveList[moveNum - 1], "\n");
4610 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4611 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4612 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4613 ChessSquare old, new = boards[moveNum][k][j];
4614 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4615 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4616 if(old == new) continue;
4617 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4618 else if(new == WhiteWazir || new == BlackWazir) {
4619 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4620 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4621 else boards[moveNum][k][j] = old; // preserve type of Gold
4622 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4623 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4626 /* Move from ICS was illegal!? Punt. */
4627 if (appData.debugMode) {
4628 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4629 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4631 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4632 strcat(parseList[moveNum - 1], " ");
4633 strcat(parseList[moveNum - 1], elapsed_time);
4634 moveList[moveNum - 1][0] = NULLCHAR;
4635 fromX = fromY = toX = toY = -1;
4638 if (appData.debugMode) {
4639 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4640 setbuf(debugFP, NULL);
4644 /* Send move to chess program (BEFORE animating it). */
4645 if (appData.zippyPlay && !newGame && newMove &&
4646 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4648 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4649 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4650 if (moveList[moveNum - 1][0] == NULLCHAR) {
4651 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4653 DisplayError(str, 0);
4655 if (first.sendTime) {
4656 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4658 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4659 if (firstMove && !bookHit) {
4661 if (first.useColors) {
4662 SendToProgram(gameMode == IcsPlayingWhite ?
4664 "black\ngo\n", &first);
4666 SendToProgram("go\n", &first);
4668 first.maybeThinking = TRUE;
4671 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4672 if (moveList[moveNum - 1][0] == NULLCHAR) {
4673 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4674 DisplayError(str, 0);
4676 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4677 SendMoveToProgram(moveNum - 1, &first);
4684 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4685 /* If move comes from a remote source, animate it. If it
4686 isn't remote, it will have already been animated. */
4687 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4688 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4690 if (!pausing && appData.highlightLastMove) {
4691 SetHighlights(fromX, fromY, toX, toY);
4695 /* Start the clocks */
4696 whiteFlag = blackFlag = FALSE;
4697 appData.clockMode = !(basetime == 0 && increment == 0);
4699 ics_clock_paused = TRUE;
4701 } else if (ticking == 1) {
4702 ics_clock_paused = FALSE;
4704 if (gameMode == IcsIdle ||
4705 relation == RELATION_OBSERVING_STATIC ||
4706 relation == RELATION_EXAMINING ||
4708 DisplayBothClocks();
4712 /* Display opponents and material strengths */
4713 if (gameInfo.variant != VariantBughouse &&
4714 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4715 if (tinyLayout || smallLayout) {
4716 if(gameInfo.variant == VariantNormal)
4717 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4718 gameInfo.white, white_stren, gameInfo.black, black_stren,
4719 basetime, increment);
4721 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4722 gameInfo.white, white_stren, gameInfo.black, black_stren,
4723 basetime, increment, (int) gameInfo.variant);
4725 if(gameInfo.variant == VariantNormal)
4726 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4727 gameInfo.white, white_stren, gameInfo.black, black_stren,
4728 basetime, increment);
4730 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4731 gameInfo.white, white_stren, gameInfo.black, black_stren,
4732 basetime, increment, VariantName(gameInfo.variant));
4735 if (appData.debugMode) {
4736 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4741 /* Display the board */
4742 if (!pausing && !appData.noGUI) {
4744 if (appData.premove)
4746 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4747 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4748 ClearPremoveHighlights();
4750 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4751 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4752 DrawPosition(j, boards[currentMove]);
4754 DisplayMove(moveNum - 1);
4755 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4756 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4757 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4758 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4762 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4764 if(bookHit) { // [HGM] book: simulate book reply
4765 static char bookMove[MSG_SIZ]; // a bit generous?
4767 programStats.nodes = programStats.depth = programStats.time =
4768 programStats.score = programStats.got_only_move = 0;
4769 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4771 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4772 strcat(bookMove, bookHit);
4773 HandleMachineMove(bookMove, &first);
4782 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4783 ics_getting_history = H_REQUESTED;
4784 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4790 AnalysisPeriodicEvent(force)
4793 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4794 && !force) || !appData.periodicUpdates)
4797 /* Send . command to Crafty to collect stats */
4798 SendToProgram(".\n", &first);
4800 /* Don't send another until we get a response (this makes
4801 us stop sending to old Crafty's which don't understand
4802 the "." command (sending illegal cmds resets node count & time,
4803 which looks bad)) */
4804 programStats.ok_to_send = 0;
4807 void ics_update_width(new_width)
4810 ics_printf("set width %d\n", new_width);
4814 SendMoveToProgram(moveNum, cps)
4816 ChessProgramState *cps;
4820 if (cps->useUsermove) {
4821 SendToProgram("usermove ", cps);
4825 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4826 int len = space - parseList[moveNum];
4827 memcpy(buf, parseList[moveNum], len);
4829 buf[len] = NULLCHAR;
4831 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4833 SendToProgram(buf, cps);
4835 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4836 AlphaRank(moveList[moveNum], 4);
4837 SendToProgram(moveList[moveNum], cps);
4838 AlphaRank(moveList[moveNum], 4); // and back
4840 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4841 * the engine. It would be nice to have a better way to identify castle
4843 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4844 && cps->useOOCastle) {
4845 int fromX = moveList[moveNum][0] - AAA;
4846 int fromY = moveList[moveNum][1] - ONE;
4847 int toX = moveList[moveNum][2] - AAA;
4848 int toY = moveList[moveNum][3] - ONE;
4849 if((boards[moveNum][fromY][fromX] == WhiteKing
4850 && boards[moveNum][toY][toX] == WhiteRook)
4851 || (boards[moveNum][fromY][fromX] == BlackKing
4852 && boards[moveNum][toY][toX] == BlackRook)) {
4853 if(toX > fromX) SendToProgram("O-O\n", cps);
4854 else SendToProgram("O-O-O\n", cps);
4856 else SendToProgram(moveList[moveNum], cps);
4858 else SendToProgram(moveList[moveNum], cps);
4859 /* End of additions by Tord */
4862 /* [HGM] setting up the opening has brought engine in force mode! */
4863 /* Send 'go' if we are in a mode where machine should play. */
4864 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4865 (gameMode == TwoMachinesPlay ||
4867 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4869 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4870 SendToProgram("go\n", cps);
4871 if (appData.debugMode) {
4872 fprintf(debugFP, "(extra)\n");
4875 setboardSpoiledMachineBlack = 0;
4879 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4881 int fromX, fromY, toX, toY;
4884 char user_move[MSG_SIZ];
4888 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4889 (int)moveType, fromX, fromY, toX, toY);
4890 DisplayError(user_move + strlen("say "), 0);
4892 case WhiteKingSideCastle:
4893 case BlackKingSideCastle:
4894 case WhiteQueenSideCastleWild:
4895 case BlackQueenSideCastleWild:
4897 case WhiteHSideCastleFR:
4898 case BlackHSideCastleFR:
4900 snprintf(user_move, MSG_SIZ, "o-o\n");
4902 case WhiteQueenSideCastle:
4903 case BlackQueenSideCastle:
4904 case WhiteKingSideCastleWild:
4905 case BlackKingSideCastleWild:
4907 case WhiteASideCastleFR:
4908 case BlackASideCastleFR:
4910 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4912 case WhiteNonPromotion:
4913 case BlackNonPromotion:
4914 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4916 case WhitePromotion:
4917 case BlackPromotion:
4918 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4919 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4920 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921 PieceToChar(WhiteFerz));
4922 else if(gameInfo.variant == VariantGreat)
4923 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4924 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925 PieceToChar(WhiteMan));
4927 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4928 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4934 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4935 ToUpper(PieceToChar((ChessSquare) fromX)),
4936 AAA + toX, ONE + toY);
4938 case IllegalMove: /* could be a variant we don't quite understand */
4939 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4941 case WhiteCapturesEnPassant:
4942 case BlackCapturesEnPassant:
4943 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4944 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4947 SendToICS(user_move);
4948 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4949 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4954 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4955 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4956 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4957 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4958 DisplayError("You cannot do this while you are playing or observing", 0);
4961 if(gameMode != IcsExamining) { // is this ever not the case?
4962 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4964 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4965 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4966 } else { // on FICS we must first go to general examine mode
4967 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4969 if(gameInfo.variant != VariantNormal) {
4970 // try figure out wild number, as xboard names are not always valid on ICS
4971 for(i=1; i<=36; i++) {
4972 snprintf(buf, MSG_SIZ, "wild/%d", i);
4973 if(StringToVariant(buf) == gameInfo.variant) break;
4975 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4976 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4977 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4978 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4979 SendToICS(ics_prefix);
4981 if(startedFromSetupPosition || backwardMostMove != 0) {
4982 fen = PositionToFEN(backwardMostMove, NULL);
4983 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4984 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4986 } else { // FICS: everything has to set by separate bsetup commands
4987 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4988 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4990 if(!WhiteOnMove(backwardMostMove)) {
4991 SendToICS("bsetup tomove black\n");
4993 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4994 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4996 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4997 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4999 i = boards[backwardMostMove][EP_STATUS];
5000 if(i >= 0) { // set e.p.
5001 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5007 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5008 SendToICS("bsetup done\n"); // switch to normal examining.
5010 for(i = backwardMostMove; i<last; i++) {
5012 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5015 SendToICS(ics_prefix);
5016 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5020 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5025 if (rf == DROP_RANK) {
5026 sprintf(move, "%c@%c%c\n",
5027 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5029 if (promoChar == 'x' || promoChar == NULLCHAR) {
5030 sprintf(move, "%c%c%c%c\n",
5031 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5033 sprintf(move, "%c%c%c%c%c\n",
5034 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5040 ProcessICSInitScript(f)
5045 while (fgets(buf, MSG_SIZ, f)) {
5046 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5053 static int lastX, lastY, selectFlag, dragging;
5058 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5059 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5060 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5061 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5062 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5063 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5066 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5067 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5068 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5069 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5071 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5072 appData.testLegality && (promoSweep == king ||
5073 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5074 ChangeDragPiece(promoSweep);
5077 int PromoScroll(int x, int y)
5081 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5082 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5083 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5084 if(!step) return FALSE;
5085 lastX = x; lastY = y;
5086 if((promoSweep < BlackPawn) == flipView) step = -step;
5087 if(step > 0) selectFlag = 1;
5088 if(!selectFlag) Sweep(step);
5095 ChessSquare piece = boards[currentMove][toY][toX];
5098 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5099 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5100 if(!step) step = -1;
5101 } while(PieceToChar(pieceSweep) == '.');
5102 boards[currentMove][toY][toX] = pieceSweep;
5103 DrawPosition(FALSE, boards[currentMove]);
5104 boards[currentMove][toY][toX] = piece;
5106 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5108 AlphaRank(char *move, int n)
5110 // char *p = move, c; int x, y;
5112 if (appData.debugMode) {
5113 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5117 move[2]>='0' && move[2]<='9' &&
5118 move[3]>='a' && move[3]<='x' ) {
5120 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5121 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5123 if(move[0]>='0' && move[0]<='9' &&
5124 move[1]>='a' && move[1]<='x' &&
5125 move[2]>='0' && move[2]<='9' &&
5126 move[3]>='a' && move[3]<='x' ) {
5127 /* input move, Shogi -> normal */
5128 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5129 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5130 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5131 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5134 move[3]>='0' && move[3]<='9' &&
5135 move[2]>='a' && move[2]<='x' ) {
5137 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5138 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5141 move[0]>='a' && move[0]<='x' &&
5142 move[3]>='0' && move[3]<='9' &&
5143 move[2]>='a' && move[2]<='x' ) {
5144 /* output move, normal -> Shogi */
5145 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5146 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5147 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5148 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5149 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5151 if (appData.debugMode) {
5152 fprintf(debugFP, " out = '%s'\n", move);
5156 char yy_textstr[8000];
5158 /* Parser for moves from gnuchess, ICS, or user typein box */
5160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5163 ChessMove *moveType;
5164 int *fromX, *fromY, *toX, *toY;
5167 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5169 switch (*moveType) {
5170 case WhitePromotion:
5171 case BlackPromotion:
5172 case WhiteNonPromotion:
5173 case BlackNonPromotion:
5175 case WhiteCapturesEnPassant:
5176 case BlackCapturesEnPassant:
5177 case WhiteKingSideCastle:
5178 case WhiteQueenSideCastle:
5179 case BlackKingSideCastle:
5180 case BlackQueenSideCastle:
5181 case WhiteKingSideCastleWild:
5182 case WhiteQueenSideCastleWild:
5183 case BlackKingSideCastleWild:
5184 case BlackQueenSideCastleWild:
5185 /* Code added by Tord: */
5186 case WhiteHSideCastleFR:
5187 case WhiteASideCastleFR:
5188 case BlackHSideCastleFR:
5189 case BlackASideCastleFR:
5190 /* End of code added by Tord */
5191 case IllegalMove: /* bug or odd chess variant */
5192 *fromX = currentMoveString[0] - AAA;
5193 *fromY = currentMoveString[1] - ONE;
5194 *toX = currentMoveString[2] - AAA;
5195 *toY = currentMoveString[3] - ONE;
5196 *promoChar = currentMoveString[4];
5197 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5198 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5199 if (appData.debugMode) {
5200 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5202 *fromX = *fromY = *toX = *toY = 0;
5205 if (appData.testLegality) {
5206 return (*moveType != IllegalMove);
5208 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5209 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5214 *fromX = *moveType == WhiteDrop ?
5215 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5216 (int) CharToPiece(ToLower(currentMoveString[0]));
5218 *toX = currentMoveString[2] - AAA;
5219 *toY = currentMoveString[3] - ONE;
5220 *promoChar = NULLCHAR;
5224 case ImpossibleMove:
5234 if (appData.debugMode) {
5235 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5238 *fromX = *fromY = *toX = *toY = 0;
5239 *promoChar = NULLCHAR;
5244 Boolean pushed = FALSE;
5247 ParsePV(char *pv, Boolean storeComments)
5248 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5249 int fromX, fromY, toX, toY; char promoChar;
5254 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5255 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5258 endPV = forwardMostMove;
5260 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5261 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5262 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5263 if(appData.debugMode){
5264 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);
5266 if(!valid && nr == 0 &&
5267 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5268 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5269 // Hande case where played move is different from leading PV move
5270 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5271 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5272 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5273 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5274 endPV += 2; // if position different, keep this
5275 moveList[endPV-1][0] = fromX + AAA;
5276 moveList[endPV-1][1] = fromY + ONE;
5277 moveList[endPV-1][2] = toX + AAA;
5278 moveList[endPV-1][3] = toY + ONE;
5279 parseList[endPV-1][0] = NULLCHAR;
5280 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5283 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5284 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5285 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5286 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5287 valid++; // allow comments in PV
5291 if(endPV+1 > framePtr) break; // no space, truncate
5294 CopyBoard(boards[endPV], boards[endPV-1]);
5295 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5296 moveList[endPV-1][0] = fromX + AAA;
5297 moveList[endPV-1][1] = fromY + ONE;
5298 moveList[endPV-1][2] = toX + AAA;
5299 moveList[endPV-1][3] = toY + ONE;
5300 moveList[endPV-1][4] = promoChar;
5301 moveList[endPV-1][5] = NULLCHAR;
5302 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5304 CoordsToAlgebraic(boards[endPV - 1],
5305 PosFlags(endPV - 1),
5306 fromY, fromX, toY, toX, promoChar,
5307 parseList[endPV - 1]);
5309 parseList[endPV-1][0] = NULLCHAR;
5311 currentMove = endPV;
5312 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5313 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5314 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5315 DrawPosition(TRUE, boards[currentMove]);
5319 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5324 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5325 lastX = x; lastY = y;
5326 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5328 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5329 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5331 do{ while(buf[index] && buf[index] != '\n') index++;
5332 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5334 ParsePV(buf+startPV, FALSE);
5335 *start = startPV; *end = index-1;
5340 LoadPV(int x, int y)
5341 { // called on right mouse click to load PV
5342 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5343 lastX = x; lastY = y;
5344 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5351 if(endPV < 0) return;
5353 currentMove = forwardMostMove;
5354 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5355 ClearPremoveHighlights();
5356 DrawPosition(TRUE, boards[currentMove]);
5360 MovePV(int x, int y, int h)
5361 { // step through PV based on mouse coordinates (called on mouse move)
5362 int margin = h>>3, step = 0;
5364 // we must somehow check if right button is still down (might be released off board!)
5365 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5366 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5367 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5369 lastX = x; lastY = y;
5371 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5372 if(endPV < 0) return;
5373 if(y < margin) step = 1; else
5374 if(y > h - margin) step = -1;
5375 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5376 currentMove += step;
5377 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380 DrawPosition(FALSE, boards[currentMove]);
5384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5385 // All positions will have equal probability, but the current method will not provide a unique
5386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5392 int piecesLeft[(int)BlackPawn];
5393 int seed, nrOfShuffles;
5395 void GetPositionNumber()
5396 { // sets global variable seed
5399 seed = appData.defaultFrcPosition;
5400 if(seed < 0) { // randomize based on time for negative FRC position numbers
5401 for(i=0; i<50; i++) seed += random();
5402 seed = random() ^ random() >> 8 ^ random() << 8;
5403 if(seed<0) seed = -seed;
5407 int put(Board board, int pieceType, int rank, int n, int shade)
5408 // put the piece on the (n-1)-th empty squares of the given shade
5412 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5413 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5414 board[rank][i] = (ChessSquare) pieceType;
5415 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5417 piecesLeft[pieceType]--;
5425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5426 // calculate where the next piece goes, (any empty square), and put it there
5430 i = seed % squaresLeft[shade];
5431 nrOfShuffles *= squaresLeft[shade];
5432 seed /= squaresLeft[shade];
5433 put(board, pieceType, rank, i, shade);
5436 void AddTwoPieces(Board board, int pieceType, int rank)
5437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5439 int i, n=squaresLeft[ANY], j=n-1, k;
5441 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5442 i = seed % k; // pick one
5445 while(i >= j) i -= j--;
5446 j = n - 1 - j; i += j;
5447 put(board, pieceType, rank, j, ANY);
5448 put(board, pieceType, rank, i, ANY);
5451 void SetUpShuffle(Board board, int number)
5455 GetPositionNumber(); nrOfShuffles = 1;
5457 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5458 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5459 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5461 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5463 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5464 p = (int) board[0][i];
5465 if(p < (int) BlackPawn) piecesLeft[p] ++;
5466 board[0][i] = EmptySquare;
5469 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5470 // shuffles restricted to allow normal castling put KRR first
5471 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5472 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5473 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5474 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5475 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5476 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5477 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5478 put(board, WhiteRook, 0, 0, ANY);
5479 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5482 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5483 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5484 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5485 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5486 while(piecesLeft[p] >= 2) {
5487 AddOnePiece(board, p, 0, LITE);
5488 AddOnePiece(board, p, 0, DARK);
5490 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5493 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5494 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5495 // but we leave King and Rooks for last, to possibly obey FRC restriction
5496 if(p == (int)WhiteRook) continue;
5497 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5498 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5501 // now everything is placed, except perhaps King (Unicorn) and Rooks
5503 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5504 // Last King gets castling rights
5505 while(piecesLeft[(int)WhiteUnicorn]) {
5506 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5507 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5510 while(piecesLeft[(int)WhiteKing]) {
5511 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5512 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5517 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5518 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5521 // Only Rooks can be left; simply place them all
5522 while(piecesLeft[(int)WhiteRook]) {
5523 i = put(board, WhiteRook, 0, 0, ANY);
5524 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5527 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5529 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5533 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5536 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5539 int SetCharTable( char *table, const char * map )
5540 /* [HGM] moved here from winboard.c because of its general usefulness */
5541 /* Basically a safe strcpy that uses the last character as King */
5543 int result = FALSE; int NrPieces;
5545 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5546 && NrPieces >= 12 && !(NrPieces&1)) {
5547 int i; /* [HGM] Accept even length from 12 to 34 */
5549 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5550 for( i=0; i<NrPieces/2-1; i++ ) {
5552 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5554 table[(int) WhiteKing] = map[NrPieces/2-1];
5555 table[(int) BlackKing] = map[NrPieces-1];
5563 void Prelude(Board board)
5564 { // [HGM] superchess: random selection of exo-pieces
5565 int i, j, k; ChessSquare p;
5566 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5568 GetPositionNumber(); // use FRC position number
5570 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5571 SetCharTable(pieceToChar, appData.pieceToCharTable);
5572 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5573 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5576 j = seed%4; seed /= 4;
5577 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5578 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5579 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5580 j = seed%3 + (seed%3 >= j); seed /= 3;
5581 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5582 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5583 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5584 j = seed%3; seed /= 3;
5585 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5586 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5587 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5588 j = seed%2 + (seed%2 >= j); seed /= 2;
5589 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5592 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5593 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5594 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5595 put(board, exoPieces[0], 0, 0, ANY);
5596 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5600 InitPosition(redraw)
5603 ChessSquare (* pieces)[BOARD_FILES];
5604 int i, j, pawnRow, overrule,
5605 oldx = gameInfo.boardWidth,
5606 oldy = gameInfo.boardHeight,
5607 oldh = gameInfo.holdingsWidth;
5610 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5612 /* [AS] Initialize pv info list [HGM] and game status */
5614 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5615 pvInfoList[i].depth = 0;
5616 boards[i][EP_STATUS] = EP_NONE;
5617 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5620 initialRulePlies = 0; /* 50-move counter start */
5622 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5623 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5627 /* [HGM] logic here is completely changed. In stead of full positions */
5628 /* the initialized data only consist of the two backranks. The switch */
5629 /* selects which one we will use, which is than copied to the Board */
5630 /* initialPosition, which for the rest is initialized by Pawns and */
5631 /* empty squares. This initial position is then copied to boards[0], */
5632 /* possibly after shuffling, so that it remains available. */
5634 gameInfo.holdingsWidth = 0; /* default board sizes */
5635 gameInfo.boardWidth = 8;
5636 gameInfo.boardHeight = 8;
5637 gameInfo.holdingsSize = 0;
5638 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5639 for(i=0; i<BOARD_FILES-2; i++)
5640 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5641 initialPosition[EP_STATUS] = EP_NONE;
5642 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5643 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5644 SetCharTable(pieceNickName, appData.pieceNickNames);
5645 else SetCharTable(pieceNickName, "............");
5648 switch (gameInfo.variant) {
5649 case VariantFischeRandom:
5650 shuffleOpenings = TRUE;
5653 case VariantShatranj:
5654 pieces = ShatranjArray;
5655 nrCastlingRights = 0;
5656 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5659 pieces = makrukArray;
5660 nrCastlingRights = 0;
5661 startedFromSetupPosition = TRUE;
5662 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5664 case VariantTwoKings:
5665 pieces = twoKingsArray;
5667 case VariantCapaRandom:
5668 shuffleOpenings = TRUE;
5669 case VariantCapablanca:
5670 pieces = CapablancaArray;
5671 gameInfo.boardWidth = 10;
5672 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5675 pieces = GothicArray;
5676 gameInfo.boardWidth = 10;
5677 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5680 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5681 gameInfo.holdingsSize = 7;
5684 pieces = JanusArray;
5685 gameInfo.boardWidth = 10;
5686 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5687 nrCastlingRights = 6;
5688 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5689 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5690 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5691 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5692 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5693 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5696 pieces = FalconArray;
5697 gameInfo.boardWidth = 10;
5698 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5700 case VariantXiangqi:
5701 pieces = XiangqiArray;
5702 gameInfo.boardWidth = 9;
5703 gameInfo.boardHeight = 10;
5704 nrCastlingRights = 0;
5705 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5708 pieces = ShogiArray;
5709 gameInfo.boardWidth = 9;
5710 gameInfo.boardHeight = 9;
5711 gameInfo.holdingsSize = 7;
5712 nrCastlingRights = 0;
5713 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5715 case VariantCourier:
5716 pieces = CourierArray;
5717 gameInfo.boardWidth = 12;
5718 nrCastlingRights = 0;
5719 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5721 case VariantKnightmate:
5722 pieces = KnightmateArray;
5723 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5725 case VariantSpartan:
5726 pieces = SpartanArray;
5727 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5730 pieces = fairyArray;
5731 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5734 pieces = GreatArray;
5735 gameInfo.boardWidth = 10;
5736 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5737 gameInfo.holdingsSize = 8;
5741 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5742 gameInfo.holdingsSize = 8;
5743 startedFromSetupPosition = TRUE;
5745 case VariantCrazyhouse:
5746 case VariantBughouse:
5748 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5749 gameInfo.holdingsSize = 5;
5751 case VariantWildCastle:
5753 /* !!?shuffle with kings guaranteed to be on d or e file */
5754 shuffleOpenings = 1;
5756 case VariantNoCastle:
5758 nrCastlingRights = 0;
5759 /* !!?unconstrained back-rank shuffle */
5760 shuffleOpenings = 1;
5765 if(appData.NrFiles >= 0) {
5766 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5767 gameInfo.boardWidth = appData.NrFiles;
5769 if(appData.NrRanks >= 0) {
5770 gameInfo.boardHeight = appData.NrRanks;
5772 if(appData.holdingsSize >= 0) {
5773 i = appData.holdingsSize;
5774 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5775 gameInfo.holdingsSize = i;
5777 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5778 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5779 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5781 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5782 if(pawnRow < 1) pawnRow = 1;
5783 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5785 /* User pieceToChar list overrules defaults */
5786 if(appData.pieceToCharTable != NULL)
5787 SetCharTable(pieceToChar, appData.pieceToCharTable);
5789 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5791 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5792 s = (ChessSquare) 0; /* account holding counts in guard band */
5793 for( i=0; i<BOARD_HEIGHT; i++ )
5794 initialPosition[i][j] = s;
5796 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5797 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5798 initialPosition[pawnRow][j] = WhitePawn;
5799 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5800 if(gameInfo.variant == VariantXiangqi) {
5802 initialPosition[pawnRow][j] =
5803 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5804 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5805 initialPosition[2][j] = WhiteCannon;
5806 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5810 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5812 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5815 initialPosition[1][j] = WhiteBishop;
5816 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5818 initialPosition[1][j] = WhiteRook;
5819 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5822 if( nrCastlingRights == -1) {
5823 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5824 /* This sets default castling rights from none to normal corners */
5825 /* Variants with other castling rights must set them themselves above */
5826 nrCastlingRights = 6;
5828 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5829 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5830 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5831 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5832 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5833 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5836 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5837 if(gameInfo.variant == VariantGreat) { // promotion commoners
5838 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5839 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5840 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5841 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5843 if( gameInfo.variant == VariantSChess ) {
5844 initialPosition[1][0] = BlackMarshall;
5845 initialPosition[2][0] = BlackAngel;
5846 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5847 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5848 initialPosition[1][1] = initialPosition[2][1] =
5849 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5851 if (appData.debugMode) {
5852 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5854 if(shuffleOpenings) {
5855 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5856 startedFromSetupPosition = TRUE;
5858 if(startedFromPositionFile) {
5859 /* [HGM] loadPos: use PositionFile for every new game */
5860 CopyBoard(initialPosition, filePosition);
5861 for(i=0; i<nrCastlingRights; i++)
5862 initialRights[i] = filePosition[CASTLING][i];
5863 startedFromSetupPosition = TRUE;
5866 CopyBoard(boards[0], initialPosition);
5868 if(oldx != gameInfo.boardWidth ||
5869 oldy != gameInfo.boardHeight ||
5870 oldv != gameInfo.variant ||
5871 oldh != gameInfo.holdingsWidth
5873 InitDrawingSizes(-2 ,0);
5875 oldv = gameInfo.variant;
5877 DrawPosition(TRUE, boards[currentMove]);
5881 SendBoard(cps, moveNum)
5882 ChessProgramState *cps;
5885 char message[MSG_SIZ];
5887 if (cps->useSetboard) {
5888 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5889 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5890 SendToProgram(message, cps);
5896 /* Kludge to set black to move, avoiding the troublesome and now
5897 * deprecated "black" command.
5899 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5900 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5902 SendToProgram("edit\n", cps);
5903 SendToProgram("#\n", cps);
5904 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5905 bp = &boards[moveNum][i][BOARD_LEFT];
5906 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5907 if ((int) *bp < (int) BlackPawn) {
5908 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5910 if(message[0] == '+' || message[0] == '~') {
5911 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5912 PieceToChar((ChessSquare)(DEMOTED *bp)),
5915 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5916 message[1] = BOARD_RGHT - 1 - j + '1';
5917 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5919 SendToProgram(message, cps);
5924 SendToProgram("c\n", cps);
5925 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5926 bp = &boards[moveNum][i][BOARD_LEFT];
5927 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5928 if (((int) *bp != (int) EmptySquare)
5929 && ((int) *bp >= (int) BlackPawn)) {
5930 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5932 if(message[0] == '+' || message[0] == '~') {
5933 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5934 PieceToChar((ChessSquare)(DEMOTED *bp)),
5937 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5938 message[1] = BOARD_RGHT - 1 - j + '1';
5939 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5941 SendToProgram(message, cps);
5946 SendToProgram(".\n", cps);
5948 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5952 DefaultPromoChoice(int white)
5955 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5956 result = WhiteFerz; // no choice
5957 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5958 result= WhiteKing; // in Suicide Q is the last thing we want
5959 else if(gameInfo.variant == VariantSpartan)
5960 result = white ? WhiteQueen : WhiteAngel;
5961 else result = WhiteQueen;
5962 if(!white) result = WHITE_TO_BLACK result;
5966 static int autoQueen; // [HGM] oneclick
5969 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5971 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5972 /* [HGM] add Shogi promotions */
5973 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5978 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5979 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5981 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5982 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5985 piece = boards[currentMove][fromY][fromX];
5986 if(gameInfo.variant == VariantShogi) {
5987 promotionZoneSize = BOARD_HEIGHT/3;
5988 highestPromotingPiece = (int)WhiteFerz;
5989 } else if(gameInfo.variant == VariantMakruk) {
5990 promotionZoneSize = 3;
5993 // Treat Lance as Pawn when it is not representing Amazon
5994 if(gameInfo.variant != VariantSuper) {
5995 if(piece == WhiteLance) piece = WhitePawn; else
5996 if(piece == BlackLance) piece = BlackPawn;
5999 // next weed out all moves that do not touch the promotion zone at all
6000 if((int)piece >= BlackPawn) {
6001 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6003 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6005 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6006 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6009 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6011 // weed out mandatory Shogi promotions
6012 if(gameInfo.variant == VariantShogi) {
6013 if(piece >= BlackPawn) {
6014 if(toY == 0 && piece == BlackPawn ||
6015 toY == 0 && piece == BlackQueen ||
6016 toY <= 1 && piece == BlackKnight) {
6021 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6022 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6023 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6030 // weed out obviously illegal Pawn moves
6031 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6032 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6033 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6034 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6035 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6036 // note we are not allowed to test for valid (non-)capture, due to premove
6039 // we either have a choice what to promote to, or (in Shogi) whether to promote
6040 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6041 *promoChoice = PieceToChar(BlackFerz); // no choice
6044 // no sense asking what we must promote to if it is going to explode...
6045 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6046 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6049 // give caller the default choice even if we will not make it
6050 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6051 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6052 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6053 && gameInfo.variant != VariantShogi
6054 && gameInfo.variant != VariantSuper) return FALSE;
6055 if(autoQueen) return FALSE; // predetermined
6057 // suppress promotion popup on illegal moves that are not premoves
6058 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6059 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6060 if(appData.testLegality && !premove) {
6061 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6062 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6063 if(moveType != WhitePromotion && moveType != BlackPromotion)
6071 InPalace(row, column)
6073 { /* [HGM] for Xiangqi */
6074 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6075 column < (BOARD_WIDTH + 4)/2 &&
6076 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6081 PieceForSquare (x, y)
6085 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6088 return boards[currentMove][y][x];
6092 OKToStartUserMove(x, y)
6095 ChessSquare from_piece;
6098 if (matchMode) return FALSE;
6099 if (gameMode == EditPosition) return TRUE;
6101 if (x >= 0 && y >= 0)
6102 from_piece = boards[currentMove][y][x];
6104 from_piece = EmptySquare;
6106 if (from_piece == EmptySquare) return FALSE;
6108 white_piece = (int)from_piece >= (int)WhitePawn &&
6109 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6112 case PlayFromGameFile:
6114 case TwoMachinesPlay:
6122 case MachinePlaysWhite:
6123 case IcsPlayingBlack:
6124 if (appData.zippyPlay) return FALSE;
6126 DisplayMoveError(_("You are playing Black"));
6131 case MachinePlaysBlack:
6132 case IcsPlayingWhite:
6133 if (appData.zippyPlay) return FALSE;
6135 DisplayMoveError(_("You are playing White"));
6141 if (!white_piece && WhiteOnMove(currentMove)) {
6142 DisplayMoveError(_("It is White's turn"));
6145 if (white_piece && !WhiteOnMove(currentMove)) {
6146 DisplayMoveError(_("It is Black's turn"));
6149 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6150 /* Editing correspondence game history */
6151 /* Could disallow this or prompt for confirmation */
6156 case BeginningOfGame:
6157 if (appData.icsActive) return FALSE;
6158 if (!appData.noChessProgram) {
6160 DisplayMoveError(_("You are playing White"));
6167 if (!white_piece && WhiteOnMove(currentMove)) {
6168 DisplayMoveError(_("It is White's turn"));
6171 if (white_piece && !WhiteOnMove(currentMove)) {
6172 DisplayMoveError(_("It is Black's turn"));
6181 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6182 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6183 && gameMode != AnalyzeFile && gameMode != Training) {
6184 DisplayMoveError(_("Displayed position is not current"));
6191 OnlyMove(int *x, int *y, Boolean captures) {
6192 DisambiguateClosure cl;
6193 if (appData.zippyPlay) return FALSE;
6195 case MachinePlaysBlack:
6196 case IcsPlayingWhite:
6197 case BeginningOfGame:
6198 if(!WhiteOnMove(currentMove)) return FALSE;
6200 case MachinePlaysWhite:
6201 case IcsPlayingBlack:
6202 if(WhiteOnMove(currentMove)) return FALSE;
6209 cl.pieceIn = EmptySquare;
6214 cl.promoCharIn = NULLCHAR;
6215 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6216 if( cl.kind == NormalMove ||
6217 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6218 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6219 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6226 if(cl.kind != ImpossibleMove) return FALSE;
6227 cl.pieceIn = EmptySquare;
6232 cl.promoCharIn = NULLCHAR;
6233 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6234 if( cl.kind == NormalMove ||
6235 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6236 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6237 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6242 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6248 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6249 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6250 int lastLoadGameUseList = FALSE;
6251 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6252 ChessMove lastLoadGameStart = EndOfFile;
6255 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6256 int fromX, fromY, toX, toY;
6260 ChessSquare pdown, pup;
6262 /* Check if the user is playing in turn. This is complicated because we
6263 let the user "pick up" a piece before it is his turn. So the piece he
6264 tried to pick up may have been captured by the time he puts it down!
6265 Therefore we use the color the user is supposed to be playing in this
6266 test, not the color of the piece that is currently on the starting
6267 square---except in EditGame mode, where the user is playing both
6268 sides; fortunately there the capture race can't happen. (It can
6269 now happen in IcsExamining mode, but that's just too bad. The user
6270 will get a somewhat confusing message in that case.)
6274 case PlayFromGameFile:
6276 case TwoMachinesPlay:
6280 /* We switched into a game mode where moves are not accepted,
6281 perhaps while the mouse button was down. */
6284 case MachinePlaysWhite:
6285 /* User is moving for Black */
6286 if (WhiteOnMove(currentMove)) {
6287 DisplayMoveError(_("It is White's turn"));
6292 case MachinePlaysBlack:
6293 /* User is moving for White */
6294 if (!WhiteOnMove(currentMove)) {
6295 DisplayMoveError(_("It is Black's turn"));
6302 case BeginningOfGame:
6305 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6306 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6307 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6308 /* User is moving for Black */
6309 if (WhiteOnMove(currentMove)) {
6310 DisplayMoveError(_("It is White's turn"));
6314 /* User is moving for White */
6315 if (!WhiteOnMove(currentMove)) {
6316 DisplayMoveError(_("It is Black's turn"));
6322 case IcsPlayingBlack:
6323 /* User is moving for Black */
6324 if (WhiteOnMove(currentMove)) {
6325 if (!appData.premove) {
6326 DisplayMoveError(_("It is White's turn"));
6327 } else if (toX >= 0 && toY >= 0) {
6330 premoveFromX = fromX;
6331 premoveFromY = fromY;
6332 premovePromoChar = promoChar;
6334 if (appData.debugMode)
6335 fprintf(debugFP, "Got premove: fromX %d,"
6336 "fromY %d, toX %d, toY %d\n",
6337 fromX, fromY, toX, toY);
6343 case IcsPlayingWhite:
6344 /* User is moving for White */
6345 if (!WhiteOnMove(currentMove)) {
6346 if (!appData.premove) {
6347 DisplayMoveError(_("It is Black's turn"));
6348 } else if (toX >= 0 && toY >= 0) {
6351 premoveFromX = fromX;
6352 premoveFromY = fromY;
6353 premovePromoChar = promoChar;
6355 if (appData.debugMode)
6356 fprintf(debugFP, "Got premove: fromX %d,"
6357 "fromY %d, toX %d, toY %d\n",
6358 fromX, fromY, toX, toY);
6368 /* EditPosition, empty square, or different color piece;
6369 click-click move is possible */
6370 if (toX == -2 || toY == -2) {
6371 boards[0][fromY][fromX] = EmptySquare;
6372 DrawPosition(FALSE, boards[currentMove]);
6374 } else if (toX >= 0 && toY >= 0) {
6375 boards[0][toY][toX] = boards[0][fromY][fromX];
6376 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6377 if(boards[0][fromY][0] != EmptySquare) {
6378 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6379 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6382 if(fromX == BOARD_RGHT+1) {
6383 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6384 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6385 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6388 boards[0][fromY][fromX] = EmptySquare;
6389 DrawPosition(FALSE, boards[currentMove]);
6395 if(toX < 0 || toY < 0) return;
6396 pdown = boards[currentMove][fromY][fromX];
6397 pup = boards[currentMove][toY][toX];
6399 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6400 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6401 if( pup != EmptySquare ) return;
6402 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6403 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6404 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6405 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6406 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6407 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6408 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6412 /* [HGM] always test for legality, to get promotion info */
6413 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6414 fromY, fromX, toY, toX, promoChar);
6415 /* [HGM] but possibly ignore an IllegalMove result */
6416 if (appData.testLegality) {
6417 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6418 DisplayMoveError(_("Illegal move"));
6423 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6426 /* Common tail of UserMoveEvent and DropMenuEvent */
6428 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6430 int fromX, fromY, toX, toY;
6431 /*char*/int promoChar;
6435 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6436 // [HGM] superchess: suppress promotions to non-available piece
6437 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6438 if(WhiteOnMove(currentMove)) {
6439 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6441 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6445 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6446 move type in caller when we know the move is a legal promotion */
6447 if(moveType == NormalMove && promoChar)
6448 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6450 /* [HGM] <popupFix> The following if has been moved here from
6451 UserMoveEvent(). Because it seemed to belong here (why not allow
6452 piece drops in training games?), and because it can only be
6453 performed after it is known to what we promote. */
6454 if (gameMode == Training) {
6455 /* compare the move played on the board to the next move in the
6456 * game. If they match, display the move and the opponent's response.
6457 * If they don't match, display an error message.
6461 CopyBoard(testBoard, boards[currentMove]);
6462 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6464 if (CompareBoards(testBoard, boards[currentMove+1])) {
6465 ForwardInner(currentMove+1);
6467 /* Autoplay the opponent's response.
6468 * if appData.animate was TRUE when Training mode was entered,
6469 * the response will be animated.
6471 saveAnimate = appData.animate;
6472 appData.animate = animateTraining;
6473 ForwardInner(currentMove+1);
6474 appData.animate = saveAnimate;
6476 /* check for the end of the game */
6477 if (currentMove >= forwardMostMove) {
6478 gameMode = PlayFromGameFile;
6480 SetTrainingModeOff();
6481 DisplayInformation(_("End of game"));
6484 DisplayError(_("Incorrect move"), 0);
6489 /* Ok, now we know that the move is good, so we can kill
6490 the previous line in Analysis Mode */
6491 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6492 && currentMove < forwardMostMove) {
6493 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6494 else forwardMostMove = currentMove;
6497 /* If we need the chess program but it's dead, restart it */
6498 ResurrectChessProgram();
6500 /* A user move restarts a paused game*/
6504 thinkOutput[0] = NULLCHAR;
6506 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6508 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6509 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6513 if (gameMode == BeginningOfGame) {
6514 if (appData.noChessProgram) {
6515 gameMode = EditGame;
6519 gameMode = MachinePlaysBlack;
6522 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6524 if (first.sendName) {
6525 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6526 SendToProgram(buf, &first);
6533 /* Relay move to ICS or chess engine */
6534 if (appData.icsActive) {
6535 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6536 gameMode == IcsExamining) {
6537 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6538 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6540 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6542 // also send plain move, in case ICS does not understand atomic claims
6543 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6547 if (first.sendTime && (gameMode == BeginningOfGame ||
6548 gameMode == MachinePlaysWhite ||
6549 gameMode == MachinePlaysBlack)) {
6550 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6552 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6553 // [HGM] book: if program might be playing, let it use book
6554 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6555 first.maybeThinking = TRUE;
6556 } else SendMoveToProgram(forwardMostMove-1, &first);
6557 if (currentMove == cmailOldMove + 1) {
6558 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6562 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6566 if(appData.testLegality)
6567 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6573 if (WhiteOnMove(currentMove)) {
6574 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6576 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6580 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6585 case MachinePlaysBlack:
6586 case MachinePlaysWhite:
6587 /* disable certain menu options while machine is thinking */
6588 SetMachineThinkingEnables();
6595 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6596 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6598 if(bookHit) { // [HGM] book: simulate book reply
6599 static char bookMove[MSG_SIZ]; // a bit generous?
6601 programStats.nodes = programStats.depth = programStats.time =
6602 programStats.score = programStats.got_only_move = 0;
6603 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6605 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6606 strcat(bookMove, bookHit);
6607 HandleMachineMove(bookMove, &first);
6613 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6620 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6621 Markers *m = (Markers *) closure;
6622 if(rf == fromY && ff == fromX)
6623 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6624 || kind == WhiteCapturesEnPassant
6625 || kind == BlackCapturesEnPassant);
6626 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6630 MarkTargetSquares(int clear)
6633 if(!appData.markers || !appData.highlightDragging ||
6634 !appData.testLegality || gameMode == EditPosition) return;
6636 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6639 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6640 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6641 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6643 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6646 DrawPosition(TRUE, NULL);
6650 Explode(Board board, int fromX, int fromY, int toX, int toY)
6652 if(gameInfo.variant == VariantAtomic &&
6653 (board[toY][toX] != EmptySquare || // capture?
6654 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6655 board[fromY][fromX] == BlackPawn )
6657 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6663 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6665 int CanPromote(ChessSquare piece, int y)
6667 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6668 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6669 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6670 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6671 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6672 gameInfo.variant == VariantMakruk) return FALSE;
6673 return (piece == BlackPawn && y == 1 ||
6674 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6675 piece == BlackLance && y == 1 ||
6676 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6679 void LeftClick(ClickType clickType, int xPix, int yPix)
6682 Boolean saveAnimate;
6683 static int second = 0, promotionChoice = 0, clearFlag = 0;
6684 char promoChoice = NULLCHAR;
6687 if(appData.seekGraph && appData.icsActive && loggedOn &&
6688 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6689 SeekGraphClick(clickType, xPix, yPix, 0);
6693 if (clickType == Press) ErrorPopDown();
6694 MarkTargetSquares(1);
6696 x = EventToSquare(xPix, BOARD_WIDTH);
6697 y = EventToSquare(yPix, BOARD_HEIGHT);
6698 if (!flipView && y >= 0) {
6699 y = BOARD_HEIGHT - 1 - y;
6701 if (flipView && x >= 0) {
6702 x = BOARD_WIDTH - 1 - x;
6705 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6706 defaultPromoChoice = promoSweep;
6707 promoSweep = EmptySquare; // terminate sweep
6708 promoDefaultAltered = TRUE;
6709 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6712 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6713 if(clickType == Release) return; // ignore upclick of click-click destination
6714 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6715 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6716 if(gameInfo.holdingsWidth &&
6717 (WhiteOnMove(currentMove)
6718 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6719 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6720 // click in right holdings, for determining promotion piece
6721 ChessSquare p = boards[currentMove][y][x];
6722 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6723 if(p != EmptySquare) {
6724 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6729 DrawPosition(FALSE, boards[currentMove]);
6733 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6734 if(clickType == Press
6735 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6736 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6737 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6740 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6741 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6743 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6744 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6745 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6746 defaultPromoChoice = DefaultPromoChoice(side);
6749 autoQueen = appData.alwaysPromoteToQueen;
6753 gatingPiece = EmptySquare;
6754 if (clickType != Press) {
6755 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6756 DragPieceEnd(xPix, yPix); dragging = 0;
6757 DrawPosition(FALSE, NULL);
6761 fromX = x; fromY = y;
6762 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6763 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6764 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6766 if (OKToStartUserMove(fromX, fromY)) {
6768 MarkTargetSquares(0);
6769 DragPieceBegin(xPix, yPix); dragging = 1;
6770 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6771 promoSweep = defaultPromoChoice;
6772 selectFlag = 0; lastX = xPix; lastY = yPix;
6773 Sweep(0); // Pawn that is going to promote: preview promotion piece
6774 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6776 if (appData.highlightDragging) {
6777 SetHighlights(fromX, fromY, -1, -1);
6779 } else fromX = fromY = -1;
6785 if (clickType == Press && gameMode != EditPosition) {
6790 // ignore off-board to clicks
6791 if(y < 0 || x < 0) return;
6793 /* Check if clicking again on the same color piece */
6794 fromP = boards[currentMove][fromY][fromX];
6795 toP = boards[currentMove][y][x];
6796 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6797 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6798 WhitePawn <= toP && toP <= WhiteKing &&
6799 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6800 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6801 (BlackPawn <= fromP && fromP <= BlackKing &&
6802 BlackPawn <= toP && toP <= BlackKing &&
6803 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6804 !(fromP == BlackKing && toP == BlackRook && frc))) {
6805 /* Clicked again on same color piece -- changed his mind */
6806 second = (x == fromX && y == fromY);
6807 promoDefaultAltered = FALSE;
6808 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6809 if (appData.highlightDragging) {
6810 SetHighlights(x, y, -1, -1);
6814 if (OKToStartUserMove(x, y)) {
6815 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6816 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6817 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6818 gatingPiece = boards[currentMove][fromY][fromX];
6819 else gatingPiece = EmptySquare;
6821 fromY = y; dragging = 1;
6822 MarkTargetSquares(0);
6823 DragPieceBegin(xPix, yPix);
6824 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6825 promoSweep = defaultPromoChoice;
6826 selectFlag = 0; lastX = xPix; lastY = yPix;
6827 Sweep(0); // Pawn that is going to promote: preview promotion piece
6831 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6834 // ignore clicks on holdings
6835 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6838 if (clickType == Release && x == fromX && y == fromY) {
6839 DragPieceEnd(xPix, yPix); dragging = 0;
6841 // a deferred attempt to click-click move an empty square on top of a piece
6842 boards[currentMove][y][x] = EmptySquare;
6844 DrawPosition(FALSE, boards[currentMove]);
6845 fromX = fromY = -1; clearFlag = 0;
6848 if (appData.animateDragging) {
6849 /* Undo animation damage if any */
6850 DrawPosition(FALSE, NULL);
6853 /* Second up/down in same square; just abort move */
6856 gatingPiece = EmptySquare;
6859 ClearPremoveHighlights();
6861 /* First upclick in same square; start click-click mode */
6862 SetHighlights(x, y, -1, -1);
6869 /* we now have a different from- and (possibly off-board) to-square */
6870 /* Completed move */
6873 saveAnimate = appData.animate;
6874 if (clickType == Press) {
6875 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6876 // must be Edit Position mode with empty-square selected
6877 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6878 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6881 /* Finish clickclick move */
6882 if (appData.animate || appData.highlightLastMove) {
6883 SetHighlights(fromX, fromY, toX, toY);
6888 /* Finish drag move */
6889 if (appData.highlightLastMove) {
6890 SetHighlights(fromX, fromY, toX, toY);
6894 DragPieceEnd(xPix, yPix); dragging = 0;
6895 /* Don't animate move and drag both */
6896 appData.animate = FALSE;
6899 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6900 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6901 ChessSquare piece = boards[currentMove][fromY][fromX];
6902 if(gameMode == EditPosition && piece != EmptySquare &&
6903 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6906 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6907 n = PieceToNumber(piece - (int)BlackPawn);
6908 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6909 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6910 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6912 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6913 n = PieceToNumber(piece);
6914 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6915 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6916 boards[currentMove][n][BOARD_WIDTH-2]++;
6918 boards[currentMove][fromY][fromX] = EmptySquare;
6922 DrawPosition(TRUE, boards[currentMove]);
6926 // off-board moves should not be highlighted
6927 if(x < 0 || y < 0) ClearHighlights();
6929 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6931 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6932 SetHighlights(fromX, fromY, toX, toY);
6933 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6934 // [HGM] super: promotion to captured piece selected from holdings
6935 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6936 promotionChoice = TRUE;
6937 // kludge follows to temporarily execute move on display, without promoting yet
6938 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6939 boards[currentMove][toY][toX] = p;
6940 DrawPosition(FALSE, boards[currentMove]);
6941 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6942 boards[currentMove][toY][toX] = q;
6943 DisplayMessage("Click in holdings to choose piece", "");
6948 int oldMove = currentMove;
6949 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6950 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6951 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6952 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6953 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6954 DrawPosition(TRUE, boards[currentMove]);
6957 appData.animate = saveAnimate;
6958 if (appData.animate || appData.animateDragging) {
6959 /* Undo animation damage if needed */
6960 DrawPosition(FALSE, NULL);
6964 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6965 { // front-end-free part taken out of PieceMenuPopup
6966 int whichMenu; int xSqr, ySqr;
6968 if(seekGraphUp) { // [HGM] seekgraph
6969 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6970 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6974 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6975 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6976 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6977 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6978 if(action == Press) {
6979 originalFlip = flipView;
6980 flipView = !flipView; // temporarily flip board to see game from partners perspective
6981 DrawPosition(TRUE, partnerBoard);
6982 DisplayMessage(partnerStatus, "");
6984 } else if(action == Release) {
6985 flipView = originalFlip;
6986 DrawPosition(TRUE, boards[currentMove]);
6992 xSqr = EventToSquare(x, BOARD_WIDTH);
6993 ySqr = EventToSquare(y, BOARD_HEIGHT);
6994 if (action == Release) {
6995 if(pieceSweep != EmptySquare) {
6996 EditPositionMenuEvent(pieceSweep, toX, toY);
6997 pieceSweep = EmptySquare;
6998 } else UnLoadPV(); // [HGM] pv
7000 if (action != Press) return -2; // return code to be ignored
7003 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7005 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7006 if (xSqr < 0 || ySqr < 0) return -1;
7007 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7008 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7009 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7010 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7014 if(!appData.icsEngineAnalyze) return -1;
7015 case IcsPlayingWhite:
7016 case IcsPlayingBlack:
7017 if(!appData.zippyPlay) goto noZip;
7020 case MachinePlaysWhite:
7021 case MachinePlaysBlack:
7022 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7023 if (!appData.dropMenu) {
7025 return 2; // flag front-end to grab mouse events
7027 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7028 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7031 if (xSqr < 0 || ySqr < 0) return -1;
7032 if (!appData.dropMenu || appData.testLegality &&
7033 gameInfo.variant != VariantBughouse &&
7034 gameInfo.variant != VariantCrazyhouse) return -1;
7035 whichMenu = 1; // drop menu
7041 if (((*fromX = xSqr) < 0) ||
7042 ((*fromY = ySqr) < 0)) {
7043 *fromX = *fromY = -1;
7047 *fromX = BOARD_WIDTH - 1 - *fromX;
7049 *fromY = BOARD_HEIGHT - 1 - *fromY;
7054 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7056 // char * hint = lastHint;
7057 FrontEndProgramStats stats;
7059 stats.which = cps == &first ? 0 : 1;
7060 stats.depth = cpstats->depth;
7061 stats.nodes = cpstats->nodes;
7062 stats.score = cpstats->score;
7063 stats.time = cpstats->time;
7064 stats.pv = cpstats->movelist;
7065 stats.hint = lastHint;
7066 stats.an_move_index = 0;
7067 stats.an_move_count = 0;
7069 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7070 stats.hint = cpstats->move_name;
7071 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7072 stats.an_move_count = cpstats->nr_moves;
7075 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
7077 SetProgramStats( &stats );
7080 #define MAXPLAYERS 500
7083 TourneyStandings(int display)
7085 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7086 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7087 char result, *p, *names[MAXPLAYERS];
7089 names[0] = p = strdup(appData.participants);
7090 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7092 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7094 while(result = appData.results[nr]) {
7095 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7096 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7097 wScore = bScore = 0;
7099 case '+': wScore = 2; break;
7100 case '-': bScore = 2; break;
7101 case '=': wScore = bScore = 1; break;
7103 case '*': return strdup("busy"); // tourney not finished
7111 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7112 for(w=0; w<nPlayers; w++) {
7114 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7115 ranking[w] = b; points[w] = bScore; score[b] = -2;
7117 p = malloc(nPlayers*34+1);
7118 for(w=0; w<nPlayers && w<display; w++)
7119 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7125 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7126 { // count all piece types
7128 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7129 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7130 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7133 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7134 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7135 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7136 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7137 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7138 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7143 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7145 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7146 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7148 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7149 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7150 if(myPawns == 2 && nMine == 3) // KPP
7151 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7152 if(myPawns == 1 && nMine == 2) // KP
7153 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7154 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7155 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7156 if(myPawns) return FALSE;
7157 if(pCnt[WhiteRook+side])
7158 return pCnt[BlackRook-side] ||
7159 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7160 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7161 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7162 if(pCnt[WhiteCannon+side]) {
7163 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7164 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7166 if(pCnt[WhiteKnight+side])
7167 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7172 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7174 VariantClass v = gameInfo.variant;
7176 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7177 if(v == VariantShatranj) return TRUE; // always winnable through baring
7178 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7179 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7181 if(v == VariantXiangqi) {
7182 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7184 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7185 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7186 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7187 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7188 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7189 if(stale) // we have at least one last-rank P plus perhaps C
7190 return majors // KPKX
7191 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7193 return pCnt[WhiteFerz+side] // KCAK
7194 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7195 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7196 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7198 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7199 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7201 if(nMine == 1) return FALSE; // bare King
7202 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
7203 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7204 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7205 // by now we have King + 1 piece (or multiple Bishops on the same color)
7206 if(pCnt[WhiteKnight+side])
7207 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7208 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7209 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7211 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7212 if(pCnt[WhiteAlfil+side])
7213 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7214 if(pCnt[WhiteWazir+side])
7215 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7222 Adjudicate(ChessProgramState *cps)
7223 { // [HGM] some adjudications useful with buggy engines
7224 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7225 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7226 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7227 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7228 int k, count = 0; static int bare = 1;
7229 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7230 Boolean canAdjudicate = !appData.icsActive;
7232 // most tests only when we understand the game, i.e. legality-checking on
7233 if( appData.testLegality )
7234 { /* [HGM] Some more adjudications for obstinate engines */
7235 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7236 static int moveCount = 6;
7238 char *reason = NULL;
7240 /* Count what is on board. */
7241 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7243 /* Some material-based adjudications that have to be made before stalemate test */
7244 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7245 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7246 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7247 if(canAdjudicate && appData.checkMates) {
7249 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7250 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7251 "Xboard adjudication: King destroyed", GE_XBOARD );
7256 /* Bare King in Shatranj (loses) or Losers (wins) */
7257 if( nrW == 1 || nrB == 1) {
7258 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7259 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7260 if(canAdjudicate && appData.checkMates) {
7262 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7263 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7264 "Xboard adjudication: Bare king", GE_XBOARD );
7268 if( gameInfo.variant == VariantShatranj && --bare < 0)
7270 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7271 if(canAdjudicate && appData.checkMates) {
7272 /* but only adjudicate if adjudication enabled */
7274 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7275 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7276 "Xboard adjudication: Bare king", GE_XBOARD );
7283 // don't wait for engine to announce game end if we can judge ourselves
7284 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7286 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7287 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7288 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7289 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7292 reason = "Xboard adjudication: 3rd check";
7293 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7303 reason = "Xboard adjudication: Stalemate";
7304 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7305 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7306 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7307 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7308 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7309 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7310 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7311 EP_CHECKMATE : EP_WINS);
7312 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7313 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7317 reason = "Xboard adjudication: Checkmate";
7318 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7322 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7324 result = GameIsDrawn; break;
7326 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7328 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7332 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7334 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7335 GameEnds( result, reason, GE_XBOARD );
7339 /* Next absolutely insufficient mating material. */
7340 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7341 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7342 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7344 /* always flag draws, for judging claims */
7345 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7347 if(canAdjudicate && appData.materialDraws) {
7348 /* but only adjudicate them if adjudication enabled */
7349 if(engineOpponent) {
7350 SendToProgram("force\n", engineOpponent); // suppress reply
7351 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7353 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7358 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7359 if(gameInfo.variant == VariantXiangqi ?
7360 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7362 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7363 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7364 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7365 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7367 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7368 { /* if the first 3 moves do not show a tactical win, declare draw */
7369 if(engineOpponent) {
7370 SendToProgram("force\n", engineOpponent); // suppress reply
7371 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7373 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7376 } else moveCount = 6;
7378 if (appData.debugMode) { int i;
7379 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7380 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7381 appData.drawRepeats);
7382 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7383 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7387 // Repetition draws and 50-move rule can be applied independently of legality testing
7389 /* Check for rep-draws */
7391 for(k = forwardMostMove-2;
7392 k>=backwardMostMove && k>=forwardMostMove-100 &&
7393 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7394 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7397 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7398 /* compare castling rights */
7399 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7400 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7401 rights++; /* King lost rights, while rook still had them */
7402 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7403 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7404 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7405 rights++; /* but at least one rook lost them */
7407 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7408 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7410 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7411 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7412 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7415 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7416 && appData.drawRepeats > 1) {
7417 /* adjudicate after user-specified nr of repeats */
7418 int result = GameIsDrawn;
7419 char *details = "XBoard adjudication: repetition draw";
7420 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7421 // [HGM] xiangqi: check for forbidden perpetuals
7422 int m, ourPerpetual = 1, hisPerpetual = 1;
7423 for(m=forwardMostMove; m>k; m-=2) {
7424 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7425 ourPerpetual = 0; // the current mover did not always check
7426 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7427 hisPerpetual = 0; // the opponent did not always check
7429 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7430 ourPerpetual, hisPerpetual);
7431 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7432 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7433 details = "Xboard adjudication: perpetual checking";
7435 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7436 break; // (or we would have caught him before). Abort repetition-checking loop.
7438 // Now check for perpetual chases
7439 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7440 hisPerpetual = PerpetualChase(k, forwardMostMove);
7441 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7442 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7443 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7444 details = "Xboard adjudication: perpetual chasing";
7446 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7447 break; // Abort repetition-checking loop.
7449 // if neither of us is checking or chasing all the time, or both are, it is draw
7451 if(engineOpponent) {
7452 SendToProgram("force\n", engineOpponent); // suppress reply
7453 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7455 GameEnds( result, details, GE_XBOARD );
7458 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7459 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7463 /* Now we test for 50-move draws. Determine ply count */
7464 count = forwardMostMove;
7465 /* look for last irreversble move */
7466 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7468 /* if we hit starting position, add initial plies */
7469 if( count == backwardMostMove )
7470 count -= initialRulePlies;
7471 count = forwardMostMove - count;
7472 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7473 // adjust reversible move counter for checks in Xiangqi
7474 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7475 if(i < backwardMostMove) i = backwardMostMove;
7476 while(i <= forwardMostMove) {
7477 lastCheck = inCheck; // check evasion does not count
7478 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7479 if(inCheck || lastCheck) count--; // check does not count
7484 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7485 /* this is used to judge if draw claims are legal */
7486 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7487 if(engineOpponent) {
7488 SendToProgram("force\n", engineOpponent); // suppress reply
7489 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7491 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7495 /* if draw offer is pending, treat it as a draw claim
7496 * when draw condition present, to allow engines a way to
7497 * claim draws before making their move to avoid a race
7498 * condition occurring after their move
7500 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7502 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7503 p = "Draw claim: 50-move rule";
7504 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7505 p = "Draw claim: 3-fold repetition";
7506 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7507 p = "Draw claim: insufficient mating material";
7508 if( p != NULL && canAdjudicate) {
7509 if(engineOpponent) {
7510 SendToProgram("force\n", engineOpponent); // suppress reply
7511 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513 GameEnds( GameIsDrawn, p, GE_XBOARD );
7518 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7519 if(engineOpponent) {
7520 SendToProgram("force\n", engineOpponent); // suppress reply
7521 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7529 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7530 { // [HGM] book: this routine intercepts moves to simulate book replies
7531 char *bookHit = NULL;
7533 //first determine if the incoming move brings opponent into his book
7534 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7535 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7536 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7537 if(bookHit != NULL && !cps->bookSuspend) {
7538 // make sure opponent is not going to reply after receiving move to book position
7539 SendToProgram("force\n", cps);
7540 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7542 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7543 // now arrange restart after book miss
7545 // after a book hit we never send 'go', and the code after the call to this routine
7546 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7548 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7549 SendToProgram(buf, cps);
7550 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7551 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7552 SendToProgram("go\n", cps);
7553 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7554 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7555 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7556 SendToProgram("go\n", cps);
7557 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7559 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7563 ChessProgramState *savedState;
7564 void DeferredBookMove(void)
7566 if(savedState->lastPing != savedState->lastPong)
7567 ScheduleDelayedEvent(DeferredBookMove, 10);
7569 HandleMachineMove(savedMessage, savedState);
7573 HandleMachineMove(message, cps)
7575 ChessProgramState *cps;
7577 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7578 char realname[MSG_SIZ];
7579 int fromX, fromY, toX, toY;
7588 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7590 * Kludge to ignore BEL characters
7592 while (*message == '\007') message++;
7595 * [HGM] engine debug message: ignore lines starting with '#' character
7597 if(cps->debug && *message == '#') return;
7600 * Look for book output
7602 if (cps == &first && bookRequested) {
7603 if (message[0] == '\t' || message[0] == ' ') {
7604 /* Part of the book output is here; append it */
7605 strcat(bookOutput, message);
7606 strcat(bookOutput, " \n");
7608 } else if (bookOutput[0] != NULLCHAR) {
7609 /* All of book output has arrived; display it */
7610 char *p = bookOutput;
7611 while (*p != NULLCHAR) {
7612 if (*p == '\t') *p = ' ';
7615 DisplayInformation(bookOutput);
7616 bookRequested = FALSE;
7617 /* Fall through to parse the current output */
7622 * Look for machine move.
7624 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7625 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7627 /* This method is only useful on engines that support ping */
7628 if (cps->lastPing != cps->lastPong) {
7629 if (gameMode == BeginningOfGame) {
7630 /* Extra move from before last new; ignore */
7631 if (appData.debugMode) {
7632 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7635 if (appData.debugMode) {
7636 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7637 cps->which, gameMode);
7640 SendToProgram("undo\n", cps);
7646 case BeginningOfGame:
7647 /* Extra move from before last reset; ignore */
7648 if (appData.debugMode) {
7649 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7656 /* Extra move after we tried to stop. The mode test is
7657 not a reliable way of detecting this problem, but it's
7658 the best we can do on engines that don't support ping.
7660 if (appData.debugMode) {
7661 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7662 cps->which, gameMode);
7664 SendToProgram("undo\n", cps);
7667 case MachinePlaysWhite:
7668 case IcsPlayingWhite:
7669 machineWhite = TRUE;
7672 case MachinePlaysBlack:
7673 case IcsPlayingBlack:
7674 machineWhite = FALSE;
7677 case TwoMachinesPlay:
7678 machineWhite = (cps->twoMachinesColor[0] == 'w');
7681 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7682 if (appData.debugMode) {
7684 "Ignoring move out of turn by %s, gameMode %d"
7685 ", forwardMost %d\n",
7686 cps->which, gameMode, forwardMostMove);
7691 if (appData.debugMode) { int f = forwardMostMove;
7692 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7693 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7694 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7696 if(cps->alphaRank) AlphaRank(machineMove, 4);
7697 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7698 &fromX, &fromY, &toX, &toY, &promoChar)) {
7699 /* Machine move could not be parsed; ignore it. */
7700 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7701 machineMove, _(cps->which));
7702 DisplayError(buf1, 0);
7703 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7704 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7705 if (gameMode == TwoMachinesPlay) {
7706 GameEnds(machineWhite ? BlackWins : WhiteWins,
7712 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7713 /* So we have to redo legality test with true e.p. status here, */
7714 /* to make sure an illegal e.p. capture does not slip through, */
7715 /* to cause a forfeit on a justified illegal-move complaint */
7716 /* of the opponent. */
7717 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7719 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7720 fromY, fromX, toY, toX, promoChar);
7721 if (appData.debugMode) {
7723 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7724 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7725 fprintf(debugFP, "castling rights\n");
7727 if(moveType == IllegalMove) {
7728 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7729 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7730 GameEnds(machineWhite ? BlackWins : WhiteWins,
7733 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7734 /* [HGM] Kludge to handle engines that send FRC-style castling
7735 when they shouldn't (like TSCP-Gothic) */
7737 case WhiteASideCastleFR:
7738 case BlackASideCastleFR:
7740 currentMoveString[2]++;
7742 case WhiteHSideCastleFR:
7743 case BlackHSideCastleFR:
7745 currentMoveString[2]--;
7747 default: ; // nothing to do, but suppresses warning of pedantic compilers
7750 hintRequested = FALSE;
7751 lastHint[0] = NULLCHAR;
7752 bookRequested = FALSE;
7753 /* Program may be pondering now */
7754 cps->maybeThinking = TRUE;
7755 if (cps->sendTime == 2) cps->sendTime = 1;
7756 if (cps->offeredDraw) cps->offeredDraw--;
7758 /* [AS] Save move info*/
7759 pvInfoList[ forwardMostMove ].score = programStats.score;
7760 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7761 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7763 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7765 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7766 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7769 while( count < adjudicateLossPlies ) {
7770 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7773 score = -score; /* Flip score for winning side */
7776 if( score > adjudicateLossThreshold ) {
7783 if( count >= adjudicateLossPlies ) {
7784 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7786 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7787 "Xboard adjudication",
7794 if(Adjudicate(cps)) {
7795 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7796 return; // [HGM] adjudicate: for all automatic game ends
7800 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7802 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7803 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7805 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7807 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7809 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7810 char buf[3*MSG_SIZ];
7812 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7813 programStats.score / 100.,
7815 programStats.time / 100.,
7816 (unsigned int)programStats.nodes,
7817 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7818 programStats.movelist);
7820 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7825 /* [AS] Clear stats for next move */
7826 ClearProgramStats();
7827 thinkOutput[0] = NULLCHAR;
7828 hiddenThinkOutputState = 0;
7831 if (gameMode == TwoMachinesPlay) {
7832 /* [HGM] relaying draw offers moved to after reception of move */
7833 /* and interpreting offer as claim if it brings draw condition */
7834 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7835 SendToProgram("draw\n", cps->other);
7837 if (cps->other->sendTime) {
7838 SendTimeRemaining(cps->other,
7839 cps->other->twoMachinesColor[0] == 'w');
7841 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7842 if (firstMove && !bookHit) {
7844 if (cps->other->useColors) {
7845 SendToProgram(cps->other->twoMachinesColor, cps->other);
7847 SendToProgram("go\n", cps->other);
7849 cps->other->maybeThinking = TRUE;
7852 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7854 if (!pausing && appData.ringBellAfterMoves) {
7859 * Reenable menu items that were disabled while
7860 * machine was thinking
7862 if (gameMode != TwoMachinesPlay)
7863 SetUserThinkingEnables();
7865 // [HGM] book: after book hit opponent has received move and is now in force mode
7866 // force the book reply into it, and then fake that it outputted this move by jumping
7867 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7869 static char bookMove[MSG_SIZ]; // a bit generous?
7871 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7872 strcat(bookMove, bookHit);
7875 programStats.nodes = programStats.depth = programStats.time =
7876 programStats.score = programStats.got_only_move = 0;
7877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7879 if(cps->lastPing != cps->lastPong) {
7880 savedMessage = message; // args for deferred call
7882 ScheduleDelayedEvent(DeferredBookMove, 10);
7891 /* Set special modes for chess engines. Later something general
7892 * could be added here; for now there is just one kludge feature,
7893 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7894 * when "xboard" is given as an interactive command.
7896 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7897 cps->useSigint = FALSE;
7898 cps->useSigterm = FALSE;
7900 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7901 ParseFeatures(message+8, cps);
7902 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7905 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7906 int dummy, s=6; char buf[MSG_SIZ];
7907 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7908 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7909 ParseFEN(boards[0], &dummy, message+s);
7910 DrawPosition(TRUE, boards[0]);
7911 startedFromSetupPosition = TRUE;
7914 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7915 * want this, I was asked to put it in, and obliged.
7917 if (!strncmp(message, "setboard ", 9)) {
7918 Board initial_position;
7920 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7922 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7923 DisplayError(_("Bad FEN received from engine"), 0);
7927 CopyBoard(boards[0], initial_position);
7928 initialRulePlies = FENrulePlies;
7929 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7930 else gameMode = MachinePlaysBlack;
7931 DrawPosition(FALSE, boards[currentMove]);
7937 * Look for communication commands
7939 if (!strncmp(message, "telluser ", 9)) {
7940 if(message[9] == '\\' && message[10] == '\\')
7941 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7942 DisplayNote(message + 9);
7945 if (!strncmp(message, "tellusererror ", 14)) {
7947 if(message[14] == '\\' && message[15] == '\\')
7948 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7949 DisplayError(message + 14, 0);
7952 if (!strncmp(message, "tellopponent ", 13)) {
7953 if (appData.icsActive) {
7955 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7959 DisplayNote(message + 13);
7963 if (!strncmp(message, "tellothers ", 11)) {
7964 if (appData.icsActive) {
7966 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7972 if (!strncmp(message, "tellall ", 8)) {
7973 if (appData.icsActive) {
7975 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7979 DisplayNote(message + 8);
7983 if (strncmp(message, "warning", 7) == 0) {
7984 /* Undocumented feature, use tellusererror in new code */
7985 DisplayError(message, 0);
7988 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7989 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7990 strcat(realname, " query");
7991 AskQuestion(realname, buf2, buf1, cps->pr);
7994 /* Commands from the engine directly to ICS. We don't allow these to be
7995 * sent until we are logged on. Crafty kibitzes have been known to
7996 * interfere with the login process.
7999 if (!strncmp(message, "tellics ", 8)) {
8000 SendToICS(message + 8);
8004 if (!strncmp(message, "tellicsnoalias ", 15)) {
8005 SendToICS(ics_prefix);
8006 SendToICS(message + 15);
8010 /* The following are for backward compatibility only */
8011 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8012 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8013 SendToICS(ics_prefix);
8019 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8023 * If the move is illegal, cancel it and redraw the board.
8024 * Also deal with other error cases. Matching is rather loose
8025 * here to accommodate engines written before the spec.
8027 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8028 strncmp(message, "Error", 5) == 0) {
8029 if (StrStr(message, "name") ||
8030 StrStr(message, "rating") || StrStr(message, "?") ||
8031 StrStr(message, "result") || StrStr(message, "board") ||
8032 StrStr(message, "bk") || StrStr(message, "computer") ||
8033 StrStr(message, "variant") || StrStr(message, "hint") ||
8034 StrStr(message, "random") || StrStr(message, "depth") ||
8035 StrStr(message, "accepted")) {
8038 if (StrStr(message, "protover")) {
8039 /* Program is responding to input, so it's apparently done
8040 initializing, and this error message indicates it is
8041 protocol version 1. So we don't need to wait any longer
8042 for it to initialize and send feature commands. */
8043 FeatureDone(cps, 1);
8044 cps->protocolVersion = 1;
8047 cps->maybeThinking = FALSE;
8049 if (StrStr(message, "draw")) {
8050 /* Program doesn't have "draw" command */
8051 cps->sendDrawOffers = 0;
8054 if (cps->sendTime != 1 &&
8055 (StrStr(message, "time") || StrStr(message, "otim"))) {
8056 /* Program apparently doesn't have "time" or "otim" command */
8060 if (StrStr(message, "analyze")) {
8061 cps->analysisSupport = FALSE;
8062 cps->analyzing = FALSE;
8064 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8065 DisplayError(buf2, 0);
8068 if (StrStr(message, "(no matching move)st")) {
8069 /* Special kludge for GNU Chess 4 only */
8070 cps->stKludge = TRUE;
8071 SendTimeControl(cps, movesPerSession, timeControl,
8072 timeIncrement, appData.searchDepth,
8076 if (StrStr(message, "(no matching move)sd")) {
8077 /* Special kludge for GNU Chess 4 only */
8078 cps->sdKludge = TRUE;
8079 SendTimeControl(cps, movesPerSession, timeControl,
8080 timeIncrement, appData.searchDepth,
8084 if (!StrStr(message, "llegal")) {
8087 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8088 gameMode == IcsIdle) return;
8089 if (forwardMostMove <= backwardMostMove) return;
8090 if (pausing) PauseEvent();
8091 if(appData.forceIllegal) {
8092 // [HGM] illegal: machine refused move; force position after move into it
8093 SendToProgram("force\n", cps);
8094 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8095 // we have a real problem now, as SendBoard will use the a2a3 kludge
8096 // when black is to move, while there might be nothing on a2 or black
8097 // might already have the move. So send the board as if white has the move.
8098 // But first we must change the stm of the engine, as it refused the last move
8099 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8100 if(WhiteOnMove(forwardMostMove)) {
8101 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8102 SendBoard(cps, forwardMostMove); // kludgeless board
8104 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8105 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8106 SendBoard(cps, forwardMostMove+1); // kludgeless board
8108 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8109 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8110 gameMode == TwoMachinesPlay)
8111 SendToProgram("go\n", cps);
8114 if (gameMode == PlayFromGameFile) {
8115 /* Stop reading this game file */
8116 gameMode = EditGame;
8119 /* [HGM] illegal-move claim should forfeit game when Xboard */
8120 /* only passes fully legal moves */
8121 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8122 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8123 "False illegal-move claim", GE_XBOARD );
8124 return; // do not take back move we tested as valid
8126 currentMove = forwardMostMove-1;
8127 DisplayMove(currentMove-1); /* before DisplayMoveError */
8128 SwitchClocks(forwardMostMove-1); // [HGM] race
8129 DisplayBothClocks();
8130 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8131 parseList[currentMove], _(cps->which));
8132 DisplayMoveError(buf1);
8133 DrawPosition(FALSE, boards[currentMove]);
8136 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8137 /* Program has a broken "time" command that
8138 outputs a string not ending in newline.
8144 * If chess program startup fails, exit with an error message.
8145 * Attempts to recover here are futile.
8147 if ((StrStr(message, "unknown host") != NULL)
8148 || (StrStr(message, "No remote directory") != NULL)
8149 || (StrStr(message, "not found") != NULL)
8150 || (StrStr(message, "No such file") != NULL)
8151 || (StrStr(message, "can't alloc") != NULL)
8152 || (StrStr(message, "Permission denied") != NULL)) {
8154 cps->maybeThinking = FALSE;
8155 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8156 _(cps->which), cps->program, cps->host, message);
8157 RemoveInputSource(cps->isr);
8158 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8159 if(cps == &first) appData.noChessProgram = TRUE;
8160 DisplayError(buf1, 0);
8166 * Look for hint output
8168 if (sscanf(message, "Hint: %s", buf1) == 1) {
8169 if (cps == &first && hintRequested) {
8170 hintRequested = FALSE;
8171 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8172 &fromX, &fromY, &toX, &toY, &promoChar)) {
8173 (void) CoordsToAlgebraic(boards[forwardMostMove],
8174 PosFlags(forwardMostMove),
8175 fromY, fromX, toY, toX, promoChar, buf1);
8176 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8177 DisplayInformation(buf2);
8179 /* Hint move could not be parsed!? */
8180 snprintf(buf2, sizeof(buf2),
8181 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8182 buf1, _(cps->which));
8183 DisplayError(buf2, 0);
8186 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8192 * Ignore other messages if game is not in progress
8194 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8195 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8198 * look for win, lose, draw, or draw offer
8200 if (strncmp(message, "1-0", 3) == 0) {
8201 char *p, *q, *r = "";
8202 p = strchr(message, '{');
8210 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8212 } else if (strncmp(message, "0-1", 3) == 0) {
8213 char *p, *q, *r = "";
8214 p = strchr(message, '{');
8222 /* Kludge for Arasan 4.1 bug */
8223 if (strcmp(r, "Black resigns") == 0) {
8224 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8227 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8229 } else if (strncmp(message, "1/2", 3) == 0) {
8230 char *p, *q, *r = "";
8231 p = strchr(message, '{');
8240 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8243 } else if (strncmp(message, "White resign", 12) == 0) {
8244 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8246 } else if (strncmp(message, "Black resign", 12) == 0) {
8247 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8249 } else if (strncmp(message, "White matches", 13) == 0 ||
8250 strncmp(message, "Black matches", 13) == 0 ) {
8251 /* [HGM] ignore GNUShogi noises */
8253 } else if (strncmp(message, "White", 5) == 0 &&
8254 message[5] != '(' &&
8255 StrStr(message, "Black") == NULL) {
8256 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8258 } else if (strncmp(message, "Black", 5) == 0 &&
8259 message[5] != '(') {
8260 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8262 } else if (strcmp(message, "resign") == 0 ||
8263 strcmp(message, "computer resigns") == 0) {
8265 case MachinePlaysBlack:
8266 case IcsPlayingBlack:
8267 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8269 case MachinePlaysWhite:
8270 case IcsPlayingWhite:
8271 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8273 case TwoMachinesPlay:
8274 if (cps->twoMachinesColor[0] == 'w')
8275 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8277 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8284 } else if (strncmp(message, "opponent mates", 14) == 0) {
8286 case MachinePlaysBlack:
8287 case IcsPlayingBlack:
8288 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8290 case MachinePlaysWhite:
8291 case IcsPlayingWhite:
8292 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8294 case TwoMachinesPlay:
8295 if (cps->twoMachinesColor[0] == 'w')
8296 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8298 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8305 } else if (strncmp(message, "computer mates", 14) == 0) {
8307 case MachinePlaysBlack:
8308 case IcsPlayingBlack:
8309 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8311 case MachinePlaysWhite:
8312 case IcsPlayingWhite:
8313 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8315 case TwoMachinesPlay:
8316 if (cps->twoMachinesColor[0] == 'w')
8317 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8319 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8326 } else if (strncmp(message, "checkmate", 9) == 0) {
8327 if (WhiteOnMove(forwardMostMove)) {
8328 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8330 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8333 } else if (strstr(message, "Draw") != NULL ||
8334 strstr(message, "game is a draw") != NULL) {
8335 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8337 } else if (strstr(message, "offer") != NULL &&
8338 strstr(message, "draw") != NULL) {
8340 if (appData.zippyPlay && first.initDone) {
8341 /* Relay offer to ICS */
8342 SendToICS(ics_prefix);
8343 SendToICS("draw\n");
8346 cps->offeredDraw = 2; /* valid until this engine moves twice */
8347 if (gameMode == TwoMachinesPlay) {
8348 if (cps->other->offeredDraw) {
8349 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8350 /* [HGM] in two-machine mode we delay relaying draw offer */
8351 /* until after we also have move, to see if it is really claim */
8353 } else if (gameMode == MachinePlaysWhite ||
8354 gameMode == MachinePlaysBlack) {
8355 if (userOfferedDraw) {
8356 DisplayInformation(_("Machine accepts your draw offer"));
8357 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8359 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8366 * Look for thinking output
8368 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8369 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8371 int plylev, mvleft, mvtot, curscore, time;
8372 char mvname[MOVE_LEN];
8376 int prefixHint = FALSE;
8377 mvname[0] = NULLCHAR;
8380 case MachinePlaysBlack:
8381 case IcsPlayingBlack:
8382 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8384 case MachinePlaysWhite:
8385 case IcsPlayingWhite:
8386 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8391 case IcsObserving: /* [DM] icsEngineAnalyze */
8392 if (!appData.icsEngineAnalyze) ignore = TRUE;
8394 case TwoMachinesPlay:
8395 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8405 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8407 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8408 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8410 if (plyext != ' ' && plyext != '\t') {
8414 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8415 if( cps->scoreIsAbsolute &&
8416 ( gameMode == MachinePlaysBlack ||
8417 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8418 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8419 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8420 !WhiteOnMove(currentMove)
8423 curscore = -curscore;
8427 tempStats.depth = plylev;
8428 tempStats.nodes = nodes;
8429 tempStats.time = time;
8430 tempStats.score = curscore;
8431 tempStats.got_only_move = 0;
8433 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8436 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8437 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8438 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8439 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8440 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8441 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8442 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8443 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8446 /* Buffer overflow protection */
8447 if (buf1[0] != NULLCHAR) {
8448 if (strlen(buf1) >= sizeof(tempStats.movelist)
8449 && appData.debugMode) {
8451 "PV is too long; using the first %u bytes.\n",
8452 (unsigned) sizeof(tempStats.movelist) - 1);
8455 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8457 sprintf(tempStats.movelist, " no PV\n");
8460 if (tempStats.seen_stat) {
8461 tempStats.ok_to_send = 1;
8464 if (strchr(tempStats.movelist, '(') != NULL) {
8465 tempStats.line_is_book = 1;
8466 tempStats.nr_moves = 0;
8467 tempStats.moves_left = 0;
8469 tempStats.line_is_book = 0;
8472 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8473 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8475 SendProgramStatsToFrontend( cps, &tempStats );
8478 [AS] Protect the thinkOutput buffer from overflow... this
8479 is only useful if buf1 hasn't overflowed first!
8481 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8483 (gameMode == TwoMachinesPlay ?
8484 ToUpper(cps->twoMachinesColor[0]) : ' '),
8485 ((double) curscore) / 100.0,
8486 prefixHint ? lastHint : "",
8487 prefixHint ? " " : "" );
8489 if( buf1[0] != NULLCHAR ) {
8490 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8492 if( strlen(buf1) > max_len ) {
8493 if( appData.debugMode) {
8494 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8496 buf1[max_len+1] = '\0';
8499 strcat( thinkOutput, buf1 );
8502 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8503 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8504 DisplayMove(currentMove - 1);
8508 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8509 /* crafty (9.25+) says "(only move) <move>"
8510 * if there is only 1 legal move
8512 sscanf(p, "(only move) %s", buf1);
8513 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8514 sprintf(programStats.movelist, "%s (only move)", buf1);
8515 programStats.depth = 1;
8516 programStats.nr_moves = 1;
8517 programStats.moves_left = 1;
8518 programStats.nodes = 1;
8519 programStats.time = 1;
8520 programStats.got_only_move = 1;
8522 /* Not really, but we also use this member to
8523 mean "line isn't going to change" (Crafty
8524 isn't searching, so stats won't change) */
8525 programStats.line_is_book = 1;
8527 SendProgramStatsToFrontend( cps, &programStats );
8529 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8530 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8531 DisplayMove(currentMove - 1);
8534 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8535 &time, &nodes, &plylev, &mvleft,
8536 &mvtot, mvname) >= 5) {
8537 /* The stat01: line is from Crafty (9.29+) in response
8538 to the "." command */
8539 programStats.seen_stat = 1;
8540 cps->maybeThinking = TRUE;
8542 if (programStats.got_only_move || !appData.periodicUpdates)
8545 programStats.depth = plylev;
8546 programStats.time = time;
8547 programStats.nodes = nodes;
8548 programStats.moves_left = mvleft;
8549 programStats.nr_moves = mvtot;
8550 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8551 programStats.ok_to_send = 1;
8552 programStats.movelist[0] = '\0';
8554 SendProgramStatsToFrontend( cps, &programStats );
8558 } else if (strncmp(message,"++",2) == 0) {
8559 /* Crafty 9.29+ outputs this */
8560 programStats.got_fail = 2;
8563 } else if (strncmp(message,"--",2) == 0) {
8564 /* Crafty 9.29+ outputs this */
8565 programStats.got_fail = 1;
8568 } else if (thinkOutput[0] != NULLCHAR &&
8569 strncmp(message, " ", 4) == 0) {
8570 unsigned message_len;
8573 while (*p && *p == ' ') p++;
8575 message_len = strlen( p );
8577 /* [AS] Avoid buffer overflow */
8578 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8579 strcat(thinkOutput, " ");
8580 strcat(thinkOutput, p);
8583 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8584 strcat(programStats.movelist, " ");
8585 strcat(programStats.movelist, p);
8588 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8589 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8590 DisplayMove(currentMove - 1);
8598 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8599 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8601 ChessProgramStats cpstats;
8603 if (plyext != ' ' && plyext != '\t') {
8607 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8608 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8609 curscore = -curscore;
8612 cpstats.depth = plylev;
8613 cpstats.nodes = nodes;
8614 cpstats.time = time;
8615 cpstats.score = curscore;
8616 cpstats.got_only_move = 0;
8617 cpstats.movelist[0] = '\0';
8619 if (buf1[0] != NULLCHAR) {
8620 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8623 cpstats.ok_to_send = 0;
8624 cpstats.line_is_book = 0;
8625 cpstats.nr_moves = 0;
8626 cpstats.moves_left = 0;
8628 SendProgramStatsToFrontend( cps, &cpstats );
8635 /* Parse a game score from the character string "game", and
8636 record it as the history of the current game. The game
8637 score is NOT assumed to start from the standard position.
8638 The display is not updated in any way.
8641 ParseGameHistory(game)
8645 int fromX, fromY, toX, toY, boardIndex;
8650 if (appData.debugMode)
8651 fprintf(debugFP, "Parsing game history: %s\n", game);
8653 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8654 gameInfo.site = StrSave(appData.icsHost);
8655 gameInfo.date = PGNDate();
8656 gameInfo.round = StrSave("-");
8658 /* Parse out names of players */
8659 while (*game == ' ') game++;
8661 while (*game != ' ') *p++ = *game++;
8663 gameInfo.white = StrSave(buf);
8664 while (*game == ' ') game++;
8666 while (*game != ' ' && *game != '\n') *p++ = *game++;
8668 gameInfo.black = StrSave(buf);
8671 boardIndex = blackPlaysFirst ? 1 : 0;
8674 yyboardindex = boardIndex;
8675 moveType = (ChessMove) Myylex();
8677 case IllegalMove: /* maybe suicide chess, etc. */
8678 if (appData.debugMode) {
8679 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8680 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8681 setbuf(debugFP, NULL);
8683 case WhitePromotion:
8684 case BlackPromotion:
8685 case WhiteNonPromotion:
8686 case BlackNonPromotion:
8688 case WhiteCapturesEnPassant:
8689 case BlackCapturesEnPassant:
8690 case WhiteKingSideCastle:
8691 case WhiteQueenSideCastle:
8692 case BlackKingSideCastle:
8693 case BlackQueenSideCastle:
8694 case WhiteKingSideCastleWild:
8695 case WhiteQueenSideCastleWild:
8696 case BlackKingSideCastleWild:
8697 case BlackQueenSideCastleWild:
8699 case WhiteHSideCastleFR:
8700 case WhiteASideCastleFR:
8701 case BlackHSideCastleFR:
8702 case BlackASideCastleFR:
8704 fromX = currentMoveString[0] - AAA;
8705 fromY = currentMoveString[1] - ONE;
8706 toX = currentMoveString[2] - AAA;
8707 toY = currentMoveString[3] - ONE;
8708 promoChar = currentMoveString[4];
8712 fromX = moveType == WhiteDrop ?
8713 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8714 (int) CharToPiece(ToLower(currentMoveString[0]));
8716 toX = currentMoveString[2] - AAA;
8717 toY = currentMoveString[3] - ONE;
8718 promoChar = NULLCHAR;
8722 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8723 if (appData.debugMode) {
8724 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8725 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8726 setbuf(debugFP, NULL);
8728 DisplayError(buf, 0);
8730 case ImpossibleMove:
8732 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8733 if (appData.debugMode) {
8734 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8735 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8736 setbuf(debugFP, NULL);
8738 DisplayError(buf, 0);
8741 if (boardIndex < backwardMostMove) {
8742 /* Oops, gap. How did that happen? */
8743 DisplayError(_("Gap in move list"), 0);
8746 backwardMostMove = blackPlaysFirst ? 1 : 0;
8747 if (boardIndex > forwardMostMove) {
8748 forwardMostMove = boardIndex;
8752 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8753 strcat(parseList[boardIndex-1], " ");
8754 strcat(parseList[boardIndex-1], yy_text);
8766 case GameUnfinished:
8767 if (gameMode == IcsExamining) {
8768 if (boardIndex < backwardMostMove) {
8769 /* Oops, gap. How did that happen? */
8772 backwardMostMove = blackPlaysFirst ? 1 : 0;
8775 gameInfo.result = moveType;
8776 p = strchr(yy_text, '{');
8777 if (p == NULL) p = strchr(yy_text, '(');
8780 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8782 q = strchr(p, *p == '{' ? '}' : ')');
8783 if (q != NULL) *q = NULLCHAR;
8786 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8787 gameInfo.resultDetails = StrSave(p);
8790 if (boardIndex >= forwardMostMove &&
8791 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8792 backwardMostMove = blackPlaysFirst ? 1 : 0;
8795 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8796 fromY, fromX, toY, toX, promoChar,
8797 parseList[boardIndex]);
8798 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8799 /* currentMoveString is set as a side-effect of yylex */
8800 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8801 strcat(moveList[boardIndex], "\n");
8803 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8804 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8810 if(gameInfo.variant != VariantShogi)
8811 strcat(parseList[boardIndex - 1], "+");
8815 strcat(parseList[boardIndex - 1], "#");
8822 /* Apply a move to the given board */
8824 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8825 int fromX, fromY, toX, toY;
8829 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8830 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8832 /* [HGM] compute & store e.p. status and castling rights for new position */
8833 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8835 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8836 oldEP = (signed char)board[EP_STATUS];
8837 board[EP_STATUS] = EP_NONE;
8839 if( board[toY][toX] != EmptySquare )
8840 board[EP_STATUS] = EP_CAPTURE;
8842 if (fromY == DROP_RANK) {
8844 piece = board[toY][toX] = (ChessSquare) fromX;
8848 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8849 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8850 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8852 if( board[fromY][fromX] == WhitePawn ) {
8853 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8854 board[EP_STATUS] = EP_PAWN_MOVE;
8856 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8857 gameInfo.variant != VariantBerolina || toX < fromX)
8858 board[EP_STATUS] = toX | berolina;
8859 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8860 gameInfo.variant != VariantBerolina || toX > fromX)
8861 board[EP_STATUS] = toX;
8864 if( board[fromY][fromX] == BlackPawn ) {
8865 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8866 board[EP_STATUS] = EP_PAWN_MOVE;
8867 if( toY-fromY== -2) {
8868 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8869 gameInfo.variant != VariantBerolina || toX < fromX)
8870 board[EP_STATUS] = toX | berolina;
8871 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8872 gameInfo.variant != VariantBerolina || toX > fromX)
8873 board[EP_STATUS] = toX;
8877 for(i=0; i<nrCastlingRights; i++) {
8878 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8879 board[CASTLING][i] == toX && castlingRank[i] == toY
8880 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8883 if (fromX == toX && fromY == toY) return;
8885 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8886 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8887 if(gameInfo.variant == VariantKnightmate)
8888 king += (int) WhiteUnicorn - (int) WhiteKing;
8890 /* Code added by Tord: */
8891 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8892 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8893 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8894 board[fromY][fromX] = EmptySquare;
8895 board[toY][toX] = EmptySquare;
8896 if((toX > fromX) != (piece == WhiteRook)) {
8897 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8899 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8901 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8902 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8903 board[fromY][fromX] = EmptySquare;
8904 board[toY][toX] = EmptySquare;
8905 if((toX > fromX) != (piece == BlackRook)) {
8906 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8908 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8910 /* End of code added by Tord */
8912 } else if (board[fromY][fromX] == king
8913 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8914 && toY == fromY && toX > fromX+1) {
8915 board[fromY][fromX] = EmptySquare;
8916 board[toY][toX] = king;
8917 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8918 board[fromY][BOARD_RGHT-1] = EmptySquare;
8919 } else if (board[fromY][fromX] == king
8920 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8921 && toY == fromY && toX < fromX-1) {
8922 board[fromY][fromX] = EmptySquare;
8923 board[toY][toX] = king;
8924 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8925 board[fromY][BOARD_LEFT] = EmptySquare;
8926 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8927 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8928 && toY >= BOARD_HEIGHT-promoRank
8930 /* white pawn promotion */
8931 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8932 if (board[toY][toX] == EmptySquare) {
8933 board[toY][toX] = WhiteQueen;
8935 if(gameInfo.variant==VariantBughouse ||
8936 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8937 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8938 board[fromY][fromX] = EmptySquare;
8939 } else if ((fromY == BOARD_HEIGHT-4)
8941 && gameInfo.variant != VariantXiangqi
8942 && gameInfo.variant != VariantBerolina
8943 && (board[fromY][fromX] == WhitePawn)
8944 && (board[toY][toX] == EmptySquare)) {
8945 board[fromY][fromX] = EmptySquare;
8946 board[toY][toX] = WhitePawn;
8947 captured = board[toY - 1][toX];
8948 board[toY - 1][toX] = EmptySquare;
8949 } else if ((fromY == BOARD_HEIGHT-4)
8951 && gameInfo.variant == VariantBerolina
8952 && (board[fromY][fromX] == WhitePawn)
8953 && (board[toY][toX] == EmptySquare)) {
8954 board[fromY][fromX] = EmptySquare;
8955 board[toY][toX] = WhitePawn;
8956 if(oldEP & EP_BEROLIN_A) {
8957 captured = board[fromY][fromX-1];
8958 board[fromY][fromX-1] = EmptySquare;
8959 }else{ captured = board[fromY][fromX+1];
8960 board[fromY][fromX+1] = EmptySquare;
8962 } else if (board[fromY][fromX] == king
8963 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8964 && toY == fromY && toX > fromX+1) {
8965 board[fromY][fromX] = EmptySquare;
8966 board[toY][toX] = king;
8967 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8968 board[fromY][BOARD_RGHT-1] = EmptySquare;
8969 } else if (board[fromY][fromX] == king
8970 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8971 && toY == fromY && toX < fromX-1) {
8972 board[fromY][fromX] = EmptySquare;
8973 board[toY][toX] = king;
8974 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8975 board[fromY][BOARD_LEFT] = EmptySquare;
8976 } else if (fromY == 7 && fromX == 3
8977 && board[fromY][fromX] == BlackKing
8978 && toY == 7 && toX == 5) {
8979 board[fromY][fromX] = EmptySquare;
8980 board[toY][toX] = BlackKing;
8981 board[fromY][7] = EmptySquare;
8982 board[toY][4] = BlackRook;
8983 } else if (fromY == 7 && fromX == 3
8984 && board[fromY][fromX] == BlackKing
8985 && toY == 7 && toX == 1) {
8986 board[fromY][fromX] = EmptySquare;
8987 board[toY][toX] = BlackKing;
8988 board[fromY][0] = EmptySquare;
8989 board[toY][2] = BlackRook;
8990 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8991 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8994 /* black pawn promotion */
8995 board[toY][toX] = CharToPiece(ToLower(promoChar));
8996 if (board[toY][toX] == EmptySquare) {
8997 board[toY][toX] = BlackQueen;
8999 if(gameInfo.variant==VariantBughouse ||
9000 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9001 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9002 board[fromY][fromX] = EmptySquare;
9003 } else if ((fromY == 3)
9005 && gameInfo.variant != VariantXiangqi
9006 && gameInfo.variant != VariantBerolina
9007 && (board[fromY][fromX] == BlackPawn)
9008 && (board[toY][toX] == EmptySquare)) {
9009 board[fromY][fromX] = EmptySquare;
9010 board[toY][toX] = BlackPawn;
9011 captured = board[toY + 1][toX];
9012 board[toY + 1][toX] = EmptySquare;
9013 } else if ((fromY == 3)
9015 && gameInfo.variant == VariantBerolina
9016 && (board[fromY][fromX] == BlackPawn)
9017 && (board[toY][toX] == EmptySquare)) {
9018 board[fromY][fromX] = EmptySquare;
9019 board[toY][toX] = BlackPawn;
9020 if(oldEP & EP_BEROLIN_A) {
9021 captured = board[fromY][fromX-1];
9022 board[fromY][fromX-1] = EmptySquare;
9023 }else{ captured = board[fromY][fromX+1];
9024 board[fromY][fromX+1] = EmptySquare;
9027 board[toY][toX] = board[fromY][fromX];
9028 board[fromY][fromX] = EmptySquare;
9032 if (gameInfo.holdingsWidth != 0) {
9034 /* !!A lot more code needs to be written to support holdings */
9035 /* [HGM] OK, so I have written it. Holdings are stored in the */
9036 /* penultimate board files, so they are automaticlly stored */
9037 /* in the game history. */
9038 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9039 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9040 /* Delete from holdings, by decreasing count */
9041 /* and erasing image if necessary */
9042 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9043 if(p < (int) BlackPawn) { /* white drop */
9044 p -= (int)WhitePawn;
9045 p = PieceToNumber((ChessSquare)p);
9046 if(p >= gameInfo.holdingsSize) p = 0;
9047 if(--board[p][BOARD_WIDTH-2] <= 0)
9048 board[p][BOARD_WIDTH-1] = EmptySquare;
9049 if((int)board[p][BOARD_WIDTH-2] < 0)
9050 board[p][BOARD_WIDTH-2] = 0;
9051 } else { /* black drop */
9052 p -= (int)BlackPawn;
9053 p = PieceToNumber((ChessSquare)p);
9054 if(p >= gameInfo.holdingsSize) p = 0;
9055 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9056 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9057 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9058 board[BOARD_HEIGHT-1-p][1] = 0;
9061 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9062 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9063 /* [HGM] holdings: Add to holdings, if holdings exist */
9064 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9065 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9066 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9069 if (p >= (int) BlackPawn) {
9070 p -= (int)BlackPawn;
9071 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9072 /* in Shogi restore piece to its original first */
9073 captured = (ChessSquare) (DEMOTED captured);
9076 p = PieceToNumber((ChessSquare)p);
9077 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9078 board[p][BOARD_WIDTH-2]++;
9079 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9081 p -= (int)WhitePawn;
9082 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9083 captured = (ChessSquare) (DEMOTED captured);
9086 p = PieceToNumber((ChessSquare)p);
9087 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9088 board[BOARD_HEIGHT-1-p][1]++;
9089 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9092 } else if (gameInfo.variant == VariantAtomic) {
9093 if (captured != EmptySquare) {
9095 for (y = toY-1; y <= toY+1; y++) {
9096 for (x = toX-1; x <= toX+1; x++) {
9097 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9098 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9099 board[y][x] = EmptySquare;
9103 board[toY][toX] = EmptySquare;
9106 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9107 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9109 if(promoChar == '+') {
9110 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9111 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9112 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9113 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9115 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9116 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9117 // [HGM] superchess: take promotion piece out of holdings
9118 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9119 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9120 if(!--board[k][BOARD_WIDTH-2])
9121 board[k][BOARD_WIDTH-1] = EmptySquare;
9123 if(!--board[BOARD_HEIGHT-1-k][1])
9124 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9130 /* Updates forwardMostMove */
9132 MakeMove(fromX, fromY, toX, toY, promoChar)
9133 int fromX, fromY, toX, toY;
9136 // forwardMostMove++; // [HGM] bare: moved downstream
9138 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9139 int timeLeft; static int lastLoadFlag=0; int king, piece;
9140 piece = boards[forwardMostMove][fromY][fromX];
9141 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9142 if(gameInfo.variant == VariantKnightmate)
9143 king += (int) WhiteUnicorn - (int) WhiteKing;
9144 if(forwardMostMove == 0) {
9146 fprintf(serverMoves, "%s;", second.tidy);
9147 fprintf(serverMoves, "%s;", first.tidy);
9148 if(!blackPlaysFirst)
9149 fprintf(serverMoves, "%s;", second.tidy);
9150 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9151 lastLoadFlag = loadFlag;
9153 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9154 // print castling suffix
9155 if( toY == fromY && piece == king ) {
9157 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9159 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9162 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9163 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9164 boards[forwardMostMove][toY][toX] == EmptySquare
9165 && fromX != toX && fromY != toY)
9166 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9168 if(promoChar != NULLCHAR)
9169 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9171 fprintf(serverMoves, "/%d/%d",
9172 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9173 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9174 else timeLeft = blackTimeRemaining/1000;
9175 fprintf(serverMoves, "/%d", timeLeft);
9177 fflush(serverMoves);
9180 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9181 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9185 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9186 if (commentList[forwardMostMove+1] != NULL) {
9187 free(commentList[forwardMostMove+1]);
9188 commentList[forwardMostMove+1] = NULL;
9190 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9191 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9192 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9193 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9194 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9195 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9196 gameInfo.result = GameUnfinished;
9197 if (gameInfo.resultDetails != NULL) {
9198 free(gameInfo.resultDetails);
9199 gameInfo.resultDetails = NULL;
9201 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9202 moveList[forwardMostMove - 1]);
9203 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9204 PosFlags(forwardMostMove - 1),
9205 fromY, fromX, toY, toX, promoChar,
9206 parseList[forwardMostMove - 1]);
9207 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9213 if(gameInfo.variant != VariantShogi)
9214 strcat(parseList[forwardMostMove - 1], "+");
9218 strcat(parseList[forwardMostMove - 1], "#");
9221 if (appData.debugMode) {
9222 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9227 /* Updates currentMove if not pausing */
9229 ShowMove(fromX, fromY, toX, toY)
9231 int instant = (gameMode == PlayFromGameFile) ?
9232 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9233 if(appData.noGUI) return;
9234 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9236 if (forwardMostMove == currentMove + 1) {
9237 AnimateMove(boards[forwardMostMove - 1],
9238 fromX, fromY, toX, toY);
9240 if (appData.highlightLastMove) {
9241 SetHighlights(fromX, fromY, toX, toY);
9244 currentMove = forwardMostMove;
9247 if (instant) return;
9249 DisplayMove(currentMove - 1);
9250 DrawPosition(FALSE, boards[currentMove]);
9251 DisplayBothClocks();
9252 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9255 void SendEgtPath(ChessProgramState *cps)
9256 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9257 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9259 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9262 char c, *q = name+1, *r, *s;
9264 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9265 while(*p && *p != ',') *q++ = *p++;
9267 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9268 strcmp(name, ",nalimov:") == 0 ) {
9269 // take nalimov path from the menu-changeable option first, if it is defined
9270 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9271 SendToProgram(buf,cps); // send egtbpath command for nalimov
9273 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9274 (s = StrStr(appData.egtFormats, name)) != NULL) {
9275 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9276 s = r = StrStr(s, ":") + 1; // beginning of path info
9277 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9278 c = *r; *r = 0; // temporarily null-terminate path info
9279 *--q = 0; // strip of trailig ':' from name
9280 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9282 SendToProgram(buf,cps); // send egtbpath command for this format
9284 if(*p == ',') p++; // read away comma to position for next format name
9289 InitChessProgram(cps, setup)
9290 ChessProgramState *cps;
9291 int setup; /* [HGM] needed to setup FRC opening position */
9293 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9294 if (appData.noChessProgram) return;
9295 hintRequested = FALSE;
9296 bookRequested = FALSE;
9298 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9299 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9300 if(cps->memSize) { /* [HGM] memory */
9301 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9302 SendToProgram(buf, cps);
9304 SendEgtPath(cps); /* [HGM] EGT */
9305 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9306 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9307 SendToProgram(buf, cps);
9310 SendToProgram(cps->initString, cps);
9311 if (gameInfo.variant != VariantNormal &&
9312 gameInfo.variant != VariantLoadable
9313 /* [HGM] also send variant if board size non-standard */
9314 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9316 char *v = VariantName(gameInfo.variant);
9317 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9318 /* [HGM] in protocol 1 we have to assume all variants valid */
9319 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9320 DisplayFatalError(buf, 0, 1);
9324 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9325 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9326 if( gameInfo.variant == VariantXiangqi )
9327 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9328 if( gameInfo.variant == VariantShogi )
9329 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9330 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9331 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9332 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9333 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9334 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9335 if( gameInfo.variant == VariantCourier )
9336 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9337 if( gameInfo.variant == VariantSuper )
9338 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9339 if( gameInfo.variant == VariantGreat )
9340 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9341 if( gameInfo.variant == VariantSChess )
9342 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9345 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9346 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9347 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9348 if(StrStr(cps->variants, b) == NULL) {
9349 // specific sized variant not known, check if general sizing allowed
9350 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9351 if(StrStr(cps->variants, "boardsize") == NULL) {
9352 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9353 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9354 DisplayFatalError(buf, 0, 1);
9357 /* [HGM] here we really should compare with the maximum supported board size */
9360 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9361 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9362 SendToProgram(buf, cps);
9364 currentlyInitializedVariant = gameInfo.variant;
9366 /* [HGM] send opening position in FRC to first engine */
9368 SendToProgram("force\n", cps);
9370 /* engine is now in force mode! Set flag to wake it up after first move. */
9371 setboardSpoiledMachineBlack = 1;
9375 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9376 SendToProgram(buf, cps);
9378 cps->maybeThinking = FALSE;
9379 cps->offeredDraw = 0;
9380 if (!appData.icsActive) {
9381 SendTimeControl(cps, movesPerSession, timeControl,
9382 timeIncrement, appData.searchDepth,
9385 if (appData.showThinking
9386 // [HGM] thinking: four options require thinking output to be sent
9387 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9389 SendToProgram("post\n", cps);
9391 SendToProgram("hard\n", cps);
9392 if (!appData.ponderNextMove) {
9393 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9394 it without being sure what state we are in first. "hard"
9395 is not a toggle, so that one is OK.
9397 SendToProgram("easy\n", cps);
9400 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9401 SendToProgram(buf, cps);
9403 cps->initDone = TRUE;
9408 StartChessProgram(cps)
9409 ChessProgramState *cps;
9414 if (appData.noChessProgram) return;
9415 cps->initDone = FALSE;
9417 if (strcmp(cps->host, "localhost") == 0) {
9418 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9419 } else if (*appData.remoteShell == NULLCHAR) {
9420 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9422 if (*appData.remoteUser == NULLCHAR) {
9423 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9426 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9427 cps->host, appData.remoteUser, cps->program);
9429 err = StartChildProcess(buf, "", &cps->pr);
9433 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9434 DisplayFatalError(buf, err, 1);
9440 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9441 if (cps->protocolVersion > 1) {
9442 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9443 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9444 cps->comboCnt = 0; // and values of combo boxes
9445 SendToProgram(buf, cps);
9447 SendToProgram("xboard\n", cps);
9452 TwoMachinesEventIfReady P((void))
9454 static int curMess = 0;
9455 if (first.lastPing != first.lastPong) {
9456 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9457 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9460 if (second.lastPing != second.lastPong) {
9461 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9462 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9465 DisplayMessage("", ""); curMess = 0;
9471 CreateTourney(char *name)
9474 if(name[0] == NULLCHAR) return 0;
9475 f = fopen(appData.tourneyFile, "r");
9476 if(f) { // file exists
9477 ParseArgsFromFile(f); // parse it
9479 f = fopen(appData.tourneyFile, "w");
9480 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9481 // create a file with tournament description
9482 fprintf(f, "-participants {%s}\n", appData.participants);
9483 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9484 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9485 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9486 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9487 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9488 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9489 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9490 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9491 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9492 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9493 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9495 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9497 fprintf(f, "-mps %d\n", appData.movesPerSession);
9498 fprintf(f, "-tc %s\n", appData.timeControl);
9499 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9501 fprintf(f, "-results \"\"\n");
9505 appData.noChessProgram = FALSE;
9506 appData.clockMode = TRUE;
9511 #define MAXENGINES 1000
9512 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9514 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9516 char buf[MSG_SIZ], *p, *q;
9520 while(*p && *p != '\n') *q++ = *p++;
9522 if(engineList[i]) free(engineList[i]);
9523 engineList[i] = strdup(buf);
9525 TidyProgramName(engineList[i], "localhost", buf);
9526 if(engineMnemonic[i]) free(engineMnemonic[i]);
9527 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9529 sscanf(q + 8, "%s", buf + strlen(buf));
9532 engineMnemonic[i] = strdup(buf);
9534 if(i > MAXENGINES - 2) break;
9536 engineList[i] = NULL;
9539 // following implemented as macro to avoid type limitations
9540 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9542 void SwapEngines(int n)
9543 { // swap settings for first engine and other engine (so far only some selected options)
9548 SWAP(chessProgram, p)
9550 SWAP(hasOwnBookUCI, h)
9551 SWAP(protocolVersion, h)
9553 SWAP(scoreIsAbsolute, h)
9560 SetPlayer(int player)
9561 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9563 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9564 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9565 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9566 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9568 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9569 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9570 ParseArgsFromString(buf);
9576 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9577 { // determine players from game number
9578 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9580 if(appData.tourneyType == 0) {
9581 roundsPerCycle = (nPlayers - 1) | 1;
9582 pairingsPerRound = nPlayers / 2;
9583 } else if(appData.tourneyType > 0) {
9584 roundsPerCycle = nPlayers - appData.tourneyType;
9585 pairingsPerRound = appData.tourneyType;
9587 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9588 gamesPerCycle = gamesPerRound * roundsPerCycle;
9589 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9590 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9591 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9592 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9593 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9594 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9596 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9597 if(appData.roundSync) *syncInterval = gamesPerRound;
9599 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9601 if(appData.tourneyType == 0) {
9602 if(curPairing == (nPlayers-1)/2 ) {
9603 *whitePlayer = curRound;
9604 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9606 *whitePlayer = curRound - pairingsPerRound + curPairing;
9607 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9608 *blackPlayer = curRound + pairingsPerRound - curPairing;
9609 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9611 } else if(appData.tourneyType > 0) {
9612 *whitePlayer = curPairing;
9613 *blackPlayer = curRound + appData.tourneyType;
9616 // take care of white/black alternation per round.
9617 // For cycles and games this is already taken care of by default, derived from matchGame!
9618 return curRound & 1;
9622 NextTourneyGame(int nr, int *swapColors)
9623 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9625 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9627 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9628 tf = fopen(appData.tourneyFile, "r");
9629 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9630 ParseArgsFromFile(tf); fclose(tf);
9631 InitTimeControls(); // TC might be altered from tourney file
9633 p = appData.participants;
9634 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9635 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9638 p = q = appData.results;
9639 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9640 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9641 DisplayMessage(_("Waiting for other game(s)"),"");
9642 waitingForGame = TRUE;
9643 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9646 waitingForGame = FALSE;
9649 if(first.pr != NoProc) return 1; // engines already loaded
9651 // redefine engines, engine dir, etc.
9652 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9653 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9655 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9656 SwapEngines(1); // and make that valid for second engine by swapping
9657 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9658 InitEngine(&second, 1);
9659 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9665 { // performs game initialization that does not invoke engines, and then tries to start the game
9666 int firstWhite, swapColors = 0;
9667 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9668 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9669 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9670 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9671 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9672 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9673 Reset(FALSE, first.pr != NoProc);
9674 appData.noChessProgram = FALSE;
9675 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9679 void UserAdjudicationEvent( int result )
9681 ChessMove gameResult = GameIsDrawn;
9684 gameResult = WhiteWins;
9686 else if( result < 0 ) {
9687 gameResult = BlackWins;
9690 if( gameMode == TwoMachinesPlay ) {
9691 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9696 // [HGM] save: calculate checksum of game to make games easily identifiable
9697 int StringCheckSum(char *s)
9700 if(s==NULL) return 0;
9701 while(*s) i = i*259 + *s++;
9708 for(i=backwardMostMove; i<forwardMostMove; i++) {
9709 sum += pvInfoList[i].depth;
9710 sum += StringCheckSum(parseList[i]);
9711 sum += StringCheckSum(commentList[i]);
9714 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9715 return sum + StringCheckSum(commentList[i]);
9716 } // end of save patch
9719 GameEnds(result, resultDetails, whosays)
9721 char *resultDetails;
9724 GameMode nextGameMode;
9726 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9728 if(endingGame) return; /* [HGM] crash: forbid recursion */
9730 if(twoBoards) { // [HGM] dual: switch back to one board
9731 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9732 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9734 if (appData.debugMode) {
9735 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9736 result, resultDetails ? resultDetails : "(null)", whosays);
9739 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9741 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9742 /* If we are playing on ICS, the server decides when the
9743 game is over, but the engine can offer to draw, claim
9747 if (appData.zippyPlay && first.initDone) {
9748 if (result == GameIsDrawn) {
9749 /* In case draw still needs to be claimed */
9750 SendToICS(ics_prefix);
9751 SendToICS("draw\n");
9752 } else if (StrCaseStr(resultDetails, "resign")) {
9753 SendToICS(ics_prefix);
9754 SendToICS("resign\n");
9758 endingGame = 0; /* [HGM] crash */
9762 /* If we're loading the game from a file, stop */
9763 if (whosays == GE_FILE) {
9764 (void) StopLoadGameTimer();
9768 /* Cancel draw offers */
9769 first.offeredDraw = second.offeredDraw = 0;
9771 /* If this is an ICS game, only ICS can really say it's done;
9772 if not, anyone can. */
9773 isIcsGame = (gameMode == IcsPlayingWhite ||
9774 gameMode == IcsPlayingBlack ||
9775 gameMode == IcsObserving ||
9776 gameMode == IcsExamining);
9778 if (!isIcsGame || whosays == GE_ICS) {
9779 /* OK -- not an ICS game, or ICS said it was done */
9781 if (!isIcsGame && !appData.noChessProgram)
9782 SetUserThinkingEnables();
9784 /* [HGM] if a machine claims the game end we verify this claim */
9785 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9786 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9788 ChessMove trueResult = (ChessMove) -1;
9790 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9791 first.twoMachinesColor[0] :
9792 second.twoMachinesColor[0] ;
9794 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9795 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9796 /* [HGM] verify: engine mate claims accepted if they were flagged */
9797 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9799 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9800 /* [HGM] verify: engine mate claims accepted if they were flagged */
9801 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9803 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9804 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9807 // now verify win claims, but not in drop games, as we don't understand those yet
9808 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9809 || gameInfo.variant == VariantGreat) &&
9810 (result == WhiteWins && claimer == 'w' ||
9811 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9812 if (appData.debugMode) {
9813 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9814 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9816 if(result != trueResult) {
9817 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9818 result = claimer == 'w' ? BlackWins : WhiteWins;
9819 resultDetails = buf;
9822 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9823 && (forwardMostMove <= backwardMostMove ||
9824 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9825 (claimer=='b')==(forwardMostMove&1))
9827 /* [HGM] verify: draws that were not flagged are false claims */
9828 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9829 result = claimer == 'w' ? BlackWins : WhiteWins;
9830 resultDetails = buf;
9832 /* (Claiming a loss is accepted no questions asked!) */
9834 /* [HGM] bare: don't allow bare King to win */
9835 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9836 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9837 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9838 && result != GameIsDrawn)
9839 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9840 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9841 int p = (signed char)boards[forwardMostMove][i][j] - color;
9842 if(p >= 0 && p <= (int)WhiteKing) k++;
9844 if (appData.debugMode) {
9845 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9846 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9849 result = GameIsDrawn;
9850 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9851 resultDetails = buf;
9857 if(serverMoves != NULL && !loadFlag) { char c = '=';
9858 if(result==WhiteWins) c = '+';
9859 if(result==BlackWins) c = '-';
9860 if(resultDetails != NULL)
9861 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9863 if (resultDetails != NULL) {
9864 gameInfo.result = result;
9865 gameInfo.resultDetails = StrSave(resultDetails);
9867 /* display last move only if game was not loaded from file */
9868 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9869 DisplayMove(currentMove - 1);
9871 if (forwardMostMove != 0) {
9872 if (gameMode != PlayFromGameFile && gameMode != EditGame
9873 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9875 if (*appData.saveGameFile != NULLCHAR) {
9876 SaveGameToFile(appData.saveGameFile, TRUE);
9877 } else if (appData.autoSaveGames) {
9880 if (*appData.savePositionFile != NULLCHAR) {
9881 SavePositionToFile(appData.savePositionFile);
9886 /* Tell program how game ended in case it is learning */
9887 /* [HGM] Moved this to after saving the PGN, just in case */
9888 /* engine died and we got here through time loss. In that */
9889 /* case we will get a fatal error writing the pipe, which */
9890 /* would otherwise lose us the PGN. */
9891 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9892 /* output during GameEnds should never be fatal anymore */
9893 if (gameMode == MachinePlaysWhite ||
9894 gameMode == MachinePlaysBlack ||
9895 gameMode == TwoMachinesPlay ||
9896 gameMode == IcsPlayingWhite ||
9897 gameMode == IcsPlayingBlack ||
9898 gameMode == BeginningOfGame) {
9900 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9902 if (first.pr != NoProc) {
9903 SendToProgram(buf, &first);
9905 if (second.pr != NoProc &&
9906 gameMode == TwoMachinesPlay) {
9907 SendToProgram(buf, &second);
9912 if (appData.icsActive) {
9913 if (appData.quietPlay &&
9914 (gameMode == IcsPlayingWhite ||
9915 gameMode == IcsPlayingBlack)) {
9916 SendToICS(ics_prefix);
9917 SendToICS("set shout 1\n");
9919 nextGameMode = IcsIdle;
9920 ics_user_moved = FALSE;
9921 /* clean up premove. It's ugly when the game has ended and the
9922 * premove highlights are still on the board.
9926 ClearPremoveHighlights();
9927 DrawPosition(FALSE, boards[currentMove]);
9929 if (whosays == GE_ICS) {
9932 if (gameMode == IcsPlayingWhite)
9934 else if(gameMode == IcsPlayingBlack)
9938 if (gameMode == IcsPlayingBlack)
9940 else if(gameMode == IcsPlayingWhite)
9947 PlayIcsUnfinishedSound();
9950 } else if (gameMode == EditGame ||
9951 gameMode == PlayFromGameFile ||
9952 gameMode == AnalyzeMode ||
9953 gameMode == AnalyzeFile) {
9954 nextGameMode = gameMode;
9956 nextGameMode = EndOfGame;
9961 nextGameMode = gameMode;
9964 if (appData.noChessProgram) {
9965 gameMode = nextGameMode;
9967 endingGame = 0; /* [HGM] crash */
9972 /* Put first chess program into idle state */
9973 if (first.pr != NoProc &&
9974 (gameMode == MachinePlaysWhite ||
9975 gameMode == MachinePlaysBlack ||
9976 gameMode == TwoMachinesPlay ||
9977 gameMode == IcsPlayingWhite ||
9978 gameMode == IcsPlayingBlack ||
9979 gameMode == BeginningOfGame)) {
9980 SendToProgram("force\n", &first);
9981 if (first.usePing) {
9983 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9984 SendToProgram(buf, &first);
9987 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9988 /* Kill off first chess program */
9989 if (first.isr != NULL)
9990 RemoveInputSource(first.isr);
9993 if (first.pr != NoProc) {
9995 DoSleep( appData.delayBeforeQuit );
9996 SendToProgram("quit\n", &first);
9997 DoSleep( appData.delayAfterQuit );
9998 DestroyChildProcess(first.pr, first.useSigterm);
10002 if (second.reuse) {
10003 /* Put second chess program into idle state */
10004 if (second.pr != NoProc &&
10005 gameMode == TwoMachinesPlay) {
10006 SendToProgram("force\n", &second);
10007 if (second.usePing) {
10009 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10010 SendToProgram(buf, &second);
10013 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10014 /* Kill off second chess program */
10015 if (second.isr != NULL)
10016 RemoveInputSource(second.isr);
10019 if (second.pr != NoProc) {
10020 DoSleep( appData.delayBeforeQuit );
10021 SendToProgram("quit\n", &second);
10022 DoSleep( appData.delayAfterQuit );
10023 DestroyChildProcess(second.pr, second.useSigterm);
10025 second.pr = NoProc;
10028 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10029 char resChar = '=';
10033 if (first.twoMachinesColor[0] == 'w') {
10036 second.matchWins++;
10041 if (first.twoMachinesColor[0] == 'b') {
10044 second.matchWins++;
10047 case GameUnfinished:
10053 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10054 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10055 ReserveGame(nextGame, resChar); // sets nextGame
10056 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10057 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10059 if (nextGame <= appData.matchGames && !abortMatch) {
10060 gameMode = nextGameMode;
10061 matchGame = nextGame; // this will be overruled in tourney mode!
10062 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10063 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10064 endingGame = 0; /* [HGM] crash */
10067 gameMode = nextGameMode;
10068 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10069 first.tidy, second.tidy,
10070 first.matchWins, second.matchWins,
10071 appData.matchGames - (first.matchWins + second.matchWins));
10072 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10073 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10074 first.twoMachinesColor = "black\n";
10075 second.twoMachinesColor = "white\n";
10077 first.twoMachinesColor = "white\n";
10078 second.twoMachinesColor = "black\n";
10082 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10083 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10085 gameMode = nextGameMode;
10087 endingGame = 0; /* [HGM] crash */
10088 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10089 if(matchMode == TRUE) { // match through command line: exit with or without popup
10091 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10093 } else DisplayFatalError(buf, 0, 0);
10094 } else { // match through menu; just stop, with or without popup
10095 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10097 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10098 } else DisplayNote(buf);
10100 if(ranking) free(ranking);
10104 /* Assumes program was just initialized (initString sent).
10105 Leaves program in force mode. */
10107 FeedMovesToProgram(cps, upto)
10108 ChessProgramState *cps;
10113 if (appData.debugMode)
10114 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10115 startedFromSetupPosition ? "position and " : "",
10116 backwardMostMove, upto, cps->which);
10117 if(currentlyInitializedVariant != gameInfo.variant) {
10119 // [HGM] variantswitch: make engine aware of new variant
10120 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10121 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10122 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10123 SendToProgram(buf, cps);
10124 currentlyInitializedVariant = gameInfo.variant;
10126 SendToProgram("force\n", cps);
10127 if (startedFromSetupPosition) {
10128 SendBoard(cps, backwardMostMove);
10129 if (appData.debugMode) {
10130 fprintf(debugFP, "feedMoves\n");
10133 for (i = backwardMostMove; i < upto; i++) {
10134 SendMoveToProgram(i, cps);
10140 ResurrectChessProgram()
10142 /* The chess program may have exited.
10143 If so, restart it and feed it all the moves made so far. */
10144 static int doInit = 0;
10146 if (appData.noChessProgram) return 1;
10148 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10149 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10150 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10151 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10153 if (first.pr != NoProc) return 1;
10154 StartChessProgram(&first);
10156 InitChessProgram(&first, FALSE);
10157 FeedMovesToProgram(&first, currentMove);
10159 if (!first.sendTime) {
10160 /* can't tell gnuchess what its clock should read,
10161 so we bow to its notion. */
10163 timeRemaining[0][currentMove] = whiteTimeRemaining;
10164 timeRemaining[1][currentMove] = blackTimeRemaining;
10167 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10168 appData.icsEngineAnalyze) && first.analysisSupport) {
10169 SendToProgram("analyze\n", &first);
10170 first.analyzing = TRUE;
10176 * Button procedures
10179 Reset(redraw, init)
10184 if (appData.debugMode) {
10185 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10186 redraw, init, gameMode);
10188 CleanupTail(); // [HGM] vari: delete any stored variations
10189 pausing = pauseExamInvalid = FALSE;
10190 startedFromSetupPosition = blackPlaysFirst = FALSE;
10192 whiteFlag = blackFlag = FALSE;
10193 userOfferedDraw = FALSE;
10194 hintRequested = bookRequested = FALSE;
10195 first.maybeThinking = FALSE;
10196 second.maybeThinking = FALSE;
10197 first.bookSuspend = FALSE; // [HGM] book
10198 second.bookSuspend = FALSE;
10199 thinkOutput[0] = NULLCHAR;
10200 lastHint[0] = NULLCHAR;
10201 ClearGameInfo(&gameInfo);
10202 gameInfo.variant = StringToVariant(appData.variant);
10203 ics_user_moved = ics_clock_paused = FALSE;
10204 ics_getting_history = H_FALSE;
10206 white_holding[0] = black_holding[0] = NULLCHAR;
10207 ClearProgramStats();
10208 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10212 flipView = appData.flipView;
10213 ClearPremoveHighlights();
10214 gotPremove = FALSE;
10215 alarmSounded = FALSE;
10217 GameEnds(EndOfFile, NULL, GE_PLAYER);
10218 if(appData.serverMovesName != NULL) {
10219 /* [HGM] prepare to make moves file for broadcasting */
10220 clock_t t = clock();
10221 if(serverMoves != NULL) fclose(serverMoves);
10222 serverMoves = fopen(appData.serverMovesName, "r");
10223 if(serverMoves != NULL) {
10224 fclose(serverMoves);
10225 /* delay 15 sec before overwriting, so all clients can see end */
10226 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10228 serverMoves = fopen(appData.serverMovesName, "w");
10232 gameMode = BeginningOfGame;
10234 if(appData.icsActive) gameInfo.variant = VariantNormal;
10235 currentMove = forwardMostMove = backwardMostMove = 0;
10236 InitPosition(redraw);
10237 for (i = 0; i < MAX_MOVES; i++) {
10238 if (commentList[i] != NULL) {
10239 free(commentList[i]);
10240 commentList[i] = NULL;
10244 timeRemaining[0][0] = whiteTimeRemaining;
10245 timeRemaining[1][0] = blackTimeRemaining;
10247 if (first.pr == NULL) {
10248 StartChessProgram(&first);
10251 InitChessProgram(&first, startedFromSetupPosition);
10254 DisplayMessage("", "");
10255 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10256 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10263 if (!AutoPlayOneMove())
10265 if (matchMode || appData.timeDelay == 0)
10267 if (appData.timeDelay < 0)
10269 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10278 int fromX, fromY, toX, toY;
10280 if (appData.debugMode) {
10281 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10284 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10287 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10288 pvInfoList[currentMove].depth = programStats.depth;
10289 pvInfoList[currentMove].score = programStats.score;
10290 pvInfoList[currentMove].time = 0;
10291 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10294 if (currentMove >= forwardMostMove) {
10295 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10296 gameMode = EditGame;
10299 /* [AS] Clear current move marker at the end of a game */
10300 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10305 toX = moveList[currentMove][2] - AAA;
10306 toY = moveList[currentMove][3] - ONE;
10308 if (moveList[currentMove][1] == '@') {
10309 if (appData.highlightLastMove) {
10310 SetHighlights(-1, -1, toX, toY);
10313 fromX = moveList[currentMove][0] - AAA;
10314 fromY = moveList[currentMove][1] - ONE;
10316 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10318 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10320 if (appData.highlightLastMove) {
10321 SetHighlights(fromX, fromY, toX, toY);
10324 DisplayMove(currentMove);
10325 SendMoveToProgram(currentMove++, &first);
10326 DisplayBothClocks();
10327 DrawPosition(FALSE, boards[currentMove]);
10328 // [HGM] PV info: always display, routine tests if empty
10329 DisplayComment(currentMove - 1, commentList[currentMove]);
10335 LoadGameOneMove(readAhead)
10336 ChessMove readAhead;
10338 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10339 char promoChar = NULLCHAR;
10340 ChessMove moveType;
10341 char move[MSG_SIZ];
10344 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10345 gameMode != AnalyzeMode && gameMode != Training) {
10350 yyboardindex = forwardMostMove;
10351 if (readAhead != EndOfFile) {
10352 moveType = readAhead;
10354 if (gameFileFP == NULL)
10356 moveType = (ChessMove) Myylex();
10360 switch (moveType) {
10362 if (appData.debugMode)
10363 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10366 /* append the comment but don't display it */
10367 AppendComment(currentMove, p, FALSE);
10370 case WhiteCapturesEnPassant:
10371 case BlackCapturesEnPassant:
10372 case WhitePromotion:
10373 case BlackPromotion:
10374 case WhiteNonPromotion:
10375 case BlackNonPromotion:
10377 case WhiteKingSideCastle:
10378 case WhiteQueenSideCastle:
10379 case BlackKingSideCastle:
10380 case BlackQueenSideCastle:
10381 case WhiteKingSideCastleWild:
10382 case WhiteQueenSideCastleWild:
10383 case BlackKingSideCastleWild:
10384 case BlackQueenSideCastleWild:
10386 case WhiteHSideCastleFR:
10387 case WhiteASideCastleFR:
10388 case BlackHSideCastleFR:
10389 case BlackASideCastleFR:
10391 if (appData.debugMode)
10392 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10393 fromX = currentMoveString[0] - AAA;
10394 fromY = currentMoveString[1] - ONE;
10395 toX = currentMoveString[2] - AAA;
10396 toY = currentMoveString[3] - ONE;
10397 promoChar = currentMoveString[4];
10402 if (appData.debugMode)
10403 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10404 fromX = moveType == WhiteDrop ?
10405 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10406 (int) CharToPiece(ToLower(currentMoveString[0]));
10408 toX = currentMoveString[2] - AAA;
10409 toY = currentMoveString[3] - ONE;
10415 case GameUnfinished:
10416 if (appData.debugMode)
10417 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10418 p = strchr(yy_text, '{');
10419 if (p == NULL) p = strchr(yy_text, '(');
10422 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10424 q = strchr(p, *p == '{' ? '}' : ')');
10425 if (q != NULL) *q = NULLCHAR;
10428 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10429 GameEnds(moveType, p, GE_FILE);
10431 if (cmailMsgLoaded) {
10433 flipView = WhiteOnMove(currentMove);
10434 if (moveType == GameUnfinished) flipView = !flipView;
10435 if (appData.debugMode)
10436 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10441 if (appData.debugMode)
10442 fprintf(debugFP, "Parser hit end of file\n");
10443 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10449 if (WhiteOnMove(currentMove)) {
10450 GameEnds(BlackWins, "Black mates", GE_FILE);
10452 GameEnds(WhiteWins, "White mates", GE_FILE);
10456 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10462 case MoveNumberOne:
10463 if (lastLoadGameStart == GNUChessGame) {
10464 /* GNUChessGames have numbers, but they aren't move numbers */
10465 if (appData.debugMode)
10466 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10467 yy_text, (int) moveType);
10468 return LoadGameOneMove(EndOfFile); /* tail recursion */
10470 /* else fall thru */
10475 /* Reached start of next game in file */
10476 if (appData.debugMode)
10477 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10478 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10484 if (WhiteOnMove(currentMove)) {
10485 GameEnds(BlackWins, "Black mates", GE_FILE);
10487 GameEnds(WhiteWins, "White mates", GE_FILE);
10491 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10497 case PositionDiagram: /* should not happen; ignore */
10498 case ElapsedTime: /* ignore */
10499 case NAG: /* ignore */
10500 if (appData.debugMode)
10501 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10502 yy_text, (int) moveType);
10503 return LoadGameOneMove(EndOfFile); /* tail recursion */
10506 if (appData.testLegality) {
10507 if (appData.debugMode)
10508 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10509 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10510 (forwardMostMove / 2) + 1,
10511 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10512 DisplayError(move, 0);
10515 if (appData.debugMode)
10516 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10517 yy_text, currentMoveString);
10518 fromX = currentMoveString[0] - AAA;
10519 fromY = currentMoveString[1] - ONE;
10520 toX = currentMoveString[2] - AAA;
10521 toY = currentMoveString[3] - ONE;
10522 promoChar = currentMoveString[4];
10526 case AmbiguousMove:
10527 if (appData.debugMode)
10528 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10529 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10530 (forwardMostMove / 2) + 1,
10531 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10532 DisplayError(move, 0);
10537 case ImpossibleMove:
10538 if (appData.debugMode)
10539 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10540 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10541 (forwardMostMove / 2) + 1,
10542 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10543 DisplayError(move, 0);
10549 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10550 DrawPosition(FALSE, boards[currentMove]);
10551 DisplayBothClocks();
10552 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10553 DisplayComment(currentMove - 1, commentList[currentMove]);
10555 (void) StopLoadGameTimer();
10557 cmailOldMove = forwardMostMove;
10560 /* currentMoveString is set as a side-effect of yylex */
10562 thinkOutput[0] = NULLCHAR;
10563 MakeMove(fromX, fromY, toX, toY, promoChar);
10564 currentMove = forwardMostMove;
10569 /* Load the nth game from the given file */
10571 LoadGameFromFile(filename, n, title, useList)
10575 /*Boolean*/ int useList;
10580 if (strcmp(filename, "-") == 0) {
10584 f = fopen(filename, "rb");
10586 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10587 DisplayError(buf, errno);
10591 if (fseek(f, 0, 0) == -1) {
10592 /* f is not seekable; probably a pipe */
10595 if (useList && n == 0) {
10596 int error = GameListBuild(f);
10598 DisplayError(_("Cannot build game list"), error);
10599 } else if (!ListEmpty(&gameList) &&
10600 ((ListGame *) gameList.tailPred)->number > 1) {
10601 GameListPopUp(f, title);
10608 return LoadGame(f, n, title, FALSE);
10613 MakeRegisteredMove()
10615 int fromX, fromY, toX, toY;
10617 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10618 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10621 if (appData.debugMode)
10622 fprintf(debugFP, "Restoring %s for game %d\n",
10623 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10625 thinkOutput[0] = NULLCHAR;
10626 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10627 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10628 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10629 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10630 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10631 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10632 MakeMove(fromX, fromY, toX, toY, promoChar);
10633 ShowMove(fromX, fromY, toX, toY);
10635 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10642 if (WhiteOnMove(currentMove)) {
10643 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10645 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10650 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10657 if (WhiteOnMove(currentMove)) {
10658 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10660 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10665 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10676 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10678 CmailLoadGame(f, gameNumber, title, useList)
10686 if (gameNumber > nCmailGames) {
10687 DisplayError(_("No more games in this message"), 0);
10690 if (f == lastLoadGameFP) {
10691 int offset = gameNumber - lastLoadGameNumber;
10693 cmailMsg[0] = NULLCHAR;
10694 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10695 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10696 nCmailMovesRegistered--;
10698 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10699 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10700 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10703 if (! RegisterMove()) return FALSE;
10707 retVal = LoadGame(f, gameNumber, title, useList);
10709 /* Make move registered during previous look at this game, if any */
10710 MakeRegisteredMove();
10712 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10713 commentList[currentMove]
10714 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10715 DisplayComment(currentMove - 1, commentList[currentMove]);
10721 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10726 int gameNumber = lastLoadGameNumber + offset;
10727 if (lastLoadGameFP == NULL) {
10728 DisplayError(_("No game has been loaded yet"), 0);
10731 if (gameNumber <= 0) {
10732 DisplayError(_("Can't back up any further"), 0);
10735 if (cmailMsgLoaded) {
10736 return CmailLoadGame(lastLoadGameFP, gameNumber,
10737 lastLoadGameTitle, lastLoadGameUseList);
10739 return LoadGame(lastLoadGameFP, gameNumber,
10740 lastLoadGameTitle, lastLoadGameUseList);
10746 /* Load the nth game from open file f */
10748 LoadGame(f, gameNumber, title, useList)
10756 int gn = gameNumber;
10757 ListGame *lg = NULL;
10758 int numPGNTags = 0;
10760 GameMode oldGameMode;
10761 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10763 if (appData.debugMode)
10764 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10766 if (gameMode == Training )
10767 SetTrainingModeOff();
10769 oldGameMode = gameMode;
10770 if (gameMode != BeginningOfGame) {
10771 Reset(FALSE, TRUE);
10775 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10776 fclose(lastLoadGameFP);
10780 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10783 fseek(f, lg->offset, 0);
10784 GameListHighlight(gameNumber);
10788 DisplayError(_("Game number out of range"), 0);
10793 if (fseek(f, 0, 0) == -1) {
10794 if (f == lastLoadGameFP ?
10795 gameNumber == lastLoadGameNumber + 1 :
10799 DisplayError(_("Can't seek on game file"), 0);
10804 lastLoadGameFP = f;
10805 lastLoadGameNumber = gameNumber;
10806 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10807 lastLoadGameUseList = useList;
10811 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10812 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10813 lg->gameInfo.black);
10815 } else if (*title != NULLCHAR) {
10816 if (gameNumber > 1) {
10817 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10820 DisplayTitle(title);
10824 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10825 gameMode = PlayFromGameFile;
10829 currentMove = forwardMostMove = backwardMostMove = 0;
10830 CopyBoard(boards[0], initialPosition);
10834 * Skip the first gn-1 games in the file.
10835 * Also skip over anything that precedes an identifiable
10836 * start of game marker, to avoid being confused by
10837 * garbage at the start of the file. Currently
10838 * recognized start of game markers are the move number "1",
10839 * the pattern "gnuchess .* game", the pattern
10840 * "^[#;%] [^ ]* game file", and a PGN tag block.
10841 * A game that starts with one of the latter two patterns
10842 * will also have a move number 1, possibly
10843 * following a position diagram.
10844 * 5-4-02: Let's try being more lenient and allowing a game to
10845 * start with an unnumbered move. Does that break anything?
10847 cm = lastLoadGameStart = EndOfFile;
10849 yyboardindex = forwardMostMove;
10850 cm = (ChessMove) Myylex();
10853 if (cmailMsgLoaded) {
10854 nCmailGames = CMAIL_MAX_GAMES - gn;
10857 DisplayError(_("Game not found in file"), 0);
10864 lastLoadGameStart = cm;
10867 case MoveNumberOne:
10868 switch (lastLoadGameStart) {
10873 case MoveNumberOne:
10875 gn--; /* count this game */
10876 lastLoadGameStart = cm;
10885 switch (lastLoadGameStart) {
10888 case MoveNumberOne:
10890 gn--; /* count this game */
10891 lastLoadGameStart = cm;
10894 lastLoadGameStart = cm; /* game counted already */
10902 yyboardindex = forwardMostMove;
10903 cm = (ChessMove) Myylex();
10904 } while (cm == PGNTag || cm == Comment);
10911 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10912 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10913 != CMAIL_OLD_RESULT) {
10915 cmailResult[ CMAIL_MAX_GAMES
10916 - gn - 1] = CMAIL_OLD_RESULT;
10922 /* Only a NormalMove can be at the start of a game
10923 * without a position diagram. */
10924 if (lastLoadGameStart == EndOfFile ) {
10926 lastLoadGameStart = MoveNumberOne;
10935 if (appData.debugMode)
10936 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10938 if (cm == XBoardGame) {
10939 /* Skip any header junk before position diagram and/or move 1 */
10941 yyboardindex = forwardMostMove;
10942 cm = (ChessMove) Myylex();
10944 if (cm == EndOfFile ||
10945 cm == GNUChessGame || cm == XBoardGame) {
10946 /* Empty game; pretend end-of-file and handle later */
10951 if (cm == MoveNumberOne || cm == PositionDiagram ||
10952 cm == PGNTag || cm == Comment)
10955 } else if (cm == GNUChessGame) {
10956 if (gameInfo.event != NULL) {
10957 free(gameInfo.event);
10959 gameInfo.event = StrSave(yy_text);
10962 startedFromSetupPosition = FALSE;
10963 while (cm == PGNTag) {
10964 if (appData.debugMode)
10965 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10966 err = ParsePGNTag(yy_text, &gameInfo);
10967 if (!err) numPGNTags++;
10969 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10970 if(gameInfo.variant != oldVariant) {
10971 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10972 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10973 InitPosition(TRUE);
10974 oldVariant = gameInfo.variant;
10975 if (appData.debugMode)
10976 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10980 if (gameInfo.fen != NULL) {
10981 Board initial_position;
10982 startedFromSetupPosition = TRUE;
10983 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10985 DisplayError(_("Bad FEN position in file"), 0);
10988 CopyBoard(boards[0], initial_position);
10989 if (blackPlaysFirst) {
10990 currentMove = forwardMostMove = backwardMostMove = 1;
10991 CopyBoard(boards[1], initial_position);
10992 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10993 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10994 timeRemaining[0][1] = whiteTimeRemaining;
10995 timeRemaining[1][1] = blackTimeRemaining;
10996 if (commentList[0] != NULL) {
10997 commentList[1] = commentList[0];
10998 commentList[0] = NULL;
11001 currentMove = forwardMostMove = backwardMostMove = 0;
11003 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11005 initialRulePlies = FENrulePlies;
11006 for( i=0; i< nrCastlingRights; i++ )
11007 initialRights[i] = initial_position[CASTLING][i];
11009 yyboardindex = forwardMostMove;
11010 free(gameInfo.fen);
11011 gameInfo.fen = NULL;
11014 yyboardindex = forwardMostMove;
11015 cm = (ChessMove) Myylex();
11017 /* Handle comments interspersed among the tags */
11018 while (cm == Comment) {
11020 if (appData.debugMode)
11021 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11023 AppendComment(currentMove, p, FALSE);
11024 yyboardindex = forwardMostMove;
11025 cm = (ChessMove) Myylex();
11029 /* don't rely on existence of Event tag since if game was
11030 * pasted from clipboard the Event tag may not exist
11032 if (numPGNTags > 0){
11034 if (gameInfo.variant == VariantNormal) {
11035 VariantClass v = StringToVariant(gameInfo.event);
11036 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11037 if(v < VariantShogi) gameInfo.variant = v;
11040 if( appData.autoDisplayTags ) {
11041 tags = PGNTags(&gameInfo);
11042 TagsPopUp(tags, CmailMsg());
11047 /* Make something up, but don't display it now */
11052 if (cm == PositionDiagram) {
11055 Board initial_position;
11057 if (appData.debugMode)
11058 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11060 if (!startedFromSetupPosition) {
11062 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11063 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11074 initial_position[i][j++] = CharToPiece(*p);
11077 while (*p == ' ' || *p == '\t' ||
11078 *p == '\n' || *p == '\r') p++;
11080 if (strncmp(p, "black", strlen("black"))==0)
11081 blackPlaysFirst = TRUE;
11083 blackPlaysFirst = FALSE;
11084 startedFromSetupPosition = TRUE;
11086 CopyBoard(boards[0], initial_position);
11087 if (blackPlaysFirst) {
11088 currentMove = forwardMostMove = backwardMostMove = 1;
11089 CopyBoard(boards[1], initial_position);
11090 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11091 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11092 timeRemaining[0][1] = whiteTimeRemaining;
11093 timeRemaining[1][1] = blackTimeRemaining;
11094 if (commentList[0] != NULL) {
11095 commentList[1] = commentList[0];
11096 commentList[0] = NULL;
11099 currentMove = forwardMostMove = backwardMostMove = 0;
11102 yyboardindex = forwardMostMove;
11103 cm = (ChessMove) Myylex();
11106 if (first.pr == NoProc) {
11107 StartChessProgram(&first);
11109 InitChessProgram(&first, FALSE);
11110 SendToProgram("force\n", &first);
11111 if (startedFromSetupPosition) {
11112 SendBoard(&first, forwardMostMove);
11113 if (appData.debugMode) {
11114 fprintf(debugFP, "Load Game\n");
11116 DisplayBothClocks();
11119 /* [HGM] server: flag to write setup moves in broadcast file as one */
11120 loadFlag = appData.suppressLoadMoves;
11122 while (cm == Comment) {
11124 if (appData.debugMode)
11125 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11127 AppendComment(currentMove, p, FALSE);
11128 yyboardindex = forwardMostMove;
11129 cm = (ChessMove) Myylex();
11132 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11133 cm == WhiteWins || cm == BlackWins ||
11134 cm == GameIsDrawn || cm == GameUnfinished) {
11135 DisplayMessage("", _("No moves in game"));
11136 if (cmailMsgLoaded) {
11137 if (appData.debugMode)
11138 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11142 DrawPosition(FALSE, boards[currentMove]);
11143 DisplayBothClocks();
11144 gameMode = EditGame;
11151 // [HGM] PV info: routine tests if comment empty
11152 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11153 DisplayComment(currentMove - 1, commentList[currentMove]);
11155 if (!matchMode && appData.timeDelay != 0)
11156 DrawPosition(FALSE, boards[currentMove]);
11158 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11159 programStats.ok_to_send = 1;
11162 /* if the first token after the PGN tags is a move
11163 * and not move number 1, retrieve it from the parser
11165 if (cm != MoveNumberOne)
11166 LoadGameOneMove(cm);
11168 /* load the remaining moves from the file */
11169 while (LoadGameOneMove(EndOfFile)) {
11170 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11171 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11174 /* rewind to the start of the game */
11175 currentMove = backwardMostMove;
11177 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11179 if (oldGameMode == AnalyzeFile ||
11180 oldGameMode == AnalyzeMode) {
11181 AnalyzeFileEvent();
11184 if (matchMode || appData.timeDelay == 0) {
11186 gameMode = EditGame;
11188 } else if (appData.timeDelay > 0) {
11189 AutoPlayGameLoop();
11192 if (appData.debugMode)
11193 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11195 loadFlag = 0; /* [HGM] true game starts */
11199 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11201 ReloadPosition(offset)
11204 int positionNumber = lastLoadPositionNumber + offset;
11205 if (lastLoadPositionFP == NULL) {
11206 DisplayError(_("No position has been loaded yet"), 0);
11209 if (positionNumber <= 0) {
11210 DisplayError(_("Can't back up any further"), 0);
11213 return LoadPosition(lastLoadPositionFP, positionNumber,
11214 lastLoadPositionTitle);
11217 /* Load the nth position from the given file */
11219 LoadPositionFromFile(filename, n, title)
11227 if (strcmp(filename, "-") == 0) {
11228 return LoadPosition(stdin, n, "stdin");
11230 f = fopen(filename, "rb");
11232 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11233 DisplayError(buf, errno);
11236 return LoadPosition(f, n, title);
11241 /* Load the nth position from the given open file, and close it */
11243 LoadPosition(f, positionNumber, title)
11245 int positionNumber;
11248 char *p, line[MSG_SIZ];
11249 Board initial_position;
11250 int i, j, fenMode, pn;
11252 if (gameMode == Training )
11253 SetTrainingModeOff();
11255 if (gameMode != BeginningOfGame) {
11256 Reset(FALSE, TRUE);
11258 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11259 fclose(lastLoadPositionFP);
11261 if (positionNumber == 0) positionNumber = 1;
11262 lastLoadPositionFP = f;
11263 lastLoadPositionNumber = positionNumber;
11264 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11265 if (first.pr == NoProc) {
11266 StartChessProgram(&first);
11267 InitChessProgram(&first, FALSE);
11269 pn = positionNumber;
11270 if (positionNumber < 0) {
11271 /* Negative position number means to seek to that byte offset */
11272 if (fseek(f, -positionNumber, 0) == -1) {
11273 DisplayError(_("Can't seek on position file"), 0);
11278 if (fseek(f, 0, 0) == -1) {
11279 if (f == lastLoadPositionFP ?
11280 positionNumber == lastLoadPositionNumber + 1 :
11281 positionNumber == 1) {
11284 DisplayError(_("Can't seek on position file"), 0);
11289 /* See if this file is FEN or old-style xboard */
11290 if (fgets(line, MSG_SIZ, f) == NULL) {
11291 DisplayError(_("Position not found in file"), 0);
11294 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11295 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11298 if (fenMode || line[0] == '#') pn--;
11300 /* skip positions before number pn */
11301 if (fgets(line, MSG_SIZ, f) == NULL) {
11303 DisplayError(_("Position not found in file"), 0);
11306 if (fenMode || line[0] == '#') pn--;
11311 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11312 DisplayError(_("Bad FEN position in file"), 0);
11316 (void) fgets(line, MSG_SIZ, f);
11317 (void) fgets(line, MSG_SIZ, f);
11319 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11320 (void) fgets(line, MSG_SIZ, f);
11321 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11324 initial_position[i][j++] = CharToPiece(*p);
11328 blackPlaysFirst = FALSE;
11330 (void) fgets(line, MSG_SIZ, f);
11331 if (strncmp(line, "black", strlen("black"))==0)
11332 blackPlaysFirst = TRUE;
11335 startedFromSetupPosition = TRUE;
11337 SendToProgram("force\n", &first);
11338 CopyBoard(boards[0], initial_position);
11339 if (blackPlaysFirst) {
11340 currentMove = forwardMostMove = backwardMostMove = 1;
11341 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11342 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11343 CopyBoard(boards[1], initial_position);
11344 DisplayMessage("", _("Black to play"));
11346 currentMove = forwardMostMove = backwardMostMove = 0;
11347 DisplayMessage("", _("White to play"));
11349 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11350 SendBoard(&first, forwardMostMove);
11351 if (appData.debugMode) {
11353 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11354 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11355 fprintf(debugFP, "Load Position\n");
11358 if (positionNumber > 1) {
11359 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11360 DisplayTitle(line);
11362 DisplayTitle(title);
11364 gameMode = EditGame;
11367 timeRemaining[0][1] = whiteTimeRemaining;
11368 timeRemaining[1][1] = blackTimeRemaining;
11369 DrawPosition(FALSE, boards[currentMove]);
11376 CopyPlayerNameIntoFileName(dest, src)
11379 while (*src != NULLCHAR && *src != ',') {
11384 *(*dest)++ = *src++;
11389 char *DefaultFileName(ext)
11392 static char def[MSG_SIZ];
11395 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11397 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11399 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11401 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11408 /* Save the current game to the given file */
11410 SaveGameToFile(filename, append)
11418 if (strcmp(filename, "-") == 0) {
11419 return SaveGame(stdout, 0, NULL);
11421 f = fopen(filename, append ? "a" : "w");
11423 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11424 DisplayError(buf, errno);
11427 safeStrCpy(buf, lastMsg, MSG_SIZ);
11428 DisplayMessage(_("Waiting for access to save file"), "");
11429 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11430 DisplayMessage(_("Saving game"), "");
11431 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11432 result = SaveGame(f, 0, NULL);
11433 DisplayMessage(buf, "");
11443 static char buf[MSG_SIZ];
11446 p = strchr(str, ' ');
11447 if (p == NULL) return str;
11448 strncpy(buf, str, p - str);
11449 buf[p - str] = NULLCHAR;
11453 #define PGN_MAX_LINE 75
11455 #define PGN_SIDE_WHITE 0
11456 #define PGN_SIDE_BLACK 1
11459 static int FindFirstMoveOutOfBook( int side )
11463 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11464 int index = backwardMostMove;
11465 int has_book_hit = 0;
11467 if( (index % 2) != side ) {
11471 while( index < forwardMostMove ) {
11472 /* Check to see if engine is in book */
11473 int depth = pvInfoList[index].depth;
11474 int score = pvInfoList[index].score;
11480 else if( score == 0 && depth == 63 ) {
11481 in_book = 1; /* Zappa */
11483 else if( score == 2 && depth == 99 ) {
11484 in_book = 1; /* Abrok */
11487 has_book_hit += in_book;
11503 void GetOutOfBookInfo( char * buf )
11507 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11509 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11510 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11514 if( oob[0] >= 0 || oob[1] >= 0 ) {
11515 for( i=0; i<2; i++ ) {
11519 if( i > 0 && oob[0] >= 0 ) {
11520 strcat( buf, " " );
11523 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11524 sprintf( buf+strlen(buf), "%s%.2f",
11525 pvInfoList[idx].score >= 0 ? "+" : "",
11526 pvInfoList[idx].score / 100.0 );
11532 /* Save game in PGN style and close the file */
11537 int i, offset, linelen, newblock;
11541 int movelen, numlen, blank;
11542 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11544 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11546 tm = time((time_t *) NULL);
11548 PrintPGNTags(f, &gameInfo);
11550 if (backwardMostMove > 0 || startedFromSetupPosition) {
11551 char *fen = PositionToFEN(backwardMostMove, NULL);
11552 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11553 fprintf(f, "\n{--------------\n");
11554 PrintPosition(f, backwardMostMove);
11555 fprintf(f, "--------------}\n");
11559 /* [AS] Out of book annotation */
11560 if( appData.saveOutOfBookInfo ) {
11563 GetOutOfBookInfo( buf );
11565 if( buf[0] != '\0' ) {
11566 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11573 i = backwardMostMove;
11577 while (i < forwardMostMove) {
11578 /* Print comments preceding this move */
11579 if (commentList[i] != NULL) {
11580 if (linelen > 0) fprintf(f, "\n");
11581 fprintf(f, "%s", commentList[i]);
11586 /* Format move number */
11588 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11591 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11593 numtext[0] = NULLCHAR;
11595 numlen = strlen(numtext);
11598 /* Print move number */
11599 blank = linelen > 0 && numlen > 0;
11600 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11609 fprintf(f, "%s", numtext);
11613 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11614 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11617 blank = linelen > 0 && movelen > 0;
11618 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11627 fprintf(f, "%s", move_buffer);
11628 linelen += movelen;
11630 /* [AS] Add PV info if present */
11631 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11632 /* [HGM] add time */
11633 char buf[MSG_SIZ]; int seconds;
11635 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11641 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11644 seconds = (seconds + 4)/10; // round to full seconds
11646 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11648 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11651 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11652 pvInfoList[i].score >= 0 ? "+" : "",
11653 pvInfoList[i].score / 100.0,
11654 pvInfoList[i].depth,
11657 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11659 /* Print score/depth */
11660 blank = linelen > 0 && movelen > 0;
11661 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11670 fprintf(f, "%s", move_buffer);
11671 linelen += movelen;
11677 /* Start a new line */
11678 if (linelen > 0) fprintf(f, "\n");
11680 /* Print comments after last move */
11681 if (commentList[i] != NULL) {
11682 fprintf(f, "%s\n", commentList[i]);
11686 if (gameInfo.resultDetails != NULL &&
11687 gameInfo.resultDetails[0] != NULLCHAR) {
11688 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11689 PGNResult(gameInfo.result));
11691 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11695 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11699 /* Save game in old style and close the file */
11701 SaveGameOldStyle(f)
11707 tm = time((time_t *) NULL);
11709 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11712 if (backwardMostMove > 0 || startedFromSetupPosition) {
11713 fprintf(f, "\n[--------------\n");
11714 PrintPosition(f, backwardMostMove);
11715 fprintf(f, "--------------]\n");
11720 i = backwardMostMove;
11721 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11723 while (i < forwardMostMove) {
11724 if (commentList[i] != NULL) {
11725 fprintf(f, "[%s]\n", commentList[i]);
11728 if ((i % 2) == 1) {
11729 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11732 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11734 if (commentList[i] != NULL) {
11738 if (i >= forwardMostMove) {
11742 fprintf(f, "%s\n", parseList[i]);
11747 if (commentList[i] != NULL) {
11748 fprintf(f, "[%s]\n", commentList[i]);
11751 /* This isn't really the old style, but it's close enough */
11752 if (gameInfo.resultDetails != NULL &&
11753 gameInfo.resultDetails[0] != NULLCHAR) {
11754 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11755 gameInfo.resultDetails);
11757 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11764 /* Save the current game to open file f and close the file */
11766 SaveGame(f, dummy, dummy2)
11771 if (gameMode == EditPosition) EditPositionDone(TRUE);
11772 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11773 if (appData.oldSaveStyle)
11774 return SaveGameOldStyle(f);
11776 return SaveGamePGN(f);
11779 /* Save the current position to the given file */
11781 SavePositionToFile(filename)
11787 if (strcmp(filename, "-") == 0) {
11788 return SavePosition(stdout, 0, NULL);
11790 f = fopen(filename, "a");
11792 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11793 DisplayError(buf, errno);
11796 safeStrCpy(buf, lastMsg, MSG_SIZ);
11797 DisplayMessage(_("Waiting for access to save file"), "");
11798 flock(fileno(f), LOCK_EX); // [HGM] lock
11799 DisplayMessage(_("Saving position"), "");
11800 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11801 SavePosition(f, 0, NULL);
11802 DisplayMessage(buf, "");
11808 /* Save the current position to the given open file and close the file */
11810 SavePosition(f, dummy, dummy2)
11818 if (gameMode == EditPosition) EditPositionDone(TRUE);
11819 if (appData.oldSaveStyle) {
11820 tm = time((time_t *) NULL);
11822 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11824 fprintf(f, "[--------------\n");
11825 PrintPosition(f, currentMove);
11826 fprintf(f, "--------------]\n");
11828 fen = PositionToFEN(currentMove, NULL);
11829 fprintf(f, "%s\n", fen);
11837 ReloadCmailMsgEvent(unregister)
11841 static char *inFilename = NULL;
11842 static char *outFilename;
11844 struct stat inbuf, outbuf;
11847 /* Any registered moves are unregistered if unregister is set, */
11848 /* i.e. invoked by the signal handler */
11850 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11851 cmailMoveRegistered[i] = FALSE;
11852 if (cmailCommentList[i] != NULL) {
11853 free(cmailCommentList[i]);
11854 cmailCommentList[i] = NULL;
11857 nCmailMovesRegistered = 0;
11860 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11861 cmailResult[i] = CMAIL_NOT_RESULT;
11865 if (inFilename == NULL) {
11866 /* Because the filenames are static they only get malloced once */
11867 /* and they never get freed */
11868 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11869 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11871 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11872 sprintf(outFilename, "%s.out", appData.cmailGameName);
11875 status = stat(outFilename, &outbuf);
11877 cmailMailedMove = FALSE;
11879 status = stat(inFilename, &inbuf);
11880 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11883 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11884 counts the games, notes how each one terminated, etc.
11886 It would be nice to remove this kludge and instead gather all
11887 the information while building the game list. (And to keep it
11888 in the game list nodes instead of having a bunch of fixed-size
11889 parallel arrays.) Note this will require getting each game's
11890 termination from the PGN tags, as the game list builder does
11891 not process the game moves. --mann
11893 cmailMsgLoaded = TRUE;
11894 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11896 /* Load first game in the file or popup game menu */
11897 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11899 #endif /* !WIN32 */
11907 char string[MSG_SIZ];
11909 if ( cmailMailedMove
11910 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11911 return TRUE; /* Allow free viewing */
11914 /* Unregister move to ensure that we don't leave RegisterMove */
11915 /* with the move registered when the conditions for registering no */
11917 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11918 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11919 nCmailMovesRegistered --;
11921 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11923 free(cmailCommentList[lastLoadGameNumber - 1]);
11924 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11928 if (cmailOldMove == -1) {
11929 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11933 if (currentMove > cmailOldMove + 1) {
11934 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11938 if (currentMove < cmailOldMove) {
11939 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11943 if (forwardMostMove > currentMove) {
11944 /* Silently truncate extra moves */
11948 if ( (currentMove == cmailOldMove + 1)
11949 || ( (currentMove == cmailOldMove)
11950 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11951 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11952 if (gameInfo.result != GameUnfinished) {
11953 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11956 if (commentList[currentMove] != NULL) {
11957 cmailCommentList[lastLoadGameNumber - 1]
11958 = StrSave(commentList[currentMove]);
11960 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11962 if (appData.debugMode)
11963 fprintf(debugFP, "Saving %s for game %d\n",
11964 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11966 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11968 f = fopen(string, "w");
11969 if (appData.oldSaveStyle) {
11970 SaveGameOldStyle(f); /* also closes the file */
11972 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11973 f = fopen(string, "w");
11974 SavePosition(f, 0, NULL); /* also closes the file */
11976 fprintf(f, "{--------------\n");
11977 PrintPosition(f, currentMove);
11978 fprintf(f, "--------------}\n\n");
11980 SaveGame(f, 0, NULL); /* also closes the file*/
11983 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11984 nCmailMovesRegistered ++;
11985 } else if (nCmailGames == 1) {
11986 DisplayError(_("You have not made a move yet"), 0);
11997 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11998 FILE *commandOutput;
11999 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12000 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12006 if (! cmailMsgLoaded) {
12007 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12011 if (nCmailGames == nCmailResults) {
12012 DisplayError(_("No unfinished games"), 0);
12016 #if CMAIL_PROHIBIT_REMAIL
12017 if (cmailMailedMove) {
12018 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);
12019 DisplayError(msg, 0);
12024 if (! (cmailMailedMove || RegisterMove())) return;
12026 if ( cmailMailedMove
12027 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12028 snprintf(string, MSG_SIZ, partCommandString,
12029 appData.debugMode ? " -v" : "", appData.cmailGameName);
12030 commandOutput = popen(string, "r");
12032 if (commandOutput == NULL) {
12033 DisplayError(_("Failed to invoke cmail"), 0);
12035 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12036 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12038 if (nBuffers > 1) {
12039 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12040 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12041 nBytes = MSG_SIZ - 1;
12043 (void) memcpy(msg, buffer, nBytes);
12045 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12047 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12048 cmailMailedMove = TRUE; /* Prevent >1 moves */
12051 for (i = 0; i < nCmailGames; i ++) {
12052 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12057 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12059 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12061 appData.cmailGameName,
12063 LoadGameFromFile(buffer, 1, buffer, FALSE);
12064 cmailMsgLoaded = FALSE;
12068 DisplayInformation(msg);
12069 pclose(commandOutput);
12072 if ((*cmailMsg) != '\0') {
12073 DisplayInformation(cmailMsg);
12078 #endif /* !WIN32 */
12087 int prependComma = 0;
12089 char string[MSG_SIZ]; /* Space for game-list */
12092 if (!cmailMsgLoaded) return "";
12094 if (cmailMailedMove) {
12095 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12097 /* Create a list of games left */
12098 snprintf(string, MSG_SIZ, "[");
12099 for (i = 0; i < nCmailGames; i ++) {
12100 if (! ( cmailMoveRegistered[i]
12101 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12102 if (prependComma) {
12103 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12105 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12109 strcat(string, number);
12112 strcat(string, "]");
12114 if (nCmailMovesRegistered + nCmailResults == 0) {
12115 switch (nCmailGames) {
12117 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12121 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12125 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12130 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12132 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12137 if (nCmailResults == nCmailGames) {
12138 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12140 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12145 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12157 if (gameMode == Training)
12158 SetTrainingModeOff();
12161 cmailMsgLoaded = FALSE;
12162 if (appData.icsActive) {
12163 SendToICS(ics_prefix);
12164 SendToICS("refresh\n");
12174 /* Give up on clean exit */
12178 /* Keep trying for clean exit */
12182 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12184 if (telnetISR != NULL) {
12185 RemoveInputSource(telnetISR);
12187 if (icsPR != NoProc) {
12188 DestroyChildProcess(icsPR, TRUE);
12191 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12192 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12194 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12195 /* make sure this other one finishes before killing it! */
12196 if(endingGame) { int count = 0;
12197 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12198 while(endingGame && count++ < 10) DoSleep(1);
12199 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12202 /* Kill off chess programs */
12203 if (first.pr != NoProc) {
12206 DoSleep( appData.delayBeforeQuit );
12207 SendToProgram("quit\n", &first);
12208 DoSleep( appData.delayAfterQuit );
12209 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12211 if (second.pr != NoProc) {
12212 DoSleep( appData.delayBeforeQuit );
12213 SendToProgram("quit\n", &second);
12214 DoSleep( appData.delayAfterQuit );
12215 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12217 if (first.isr != NULL) {
12218 RemoveInputSource(first.isr);
12220 if (second.isr != NULL) {
12221 RemoveInputSource(second.isr);
12224 ShutDownFrontEnd();
12231 if (appData.debugMode)
12232 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12236 if (gameMode == MachinePlaysWhite ||
12237 gameMode == MachinePlaysBlack) {
12240 DisplayBothClocks();
12242 if (gameMode == PlayFromGameFile) {
12243 if (appData.timeDelay >= 0)
12244 AutoPlayGameLoop();
12245 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12246 Reset(FALSE, TRUE);
12247 SendToICS(ics_prefix);
12248 SendToICS("refresh\n");
12249 } else if (currentMove < forwardMostMove) {
12250 ForwardInner(forwardMostMove);
12252 pauseExamInvalid = FALSE;
12254 switch (gameMode) {
12258 pauseExamForwardMostMove = forwardMostMove;
12259 pauseExamInvalid = FALSE;
12262 case IcsPlayingWhite:
12263 case IcsPlayingBlack:
12267 case PlayFromGameFile:
12268 (void) StopLoadGameTimer();
12272 case BeginningOfGame:
12273 if (appData.icsActive) return;
12274 /* else fall through */
12275 case MachinePlaysWhite:
12276 case MachinePlaysBlack:
12277 case TwoMachinesPlay:
12278 if (forwardMostMove == 0)
12279 return; /* don't pause if no one has moved */
12280 if ((gameMode == MachinePlaysWhite &&
12281 !WhiteOnMove(forwardMostMove)) ||
12282 (gameMode == MachinePlaysBlack &&
12283 WhiteOnMove(forwardMostMove))) {
12296 char title[MSG_SIZ];
12298 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12299 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12301 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12302 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12303 parseList[currentMove - 1]);
12306 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12313 char *tags = PGNTags(&gameInfo);
12314 EditTagsPopUp(tags, NULL);
12321 if (appData.noChessProgram || gameMode == AnalyzeMode)
12324 if (gameMode != AnalyzeFile) {
12325 if (!appData.icsEngineAnalyze) {
12327 if (gameMode != EditGame) return;
12329 ResurrectChessProgram();
12330 SendToProgram("analyze\n", &first);
12331 first.analyzing = TRUE;
12332 /*first.maybeThinking = TRUE;*/
12333 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12334 EngineOutputPopUp();
12336 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12341 StartAnalysisClock();
12342 GetTimeMark(&lastNodeCountTime);
12349 if (appData.noChessProgram || gameMode == AnalyzeFile)
12352 if (gameMode != AnalyzeMode) {
12354 if (gameMode != EditGame) return;
12355 ResurrectChessProgram();
12356 SendToProgram("analyze\n", &first);
12357 first.analyzing = TRUE;
12358 /*first.maybeThinking = TRUE;*/
12359 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12360 EngineOutputPopUp();
12362 gameMode = AnalyzeFile;
12367 StartAnalysisClock();
12368 GetTimeMark(&lastNodeCountTime);
12373 MachineWhiteEvent()
12376 char *bookHit = NULL;
12378 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12382 if (gameMode == PlayFromGameFile ||
12383 gameMode == TwoMachinesPlay ||
12384 gameMode == Training ||
12385 gameMode == AnalyzeMode ||
12386 gameMode == EndOfGame)
12389 if (gameMode == EditPosition)
12390 EditPositionDone(TRUE);
12392 if (!WhiteOnMove(currentMove)) {
12393 DisplayError(_("It is not White's turn"), 0);
12397 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12400 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12401 gameMode == AnalyzeFile)
12404 ResurrectChessProgram(); /* in case it isn't running */
12405 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12406 gameMode = MachinePlaysWhite;
12409 gameMode = MachinePlaysWhite;
12413 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12415 if (first.sendName) {
12416 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12417 SendToProgram(buf, &first);
12419 if (first.sendTime) {
12420 if (first.useColors) {
12421 SendToProgram("black\n", &first); /*gnu kludge*/
12423 SendTimeRemaining(&first, TRUE);
12425 if (first.useColors) {
12426 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12428 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12429 SetMachineThinkingEnables();
12430 first.maybeThinking = TRUE;
12434 if (appData.autoFlipView && !flipView) {
12435 flipView = !flipView;
12436 DrawPosition(FALSE, NULL);
12437 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12440 if(bookHit) { // [HGM] book: simulate book reply
12441 static char bookMove[MSG_SIZ]; // a bit generous?
12443 programStats.nodes = programStats.depth = programStats.time =
12444 programStats.score = programStats.got_only_move = 0;
12445 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12447 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12448 strcat(bookMove, bookHit);
12449 HandleMachineMove(bookMove, &first);
12454 MachineBlackEvent()
12457 char *bookHit = NULL;
12459 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12463 if (gameMode == PlayFromGameFile ||
12464 gameMode == TwoMachinesPlay ||
12465 gameMode == Training ||
12466 gameMode == AnalyzeMode ||
12467 gameMode == EndOfGame)
12470 if (gameMode == EditPosition)
12471 EditPositionDone(TRUE);
12473 if (WhiteOnMove(currentMove)) {
12474 DisplayError(_("It is not Black's turn"), 0);
12478 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12481 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12482 gameMode == AnalyzeFile)
12485 ResurrectChessProgram(); /* in case it isn't running */
12486 gameMode = MachinePlaysBlack;
12490 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12492 if (first.sendName) {
12493 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12494 SendToProgram(buf, &first);
12496 if (first.sendTime) {
12497 if (first.useColors) {
12498 SendToProgram("white\n", &first); /*gnu kludge*/
12500 SendTimeRemaining(&first, FALSE);
12502 if (first.useColors) {
12503 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12505 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12506 SetMachineThinkingEnables();
12507 first.maybeThinking = TRUE;
12510 if (appData.autoFlipView && flipView) {
12511 flipView = !flipView;
12512 DrawPosition(FALSE, NULL);
12513 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12515 if(bookHit) { // [HGM] book: simulate book reply
12516 static char bookMove[MSG_SIZ]; // a bit generous?
12518 programStats.nodes = programStats.depth = programStats.time =
12519 programStats.score = programStats.got_only_move = 0;
12520 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12522 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12523 strcat(bookMove, bookHit);
12524 HandleMachineMove(bookMove, &first);
12530 DisplayTwoMachinesTitle()
12533 if (appData.matchGames > 0) {
12534 if (first.twoMachinesColor[0] == 'w') {
12535 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12536 gameInfo.white, gameInfo.black,
12537 first.matchWins, second.matchWins,
12538 matchGame - 1 - (first.matchWins + second.matchWins));
12540 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12541 gameInfo.white, gameInfo.black,
12542 second.matchWins, first.matchWins,
12543 matchGame - 1 - (first.matchWins + second.matchWins));
12546 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12552 SettingsMenuIfReady()
12554 if (second.lastPing != second.lastPong) {
12555 DisplayMessage("", _("Waiting for second chess program"));
12556 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12560 DisplayMessage("", "");
12561 SettingsPopUp(&second);
12565 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12568 if (cps->pr == NULL) {
12569 StartChessProgram(cps);
12570 if (cps->protocolVersion == 1) {
12573 /* kludge: allow timeout for initial "feature" command */
12575 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12576 DisplayMessage("", buf);
12577 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12585 TwoMachinesEvent P((void))
12589 ChessProgramState *onmove;
12590 char *bookHit = NULL;
12591 static int stalling = 0;
12595 if (appData.noChessProgram) return;
12597 switch (gameMode) {
12598 case TwoMachinesPlay:
12600 case MachinePlaysWhite:
12601 case MachinePlaysBlack:
12602 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12603 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12607 case BeginningOfGame:
12608 case PlayFromGameFile:
12611 if (gameMode != EditGame) return;
12614 EditPositionDone(TRUE);
12625 // forwardMostMove = currentMove;
12626 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12628 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12630 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12631 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12632 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12636 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12637 SendToProgram("force\n", &second);
12639 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12642 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12643 if(appData.matchPause>10000 || appData.matchPause<10)
12644 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12645 wait = SubtractTimeMarks(&now, &pauseStart);
12646 if(wait < appData.matchPause) {
12647 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12651 DisplayMessage("", "");
12652 if (startedFromSetupPosition) {
12653 SendBoard(&second, backwardMostMove);
12654 if (appData.debugMode) {
12655 fprintf(debugFP, "Two Machines\n");
12658 for (i = backwardMostMove; i < forwardMostMove; i++) {
12659 SendMoveToProgram(i, &second);
12662 gameMode = TwoMachinesPlay;
12666 DisplayTwoMachinesTitle();
12668 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12673 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12674 SendToProgram(first.computerString, &first);
12675 if (first.sendName) {
12676 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12677 SendToProgram(buf, &first);
12679 SendToProgram(second.computerString, &second);
12680 if (second.sendName) {
12681 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12682 SendToProgram(buf, &second);
12686 if (!first.sendTime || !second.sendTime) {
12687 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12688 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12690 if (onmove->sendTime) {
12691 if (onmove->useColors) {
12692 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12694 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12696 if (onmove->useColors) {
12697 SendToProgram(onmove->twoMachinesColor, onmove);
12699 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12700 // SendToProgram("go\n", onmove);
12701 onmove->maybeThinking = TRUE;
12702 SetMachineThinkingEnables();
12706 if(bookHit) { // [HGM] book: simulate book reply
12707 static char bookMove[MSG_SIZ]; // a bit generous?
12709 programStats.nodes = programStats.depth = programStats.time =
12710 programStats.score = programStats.got_only_move = 0;
12711 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12713 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12714 strcat(bookMove, bookHit);
12715 savedMessage = bookMove; // args for deferred call
12716 savedState = onmove;
12717 ScheduleDelayedEvent(DeferredBookMove, 1);
12724 if (gameMode == Training) {
12725 SetTrainingModeOff();
12726 gameMode = PlayFromGameFile;
12727 DisplayMessage("", _("Training mode off"));
12729 gameMode = Training;
12730 animateTraining = appData.animate;
12732 /* make sure we are not already at the end of the game */
12733 if (currentMove < forwardMostMove) {
12734 SetTrainingModeOn();
12735 DisplayMessage("", _("Training mode on"));
12737 gameMode = PlayFromGameFile;
12738 DisplayError(_("Already at end of game"), 0);
12747 if (!appData.icsActive) return;
12748 switch (gameMode) {
12749 case IcsPlayingWhite:
12750 case IcsPlayingBlack:
12753 case BeginningOfGame:
12761 EditPositionDone(TRUE);
12774 gameMode = IcsIdle;
12785 switch (gameMode) {
12787 SetTrainingModeOff();
12789 case MachinePlaysWhite:
12790 case MachinePlaysBlack:
12791 case BeginningOfGame:
12792 SendToProgram("force\n", &first);
12793 SetUserThinkingEnables();
12795 case PlayFromGameFile:
12796 (void) StopLoadGameTimer();
12797 if (gameFileFP != NULL) {
12802 EditPositionDone(TRUE);
12807 SendToProgram("force\n", &first);
12809 case TwoMachinesPlay:
12810 GameEnds(EndOfFile, NULL, GE_PLAYER);
12811 ResurrectChessProgram();
12812 SetUserThinkingEnables();
12815 ResurrectChessProgram();
12817 case IcsPlayingBlack:
12818 case IcsPlayingWhite:
12819 DisplayError(_("Warning: You are still playing a game"), 0);
12822 DisplayError(_("Warning: You are still observing a game"), 0);
12825 DisplayError(_("Warning: You are still examining a game"), 0);
12836 first.offeredDraw = second.offeredDraw = 0;
12838 if (gameMode == PlayFromGameFile) {
12839 whiteTimeRemaining = timeRemaining[0][currentMove];
12840 blackTimeRemaining = timeRemaining[1][currentMove];
12844 if (gameMode == MachinePlaysWhite ||
12845 gameMode == MachinePlaysBlack ||
12846 gameMode == TwoMachinesPlay ||
12847 gameMode == EndOfGame) {
12848 i = forwardMostMove;
12849 while (i > currentMove) {
12850 SendToProgram("undo\n", &first);
12853 whiteTimeRemaining = timeRemaining[0][currentMove];
12854 blackTimeRemaining = timeRemaining[1][currentMove];
12855 DisplayBothClocks();
12856 if (whiteFlag || blackFlag) {
12857 whiteFlag = blackFlag = 0;
12862 gameMode = EditGame;
12869 EditPositionEvent()
12871 if (gameMode == EditPosition) {
12877 if (gameMode != EditGame) return;
12879 gameMode = EditPosition;
12882 if (currentMove > 0)
12883 CopyBoard(boards[0], boards[currentMove]);
12885 blackPlaysFirst = !WhiteOnMove(currentMove);
12887 currentMove = forwardMostMove = backwardMostMove = 0;
12888 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12895 /* [DM] icsEngineAnalyze - possible call from other functions */
12896 if (appData.icsEngineAnalyze) {
12897 appData.icsEngineAnalyze = FALSE;
12899 DisplayMessage("",_("Close ICS engine analyze..."));
12901 if (first.analysisSupport && first.analyzing) {
12902 SendToProgram("exit\n", &first);
12903 first.analyzing = FALSE;
12905 thinkOutput[0] = NULLCHAR;
12909 EditPositionDone(Boolean fakeRights)
12911 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12913 startedFromSetupPosition = TRUE;
12914 InitChessProgram(&first, FALSE);
12915 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12916 boards[0][EP_STATUS] = EP_NONE;
12917 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12918 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12919 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12920 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12921 } else boards[0][CASTLING][2] = NoRights;
12922 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12923 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12924 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12925 } else boards[0][CASTLING][5] = NoRights;
12927 SendToProgram("force\n", &first);
12928 if (blackPlaysFirst) {
12929 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12930 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12931 currentMove = forwardMostMove = backwardMostMove = 1;
12932 CopyBoard(boards[1], boards[0]);
12934 currentMove = forwardMostMove = backwardMostMove = 0;
12936 SendBoard(&first, forwardMostMove);
12937 if (appData.debugMode) {
12938 fprintf(debugFP, "EditPosDone\n");
12941 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12942 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12943 gameMode = EditGame;
12945 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12946 ClearHighlights(); /* [AS] */
12949 /* Pause for `ms' milliseconds */
12950 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12960 } while (SubtractTimeMarks(&m2, &m1) < ms);
12963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12965 SendMultiLineToICS(buf)
12968 char temp[MSG_SIZ+1], *p;
12975 strncpy(temp, buf, len);
12980 if (*p == '\n' || *p == '\r')
12985 strcat(temp, "\n");
12987 SendToPlayer(temp, strlen(temp));
12991 SetWhiteToPlayEvent()
12993 if (gameMode == EditPosition) {
12994 blackPlaysFirst = FALSE;
12995 DisplayBothClocks(); /* works because currentMove is 0 */
12996 } else if (gameMode == IcsExamining) {
12997 SendToICS(ics_prefix);
12998 SendToICS("tomove white\n");
13003 SetBlackToPlayEvent()
13005 if (gameMode == EditPosition) {
13006 blackPlaysFirst = TRUE;
13007 currentMove = 1; /* kludge */
13008 DisplayBothClocks();
13010 } else if (gameMode == IcsExamining) {
13011 SendToICS(ics_prefix);
13012 SendToICS("tomove black\n");
13017 EditPositionMenuEvent(selection, x, y)
13018 ChessSquare selection;
13022 ChessSquare piece = boards[0][y][x];
13024 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13026 switch (selection) {
13028 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13029 SendToICS(ics_prefix);
13030 SendToICS("bsetup clear\n");
13031 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13032 SendToICS(ics_prefix);
13033 SendToICS("clearboard\n");
13035 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13036 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13037 for (y = 0; y < BOARD_HEIGHT; y++) {
13038 if (gameMode == IcsExamining) {
13039 if (boards[currentMove][y][x] != EmptySquare) {
13040 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13045 boards[0][y][x] = p;
13050 if (gameMode == EditPosition) {
13051 DrawPosition(FALSE, boards[0]);
13056 SetWhiteToPlayEvent();
13060 SetBlackToPlayEvent();
13064 if (gameMode == IcsExamining) {
13065 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13066 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13069 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13070 if(x == BOARD_LEFT-2) {
13071 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13072 boards[0][y][1] = 0;
13074 if(x == BOARD_RGHT+1) {
13075 if(y >= gameInfo.holdingsSize) break;
13076 boards[0][y][BOARD_WIDTH-2] = 0;
13079 boards[0][y][x] = EmptySquare;
13080 DrawPosition(FALSE, boards[0]);
13085 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13086 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13087 selection = (ChessSquare) (PROMOTED piece);
13088 } else if(piece == EmptySquare) selection = WhiteSilver;
13089 else selection = (ChessSquare)((int)piece - 1);
13093 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13094 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13095 selection = (ChessSquare) (DEMOTED piece);
13096 } else if(piece == EmptySquare) selection = BlackSilver;
13097 else selection = (ChessSquare)((int)piece + 1);
13102 if(gameInfo.variant == VariantShatranj ||
13103 gameInfo.variant == VariantXiangqi ||
13104 gameInfo.variant == VariantCourier ||
13105 gameInfo.variant == VariantMakruk )
13106 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13111 if(gameInfo.variant == VariantXiangqi)
13112 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13113 if(gameInfo.variant == VariantKnightmate)
13114 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13117 if (gameMode == IcsExamining) {
13118 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13119 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13120 PieceToChar(selection), AAA + x, ONE + y);
13123 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13125 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13126 n = PieceToNumber(selection - BlackPawn);
13127 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13128 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13129 boards[0][BOARD_HEIGHT-1-n][1]++;
13131 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13132 n = PieceToNumber(selection);
13133 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13134 boards[0][n][BOARD_WIDTH-1] = selection;
13135 boards[0][n][BOARD_WIDTH-2]++;
13138 boards[0][y][x] = selection;
13139 DrawPosition(TRUE, boards[0]);
13147 DropMenuEvent(selection, x, y)
13148 ChessSquare selection;
13151 ChessMove moveType;
13153 switch (gameMode) {
13154 case IcsPlayingWhite:
13155 case MachinePlaysBlack:
13156 if (!WhiteOnMove(currentMove)) {
13157 DisplayMoveError(_("It is Black's turn"));
13160 moveType = WhiteDrop;
13162 case IcsPlayingBlack:
13163 case MachinePlaysWhite:
13164 if (WhiteOnMove(currentMove)) {
13165 DisplayMoveError(_("It is White's turn"));
13168 moveType = BlackDrop;
13171 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13177 if (moveType == BlackDrop && selection < BlackPawn) {
13178 selection = (ChessSquare) ((int) selection
13179 + (int) BlackPawn - (int) WhitePawn);
13181 if (boards[currentMove][y][x] != EmptySquare) {
13182 DisplayMoveError(_("That square is occupied"));
13186 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13192 /* Accept a pending offer of any kind from opponent */
13194 if (appData.icsActive) {
13195 SendToICS(ics_prefix);
13196 SendToICS("accept\n");
13197 } else if (cmailMsgLoaded) {
13198 if (currentMove == cmailOldMove &&
13199 commentList[cmailOldMove] != NULL &&
13200 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13201 "Black offers a draw" : "White offers a draw")) {
13203 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13204 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13206 DisplayError(_("There is no pending offer on this move"), 0);
13207 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13210 /* Not used for offers from chess program */
13217 /* Decline a pending offer of any kind from opponent */
13219 if (appData.icsActive) {
13220 SendToICS(ics_prefix);
13221 SendToICS("decline\n");
13222 } else if (cmailMsgLoaded) {
13223 if (currentMove == cmailOldMove &&
13224 commentList[cmailOldMove] != NULL &&
13225 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13226 "Black offers a draw" : "White offers a draw")) {
13228 AppendComment(cmailOldMove, "Draw declined", TRUE);
13229 DisplayComment(cmailOldMove - 1, "Draw declined");
13232 DisplayError(_("There is no pending offer on this move"), 0);
13235 /* Not used for offers from chess program */
13242 /* Issue ICS rematch command */
13243 if (appData.icsActive) {
13244 SendToICS(ics_prefix);
13245 SendToICS("rematch\n");
13252 /* Call your opponent's flag (claim a win on time) */
13253 if (appData.icsActive) {
13254 SendToICS(ics_prefix);
13255 SendToICS("flag\n");
13257 switch (gameMode) {
13260 case MachinePlaysWhite:
13263 GameEnds(GameIsDrawn, "Both players ran out of time",
13266 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13268 DisplayError(_("Your opponent is not out of time"), 0);
13271 case MachinePlaysBlack:
13274 GameEnds(GameIsDrawn, "Both players ran out of time",
13277 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13279 DisplayError(_("Your opponent is not out of time"), 0);
13287 ClockClick(int which)
13288 { // [HGM] code moved to back-end from winboard.c
13289 if(which) { // black clock
13290 if (gameMode == EditPosition || gameMode == IcsExamining) {
13291 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13292 SetBlackToPlayEvent();
13293 } else if (gameMode == EditGame || shiftKey) {
13294 AdjustClock(which, -1);
13295 } else if (gameMode == IcsPlayingWhite ||
13296 gameMode == MachinePlaysBlack) {
13299 } else { // white clock
13300 if (gameMode == EditPosition || gameMode == IcsExamining) {
13301 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13302 SetWhiteToPlayEvent();
13303 } else if (gameMode == EditGame || shiftKey) {
13304 AdjustClock(which, -1);
13305 } else if (gameMode == IcsPlayingBlack ||
13306 gameMode == MachinePlaysWhite) {
13315 /* Offer draw or accept pending draw offer from opponent */
13317 if (appData.icsActive) {
13318 /* Note: tournament rules require draw offers to be
13319 made after you make your move but before you punch
13320 your clock. Currently ICS doesn't let you do that;
13321 instead, you immediately punch your clock after making
13322 a move, but you can offer a draw at any time. */
13324 SendToICS(ics_prefix);
13325 SendToICS("draw\n");
13326 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13327 } else if (cmailMsgLoaded) {
13328 if (currentMove == cmailOldMove &&
13329 commentList[cmailOldMove] != NULL &&
13330 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13331 "Black offers a draw" : "White offers a draw")) {
13332 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13333 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13334 } else if (currentMove == cmailOldMove + 1) {
13335 char *offer = WhiteOnMove(cmailOldMove) ?
13336 "White offers a draw" : "Black offers a draw";
13337 AppendComment(currentMove, offer, TRUE);
13338 DisplayComment(currentMove - 1, offer);
13339 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13341 DisplayError(_("You must make your move before offering a draw"), 0);
13342 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13344 } else if (first.offeredDraw) {
13345 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13347 if (first.sendDrawOffers) {
13348 SendToProgram("draw\n", &first);
13349 userOfferedDraw = TRUE;
13357 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13359 if (appData.icsActive) {
13360 SendToICS(ics_prefix);
13361 SendToICS("adjourn\n");
13363 /* Currently GNU Chess doesn't offer or accept Adjourns */
13371 /* Offer Abort or accept pending Abort offer from opponent */
13373 if (appData.icsActive) {
13374 SendToICS(ics_prefix);
13375 SendToICS("abort\n");
13377 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13384 /* Resign. You can do this even if it's not your turn. */
13386 if (appData.icsActive) {
13387 SendToICS(ics_prefix);
13388 SendToICS("resign\n");
13390 switch (gameMode) {
13391 case MachinePlaysWhite:
13392 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13394 case MachinePlaysBlack:
13395 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13398 if (cmailMsgLoaded) {
13400 if (WhiteOnMove(cmailOldMove)) {
13401 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13403 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13405 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13416 StopObservingEvent()
13418 /* Stop observing current games */
13419 SendToICS(ics_prefix);
13420 SendToICS("unobserve\n");
13424 StopExaminingEvent()
13426 /* Stop observing current game */
13427 SendToICS(ics_prefix);
13428 SendToICS("unexamine\n");
13432 ForwardInner(target)
13437 if (appData.debugMode)
13438 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13439 target, currentMove, forwardMostMove);
13441 if (gameMode == EditPosition)
13444 if (gameMode == PlayFromGameFile && !pausing)
13447 if (gameMode == IcsExamining && pausing)
13448 limit = pauseExamForwardMostMove;
13450 limit = forwardMostMove;
13452 if (target > limit) target = limit;
13454 if (target > 0 && moveList[target - 1][0]) {
13455 int fromX, fromY, toX, toY;
13456 toX = moveList[target - 1][2] - AAA;
13457 toY = moveList[target - 1][3] - ONE;
13458 if (moveList[target - 1][1] == '@') {
13459 if (appData.highlightLastMove) {
13460 SetHighlights(-1, -1, toX, toY);
13463 fromX = moveList[target - 1][0] - AAA;
13464 fromY = moveList[target - 1][1] - ONE;
13465 if (target == currentMove + 1) {
13466 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13468 if (appData.highlightLastMove) {
13469 SetHighlights(fromX, fromY, toX, toY);
13473 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13474 gameMode == Training || gameMode == PlayFromGameFile ||
13475 gameMode == AnalyzeFile) {
13476 while (currentMove < target) {
13477 SendMoveToProgram(currentMove++, &first);
13480 currentMove = target;
13483 if (gameMode == EditGame || gameMode == EndOfGame) {
13484 whiteTimeRemaining = timeRemaining[0][currentMove];
13485 blackTimeRemaining = timeRemaining[1][currentMove];
13487 DisplayBothClocks();
13488 DisplayMove(currentMove - 1);
13489 DrawPosition(FALSE, boards[currentMove]);
13490 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13491 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13492 DisplayComment(currentMove - 1, commentList[currentMove]);
13500 if (gameMode == IcsExamining && !pausing) {
13501 SendToICS(ics_prefix);
13502 SendToICS("forward\n");
13504 ForwardInner(currentMove + 1);
13511 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13512 /* to optimze, we temporarily turn off analysis mode while we feed
13513 * the remaining moves to the engine. Otherwise we get analysis output
13516 if (first.analysisSupport) {
13517 SendToProgram("exit\nforce\n", &first);
13518 first.analyzing = FALSE;
13522 if (gameMode == IcsExamining && !pausing) {
13523 SendToICS(ics_prefix);
13524 SendToICS("forward 999999\n");
13526 ForwardInner(forwardMostMove);
13529 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13530 /* we have fed all the moves, so reactivate analysis mode */
13531 SendToProgram("analyze\n", &first);
13532 first.analyzing = TRUE;
13533 /*first.maybeThinking = TRUE;*/
13534 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13539 BackwardInner(target)
13542 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13544 if (appData.debugMode)
13545 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13546 target, currentMove, forwardMostMove);
13548 if (gameMode == EditPosition) return;
13549 if (currentMove <= backwardMostMove) {
13551 DrawPosition(full_redraw, boards[currentMove]);
13554 if (gameMode == PlayFromGameFile && !pausing)
13557 if (moveList[target][0]) {
13558 int fromX, fromY, toX, toY;
13559 toX = moveList[target][2] - AAA;
13560 toY = moveList[target][3] - ONE;
13561 if (moveList[target][1] == '@') {
13562 if (appData.highlightLastMove) {
13563 SetHighlights(-1, -1, toX, toY);
13566 fromX = moveList[target][0] - AAA;
13567 fromY = moveList[target][1] - ONE;
13568 if (target == currentMove - 1) {
13569 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13571 if (appData.highlightLastMove) {
13572 SetHighlights(fromX, fromY, toX, toY);
13576 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13577 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13578 while (currentMove > target) {
13579 SendToProgram("undo\n", &first);
13583 currentMove = target;
13586 if (gameMode == EditGame || gameMode == EndOfGame) {
13587 whiteTimeRemaining = timeRemaining[0][currentMove];
13588 blackTimeRemaining = timeRemaining[1][currentMove];
13590 DisplayBothClocks();
13591 DisplayMove(currentMove - 1);
13592 DrawPosition(full_redraw, boards[currentMove]);
13593 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13594 // [HGM] PV info: routine tests if comment empty
13595 DisplayComment(currentMove - 1, commentList[currentMove]);
13601 if (gameMode == IcsExamining && !pausing) {
13602 SendToICS(ics_prefix);
13603 SendToICS("backward\n");
13605 BackwardInner(currentMove - 1);
13612 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13613 /* to optimize, we temporarily turn off analysis mode while we undo
13614 * all the moves. Otherwise we get analysis output after each undo.
13616 if (first.analysisSupport) {
13617 SendToProgram("exit\nforce\n", &first);
13618 first.analyzing = FALSE;
13622 if (gameMode == IcsExamining && !pausing) {
13623 SendToICS(ics_prefix);
13624 SendToICS("backward 999999\n");
13626 BackwardInner(backwardMostMove);
13629 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13630 /* we have fed all the moves, so reactivate analysis mode */
13631 SendToProgram("analyze\n", &first);
13632 first.analyzing = TRUE;
13633 /*first.maybeThinking = TRUE;*/
13634 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13641 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13642 if (to >= forwardMostMove) to = forwardMostMove;
13643 if (to <= backwardMostMove) to = backwardMostMove;
13644 if (to < currentMove) {
13652 RevertEvent(Boolean annotate)
13654 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13657 if (gameMode != IcsExamining) {
13658 DisplayError(_("You are not examining a game"), 0);
13662 DisplayError(_("You can't revert while pausing"), 0);
13665 SendToICS(ics_prefix);
13666 SendToICS("revert\n");
13672 switch (gameMode) {
13673 case MachinePlaysWhite:
13674 case MachinePlaysBlack:
13675 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13676 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13679 if (forwardMostMove < 2) return;
13680 currentMove = forwardMostMove = forwardMostMove - 2;
13681 whiteTimeRemaining = timeRemaining[0][currentMove];
13682 blackTimeRemaining = timeRemaining[1][currentMove];
13683 DisplayBothClocks();
13684 DisplayMove(currentMove - 1);
13685 ClearHighlights();/*!! could figure this out*/
13686 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13687 SendToProgram("remove\n", &first);
13688 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13691 case BeginningOfGame:
13695 case IcsPlayingWhite:
13696 case IcsPlayingBlack:
13697 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13698 SendToICS(ics_prefix);
13699 SendToICS("takeback 2\n");
13701 SendToICS(ics_prefix);
13702 SendToICS("takeback 1\n");
13711 ChessProgramState *cps;
13713 switch (gameMode) {
13714 case MachinePlaysWhite:
13715 if (!WhiteOnMove(forwardMostMove)) {
13716 DisplayError(_("It is your turn"), 0);
13721 case MachinePlaysBlack:
13722 if (WhiteOnMove(forwardMostMove)) {
13723 DisplayError(_("It is your turn"), 0);
13728 case TwoMachinesPlay:
13729 if (WhiteOnMove(forwardMostMove) ==
13730 (first.twoMachinesColor[0] == 'w')) {
13736 case BeginningOfGame:
13740 SendToProgram("?\n", cps);
13744 TruncateGameEvent()
13747 if (gameMode != EditGame) return;
13754 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13755 if (forwardMostMove > currentMove) {
13756 if (gameInfo.resultDetails != NULL) {
13757 free(gameInfo.resultDetails);
13758 gameInfo.resultDetails = NULL;
13759 gameInfo.result = GameUnfinished;
13761 forwardMostMove = currentMove;
13762 HistorySet(parseList, backwardMostMove, forwardMostMove,
13770 if (appData.noChessProgram) return;
13771 switch (gameMode) {
13772 case MachinePlaysWhite:
13773 if (WhiteOnMove(forwardMostMove)) {
13774 DisplayError(_("Wait until your turn"), 0);
13778 case BeginningOfGame:
13779 case MachinePlaysBlack:
13780 if (!WhiteOnMove(forwardMostMove)) {
13781 DisplayError(_("Wait until your turn"), 0);
13786 DisplayError(_("No hint available"), 0);
13789 SendToProgram("hint\n", &first);
13790 hintRequested = TRUE;
13796 if (appData.noChessProgram) return;
13797 switch (gameMode) {
13798 case MachinePlaysWhite:
13799 if (WhiteOnMove(forwardMostMove)) {
13800 DisplayError(_("Wait until your turn"), 0);
13804 case BeginningOfGame:
13805 case MachinePlaysBlack:
13806 if (!WhiteOnMove(forwardMostMove)) {
13807 DisplayError(_("Wait until your turn"), 0);
13812 EditPositionDone(TRUE);
13814 case TwoMachinesPlay:
13819 SendToProgram("bk\n", &first);
13820 bookOutput[0] = NULLCHAR;
13821 bookRequested = TRUE;
13827 char *tags = PGNTags(&gameInfo);
13828 TagsPopUp(tags, CmailMsg());
13832 /* end button procedures */
13835 PrintPosition(fp, move)
13841 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13842 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13843 char c = PieceToChar(boards[move][i][j]);
13844 fputc(c == 'x' ? '.' : c, fp);
13845 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13848 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13849 fprintf(fp, "white to play\n");
13851 fprintf(fp, "black to play\n");
13858 if (gameInfo.white != NULL) {
13859 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13865 /* Find last component of program's own name, using some heuristics */
13867 TidyProgramName(prog, host, buf)
13868 char *prog, *host, buf[MSG_SIZ];
13871 int local = (strcmp(host, "localhost") == 0);
13872 while (!local && (p = strchr(prog, ';')) != NULL) {
13874 while (*p == ' ') p++;
13877 if (*prog == '"' || *prog == '\'') {
13878 q = strchr(prog + 1, *prog);
13880 q = strchr(prog, ' ');
13882 if (q == NULL) q = prog + strlen(prog);
13884 while (p >= prog && *p != '/' && *p != '\\') p--;
13886 if(p == prog && *p == '"') p++;
13887 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13888 memcpy(buf, p, q - p);
13889 buf[q - p] = NULLCHAR;
13897 TimeControlTagValue()
13900 if (!appData.clockMode) {
13901 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13902 } else if (movesPerSession > 0) {
13903 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13904 } else if (timeIncrement == 0) {
13905 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13907 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13909 return StrSave(buf);
13915 /* This routine is used only for certain modes */
13916 VariantClass v = gameInfo.variant;
13917 ChessMove r = GameUnfinished;
13920 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13921 r = gameInfo.result;
13922 p = gameInfo.resultDetails;
13923 gameInfo.resultDetails = NULL;
13925 ClearGameInfo(&gameInfo);
13926 gameInfo.variant = v;
13928 switch (gameMode) {
13929 case MachinePlaysWhite:
13930 gameInfo.event = StrSave( appData.pgnEventHeader );
13931 gameInfo.site = StrSave(HostName());
13932 gameInfo.date = PGNDate();
13933 gameInfo.round = StrSave("-");
13934 gameInfo.white = StrSave(first.tidy);
13935 gameInfo.black = StrSave(UserName());
13936 gameInfo.timeControl = TimeControlTagValue();
13939 case MachinePlaysBlack:
13940 gameInfo.event = StrSave( appData.pgnEventHeader );
13941 gameInfo.site = StrSave(HostName());
13942 gameInfo.date = PGNDate();
13943 gameInfo.round = StrSave("-");
13944 gameInfo.white = StrSave(UserName());
13945 gameInfo.black = StrSave(first.tidy);
13946 gameInfo.timeControl = TimeControlTagValue();
13949 case TwoMachinesPlay:
13950 gameInfo.event = StrSave( appData.pgnEventHeader );
13951 gameInfo.site = StrSave(HostName());
13952 gameInfo.date = PGNDate();
13955 snprintf(buf, MSG_SIZ, "%d", roundNr);
13956 gameInfo.round = StrSave(buf);
13958 gameInfo.round = StrSave("-");
13960 if (first.twoMachinesColor[0] == 'w') {
13961 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13962 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13964 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13965 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13967 gameInfo.timeControl = TimeControlTagValue();
13971 gameInfo.event = StrSave("Edited game");
13972 gameInfo.site = StrSave(HostName());
13973 gameInfo.date = PGNDate();
13974 gameInfo.round = StrSave("-");
13975 gameInfo.white = StrSave("-");
13976 gameInfo.black = StrSave("-");
13977 gameInfo.result = r;
13978 gameInfo.resultDetails = p;
13982 gameInfo.event = StrSave("Edited position");
13983 gameInfo.site = StrSave(HostName());
13984 gameInfo.date = PGNDate();
13985 gameInfo.round = StrSave("-");
13986 gameInfo.white = StrSave("-");
13987 gameInfo.black = StrSave("-");
13990 case IcsPlayingWhite:
13991 case IcsPlayingBlack:
13996 case PlayFromGameFile:
13997 gameInfo.event = StrSave("Game from non-PGN file");
13998 gameInfo.site = StrSave(HostName());
13999 gameInfo.date = PGNDate();
14000 gameInfo.round = StrSave("-");
14001 gameInfo.white = StrSave("?");
14002 gameInfo.black = StrSave("?");
14011 ReplaceComment(index, text)
14019 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14020 pvInfoList[index-1].depth == len &&
14021 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14022 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14023 while (*text == '\n') text++;
14024 len = strlen(text);
14025 while (len > 0 && text[len - 1] == '\n') len--;
14027 if (commentList[index] != NULL)
14028 free(commentList[index]);
14031 commentList[index] = NULL;
14034 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14035 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14036 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14037 commentList[index] = (char *) malloc(len + 2);
14038 strncpy(commentList[index], text, len);
14039 commentList[index][len] = '\n';
14040 commentList[index][len + 1] = NULLCHAR;
14042 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14044 commentList[index] = (char *) malloc(len + 7);
14045 safeStrCpy(commentList[index], "{\n", 3);
14046 safeStrCpy(commentList[index]+2, text, len+1);
14047 commentList[index][len+2] = NULLCHAR;
14048 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14049 strcat(commentList[index], "\n}\n");
14063 if (ch == '\r') continue;
14065 } while (ch != '\0');
14069 AppendComment(index, text, addBraces)
14072 Boolean addBraces; // [HGM] braces: tells if we should add {}
14077 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14078 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14081 while (*text == '\n') text++;
14082 len = strlen(text);
14083 while (len > 0 && text[len - 1] == '\n') len--;
14085 if (len == 0) return;
14087 if (commentList[index] != NULL) {
14088 old = commentList[index];
14089 oldlen = strlen(old);
14090 while(commentList[index][oldlen-1] == '\n')
14091 commentList[index][--oldlen] = NULLCHAR;
14092 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14093 safeStrCpy(commentList[index], old, oldlen + len + 6);
14095 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14096 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14097 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14098 while (*text == '\n') { text++; len--; }
14099 commentList[index][--oldlen] = NULLCHAR;
14101 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14102 else strcat(commentList[index], "\n");
14103 strcat(commentList[index], text);
14104 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14105 else strcat(commentList[index], "\n");
14107 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14109 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14110 else commentList[index][0] = NULLCHAR;
14111 strcat(commentList[index], text);
14112 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14113 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14117 static char * FindStr( char * text, char * sub_text )
14119 char * result = strstr( text, sub_text );
14121 if( result != NULL ) {
14122 result += strlen( sub_text );
14128 /* [AS] Try to extract PV info from PGN comment */
14129 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14130 char *GetInfoFromComment( int index, char * text )
14132 char * sep = text, *p;
14134 if( text != NULL && index > 0 ) {
14137 int time = -1, sec = 0, deci;
14138 char * s_eval = FindStr( text, "[%eval " );
14139 char * s_emt = FindStr( text, "[%emt " );
14141 if( s_eval != NULL || s_emt != NULL ) {
14145 if( s_eval != NULL ) {
14146 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14150 if( delim != ']' ) {
14155 if( s_emt != NULL ) {
14160 /* We expect something like: [+|-]nnn.nn/dd */
14163 if(*text != '{') return text; // [HGM] braces: must be normal comment
14165 sep = strchr( text, '/' );
14166 if( sep == NULL || sep < (text+4) ) {
14171 if(p[1] == '(') { // comment starts with PV
14172 p = strchr(p, ')'); // locate end of PV
14173 if(p == NULL || sep < p+5) return text;
14174 // at this point we have something like "{(.*) +0.23/6 ..."
14175 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14176 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14177 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14179 time = -1; sec = -1; deci = -1;
14180 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14181 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14182 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14183 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14187 if( score_lo < 0 || score_lo >= 100 ) {
14191 if(sec >= 0) time = 600*time + 10*sec; else
14192 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14194 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14196 /* [HGM] PV time: now locate end of PV info */
14197 while( *++sep >= '0' && *sep <= '9'); // strip depth
14199 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14201 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14203 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14204 while(*sep == ' ') sep++;
14215 pvInfoList[index-1].depth = depth;
14216 pvInfoList[index-1].score = score;
14217 pvInfoList[index-1].time = 10*time; // centi-sec
14218 if(*sep == '}') *sep = 0; else *--sep = '{';
14219 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14225 SendToProgram(message, cps)
14227 ChessProgramState *cps;
14229 int count, outCount, error;
14232 if (cps->pr == NULL) return;
14235 if (appData.debugMode) {
14238 fprintf(debugFP, "%ld >%-6s: %s",
14239 SubtractTimeMarks(&now, &programStartTime),
14240 cps->which, message);
14243 count = strlen(message);
14244 outCount = OutputToProcess(cps->pr, message, count, &error);
14245 if (outCount < count && !exiting
14246 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14247 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14248 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14249 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14250 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14251 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14252 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14253 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14255 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14256 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14257 gameInfo.result = res;
14259 gameInfo.resultDetails = StrSave(buf);
14261 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14262 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14267 ReceiveFromProgram(isr, closure, message, count, error)
14268 InputSourceRef isr;
14276 ChessProgramState *cps = (ChessProgramState *)closure;
14278 if (isr != cps->isr) return; /* Killed intentionally */
14281 RemoveInputSource(cps->isr);
14282 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14283 _(cps->which), cps->program);
14284 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14285 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14286 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14287 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14288 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14290 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14291 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14292 gameInfo.result = res;
14294 gameInfo.resultDetails = StrSave(buf);
14296 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14297 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14299 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14300 _(cps->which), cps->program);
14301 RemoveInputSource(cps->isr);
14303 /* [AS] Program is misbehaving badly... kill it */
14304 if( count == -2 ) {
14305 DestroyChildProcess( cps->pr, 9 );
14309 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14314 if ((end_str = strchr(message, '\r')) != NULL)
14315 *end_str = NULLCHAR;
14316 if ((end_str = strchr(message, '\n')) != NULL)
14317 *end_str = NULLCHAR;
14319 if (appData.debugMode) {
14320 TimeMark now; int print = 1;
14321 char *quote = ""; char c; int i;
14323 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14324 char start = message[0];
14325 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14326 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14327 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14328 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14329 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14330 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14331 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14332 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14333 sscanf(message, "hint: %c", &c)!=1 &&
14334 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14335 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14336 print = (appData.engineComments >= 2);
14338 message[0] = start; // restore original message
14342 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14343 SubtractTimeMarks(&now, &programStartTime), cps->which,
14349 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14350 if (appData.icsEngineAnalyze) {
14351 if (strstr(message, "whisper") != NULL ||
14352 strstr(message, "kibitz") != NULL ||
14353 strstr(message, "tellics") != NULL) return;
14356 HandleMachineMove(message, cps);
14361 SendTimeControl(cps, mps, tc, inc, sd, st)
14362 ChessProgramState *cps;
14363 int mps, inc, sd, st;
14369 if( timeControl_2 > 0 ) {
14370 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14371 tc = timeControl_2;
14374 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14375 inc /= cps->timeOdds;
14376 st /= cps->timeOdds;
14378 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14381 /* Set exact time per move, normally using st command */
14382 if (cps->stKludge) {
14383 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14385 if (seconds == 0) {
14386 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14388 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14391 snprintf(buf, MSG_SIZ, "st %d\n", st);
14394 /* Set conventional or incremental time control, using level command */
14395 if (seconds == 0) {
14396 /* Note old gnuchess bug -- minutes:seconds used to not work.
14397 Fixed in later versions, but still avoid :seconds
14398 when seconds is 0. */
14399 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14401 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14402 seconds, inc/1000.);
14405 SendToProgram(buf, cps);
14407 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14408 /* Orthogonally, limit search to given depth */
14410 if (cps->sdKludge) {
14411 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14413 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14415 SendToProgram(buf, cps);
14418 if(cps->nps >= 0) { /* [HGM] nps */
14419 if(cps->supportsNPS == FALSE)
14420 cps->nps = -1; // don't use if engine explicitly says not supported!
14422 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14423 SendToProgram(buf, cps);
14428 ChessProgramState *WhitePlayer()
14429 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14431 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14432 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14438 SendTimeRemaining(cps, machineWhite)
14439 ChessProgramState *cps;
14440 int /*boolean*/ machineWhite;
14442 char message[MSG_SIZ];
14445 /* Note: this routine must be called when the clocks are stopped
14446 or when they have *just* been set or switched; otherwise
14447 it will be off by the time since the current tick started.
14449 if (machineWhite) {
14450 time = whiteTimeRemaining / 10;
14451 otime = blackTimeRemaining / 10;
14453 time = blackTimeRemaining / 10;
14454 otime = whiteTimeRemaining / 10;
14456 /* [HGM] translate opponent's time by time-odds factor */
14457 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14458 if (appData.debugMode) {
14459 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14462 if (time <= 0) time = 1;
14463 if (otime <= 0) otime = 1;
14465 snprintf(message, MSG_SIZ, "time %ld\n", time);
14466 SendToProgram(message, cps);
14468 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14469 SendToProgram(message, cps);
14473 BoolFeature(p, name, loc, cps)
14477 ChessProgramState *cps;
14480 int len = strlen(name);
14483 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14485 sscanf(*p, "%d", &val);
14487 while (**p && **p != ' ')
14489 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14490 SendToProgram(buf, cps);
14497 IntFeature(p, name, loc, cps)
14501 ChessProgramState *cps;
14504 int len = strlen(name);
14505 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14507 sscanf(*p, "%d", loc);
14508 while (**p && **p != ' ') (*p)++;
14509 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14510 SendToProgram(buf, cps);
14517 StringFeature(p, name, loc, cps)
14521 ChessProgramState *cps;
14524 int len = strlen(name);
14525 if (strncmp((*p), name, len) == 0
14526 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14528 sscanf(*p, "%[^\"]", loc);
14529 while (**p && **p != '\"') (*p)++;
14530 if (**p == '\"') (*p)++;
14531 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14532 SendToProgram(buf, cps);
14539 ParseOption(Option *opt, ChessProgramState *cps)
14540 // [HGM] options: process the string that defines an engine option, and determine
14541 // name, type, default value, and allowed value range
14543 char *p, *q, buf[MSG_SIZ];
14544 int n, min = (-1)<<31, max = 1<<31, def;
14546 if(p = strstr(opt->name, " -spin ")) {
14547 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14548 if(max < min) max = min; // enforce consistency
14549 if(def < min) def = min;
14550 if(def > max) def = max;
14555 } else if((p = strstr(opt->name, " -slider "))) {
14556 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14557 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14558 if(max < min) max = min; // enforce consistency
14559 if(def < min) def = min;
14560 if(def > max) def = max;
14564 opt->type = Spin; // Slider;
14565 } else if((p = strstr(opt->name, " -string "))) {
14566 opt->textValue = p+9;
14567 opt->type = TextBox;
14568 } else if((p = strstr(opt->name, " -file "))) {
14569 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14570 opt->textValue = p+7;
14571 opt->type = FileName; // FileName;
14572 } else if((p = strstr(opt->name, " -path "))) {
14573 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14574 opt->textValue = p+7;
14575 opt->type = PathName; // PathName;
14576 } else if(p = strstr(opt->name, " -check ")) {
14577 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14578 opt->value = (def != 0);
14579 opt->type = CheckBox;
14580 } else if(p = strstr(opt->name, " -combo ")) {
14581 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14582 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14583 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14584 opt->value = n = 0;
14585 while(q = StrStr(q, " /// ")) {
14586 n++; *q = 0; // count choices, and null-terminate each of them
14588 if(*q == '*') { // remember default, which is marked with * prefix
14592 cps->comboList[cps->comboCnt++] = q;
14594 cps->comboList[cps->comboCnt++] = NULL;
14596 opt->type = ComboBox;
14597 } else if(p = strstr(opt->name, " -button")) {
14598 opt->type = Button;
14599 } else if(p = strstr(opt->name, " -save")) {
14600 opt->type = SaveButton;
14601 } else return FALSE;
14602 *p = 0; // terminate option name
14603 // now look if the command-line options define a setting for this engine option.
14604 if(cps->optionSettings && cps->optionSettings[0])
14605 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14606 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14607 snprintf(buf, MSG_SIZ, "option %s", p);
14608 if(p = strstr(buf, ",")) *p = 0;
14609 if(q = strchr(buf, '=')) switch(opt->type) {
14611 for(n=0; n<opt->max; n++)
14612 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14615 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14619 opt->value = atoi(q+1);
14624 SendToProgram(buf, cps);
14630 FeatureDone(cps, val)
14631 ChessProgramState* cps;
14634 DelayedEventCallback cb = GetDelayedEvent();
14635 if ((cb == InitBackEnd3 && cps == &first) ||
14636 (cb == SettingsMenuIfReady && cps == &second) ||
14637 (cb == LoadEngine) ||
14638 (cb == TwoMachinesEventIfReady)) {
14639 CancelDelayedEvent();
14640 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14642 cps->initDone = val;
14645 /* Parse feature command from engine */
14647 ParseFeatures(args, cps)
14649 ChessProgramState *cps;
14657 while (*p == ' ') p++;
14658 if (*p == NULLCHAR) return;
14660 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14661 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14662 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14663 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14664 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14665 if (BoolFeature(&p, "reuse", &val, cps)) {
14666 /* Engine can disable reuse, but can't enable it if user said no */
14667 if (!val) cps->reuse = FALSE;
14670 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14671 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14672 if (gameMode == TwoMachinesPlay) {
14673 DisplayTwoMachinesTitle();
14679 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14680 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14681 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14682 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14683 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14684 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14685 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14686 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14687 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14688 if (IntFeature(&p, "done", &val, cps)) {
14689 FeatureDone(cps, val);
14692 /* Added by Tord: */
14693 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14694 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14695 /* End of additions by Tord */
14697 /* [HGM] added features: */
14698 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14699 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14700 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14701 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14702 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14703 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14704 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14705 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14706 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14707 SendToProgram(buf, cps);
14710 if(cps->nrOptions >= MAX_OPTIONS) {
14712 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14713 DisplayError(buf, 0);
14717 /* End of additions by HGM */
14719 /* unknown feature: complain and skip */
14721 while (*q && *q != '=') q++;
14722 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14723 SendToProgram(buf, cps);
14729 while (*p && *p != '\"') p++;
14730 if (*p == '\"') p++;
14732 while (*p && *p != ' ') p++;
14740 PeriodicUpdatesEvent(newState)
14743 if (newState == appData.periodicUpdates)
14746 appData.periodicUpdates=newState;
14748 /* Display type changes, so update it now */
14749 // DisplayAnalysis();
14751 /* Get the ball rolling again... */
14753 AnalysisPeriodicEvent(1);
14754 StartAnalysisClock();
14759 PonderNextMoveEvent(newState)
14762 if (newState == appData.ponderNextMove) return;
14763 if (gameMode == EditPosition) EditPositionDone(TRUE);
14765 SendToProgram("hard\n", &first);
14766 if (gameMode == TwoMachinesPlay) {
14767 SendToProgram("hard\n", &second);
14770 SendToProgram("easy\n", &first);
14771 thinkOutput[0] = NULLCHAR;
14772 if (gameMode == TwoMachinesPlay) {
14773 SendToProgram("easy\n", &second);
14776 appData.ponderNextMove = newState;
14780 NewSettingEvent(option, feature, command, value)
14782 int option, value, *feature;
14786 if (gameMode == EditPosition) EditPositionDone(TRUE);
14787 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14788 if(feature == NULL || *feature) SendToProgram(buf, &first);
14789 if (gameMode == TwoMachinesPlay) {
14790 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14795 ShowThinkingEvent()
14796 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14798 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14799 int newState = appData.showThinking
14800 // [HGM] thinking: other features now need thinking output as well
14801 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14803 if (oldState == newState) return;
14804 oldState = newState;
14805 if (gameMode == EditPosition) EditPositionDone(TRUE);
14807 SendToProgram("post\n", &first);
14808 if (gameMode == TwoMachinesPlay) {
14809 SendToProgram("post\n", &second);
14812 SendToProgram("nopost\n", &first);
14813 thinkOutput[0] = NULLCHAR;
14814 if (gameMode == TwoMachinesPlay) {
14815 SendToProgram("nopost\n", &second);
14818 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14822 AskQuestionEvent(title, question, replyPrefix, which)
14823 char *title; char *question; char *replyPrefix; char *which;
14825 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14826 if (pr == NoProc) return;
14827 AskQuestion(title, question, replyPrefix, pr);
14831 TypeInEvent(char firstChar)
14833 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14834 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14835 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14836 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14837 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14838 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14839 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14840 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14841 gameMode == Training) PopUpMoveDialog(firstChar);
14845 TypeInDoneEvent(char *move)
14848 int n, fromX, fromY, toX, toY;
14850 ChessMove moveType;
\r
14853 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14854 EditPositionPasteFEN(move);
\r
14857 // [HGM] movenum: allow move number to be typed in any mode
\r
14858 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14859 ToNrEvent(2*n-1);
\r
14863 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14864 gameMode != Training) {
\r
14865 DisplayMoveError(_("Displayed move is not current"));
\r
14867 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14868 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14869 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14870 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14871 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14872 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14874 DisplayMoveError(_("Could not parse move"));
\r
14880 DisplayMove(moveNumber)
14883 char message[MSG_SIZ];
14885 char cpThinkOutput[MSG_SIZ];
14887 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14889 if (moveNumber == forwardMostMove - 1 ||
14890 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14892 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14894 if (strchr(cpThinkOutput, '\n')) {
14895 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14898 *cpThinkOutput = NULLCHAR;
14901 /* [AS] Hide thinking from human user */
14902 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14903 *cpThinkOutput = NULLCHAR;
14904 if( thinkOutput[0] != NULLCHAR ) {
14907 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14908 cpThinkOutput[i] = '.';
14910 cpThinkOutput[i] = NULLCHAR;
14911 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14915 if (moveNumber == forwardMostMove - 1 &&
14916 gameInfo.resultDetails != NULL) {
14917 if (gameInfo.resultDetails[0] == NULLCHAR) {
14918 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14920 snprintf(res, MSG_SIZ, " {%s} %s",
14921 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14927 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14928 DisplayMessage(res, cpThinkOutput);
14930 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14931 WhiteOnMove(moveNumber) ? " " : ".. ",
14932 parseList[moveNumber], res);
14933 DisplayMessage(message, cpThinkOutput);
14938 DisplayComment(moveNumber, text)
14942 char title[MSG_SIZ];
14943 char buf[8000]; // comment can be long!
14946 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14947 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14949 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14950 WhiteOnMove(moveNumber) ? " " : ".. ",
14951 parseList[moveNumber]);
14953 // [HGM] PV info: display PV info together with (or as) comment
14954 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14955 if(text == NULL) text = "";
14956 score = pvInfoList[moveNumber].score;
14957 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14958 depth, (pvInfoList[moveNumber].time+50)/100, text);
14961 if (text != NULL && (appData.autoDisplayComment || commentUp))
14962 CommentPopUp(title, text);
14965 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14966 * might be busy thinking or pondering. It can be omitted if your
14967 * gnuchess is configured to stop thinking immediately on any user
14968 * input. However, that gnuchess feature depends on the FIONREAD
14969 * ioctl, which does not work properly on some flavors of Unix.
14973 ChessProgramState *cps;
14976 if (!cps->useSigint) return;
14977 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14978 switch (gameMode) {
14979 case MachinePlaysWhite:
14980 case MachinePlaysBlack:
14981 case TwoMachinesPlay:
14982 case IcsPlayingWhite:
14983 case IcsPlayingBlack:
14986 /* Skip if we know it isn't thinking */
14987 if (!cps->maybeThinking) return;
14988 if (appData.debugMode)
14989 fprintf(debugFP, "Interrupting %s\n", cps->which);
14990 InterruptChildProcess(cps->pr);
14991 cps->maybeThinking = FALSE;
14996 #endif /*ATTENTION*/
15002 if (whiteTimeRemaining <= 0) {
15005 if (appData.icsActive) {
15006 if (appData.autoCallFlag &&
15007 gameMode == IcsPlayingBlack && !blackFlag) {
15008 SendToICS(ics_prefix);
15009 SendToICS("flag\n");
15013 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15015 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15016 if (appData.autoCallFlag) {
15017 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15024 if (blackTimeRemaining <= 0) {
15027 if (appData.icsActive) {
15028 if (appData.autoCallFlag &&
15029 gameMode == IcsPlayingWhite && !whiteFlag) {
15030 SendToICS(ics_prefix);
15031 SendToICS("flag\n");
15035 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15037 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15038 if (appData.autoCallFlag) {
15039 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15052 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15053 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15056 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15058 if ( !WhiteOnMove(forwardMostMove) ) {
15059 /* White made time control */
15060 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15061 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15062 /* [HGM] time odds: correct new time quota for time odds! */
15063 / WhitePlayer()->timeOdds;
15064 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15066 lastBlack -= blackTimeRemaining;
15067 /* Black made time control */
15068 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15069 / WhitePlayer()->other->timeOdds;
15070 lastWhite = whiteTimeRemaining;
15075 DisplayBothClocks()
15077 int wom = gameMode == EditPosition ?
15078 !blackPlaysFirst : WhiteOnMove(currentMove);
15079 DisplayWhiteClock(whiteTimeRemaining, wom);
15080 DisplayBlackClock(blackTimeRemaining, !wom);
15084 /* Timekeeping seems to be a portability nightmare. I think everyone
15085 has ftime(), but I'm really not sure, so I'm including some ifdefs
15086 to use other calls if you don't. Clocks will be less accurate if
15087 you have neither ftime nor gettimeofday.
15090 /* VS 2008 requires the #include outside of the function */
15091 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15092 #include <sys/timeb.h>
15095 /* Get the current time as a TimeMark */
15100 #if HAVE_GETTIMEOFDAY
15102 struct timeval timeVal;
15103 struct timezone timeZone;
15105 gettimeofday(&timeVal, &timeZone);
15106 tm->sec = (long) timeVal.tv_sec;
15107 tm->ms = (int) (timeVal.tv_usec / 1000L);
15109 #else /*!HAVE_GETTIMEOFDAY*/
15112 // include <sys/timeb.h> / moved to just above start of function
15113 struct timeb timeB;
15116 tm->sec = (long) timeB.time;
15117 tm->ms = (int) timeB.millitm;
15119 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15120 tm->sec = (long) time(NULL);
15126 /* Return the difference in milliseconds between two
15127 time marks. We assume the difference will fit in a long!
15130 SubtractTimeMarks(tm2, tm1)
15131 TimeMark *tm2, *tm1;
15133 return 1000L*(tm2->sec - tm1->sec) +
15134 (long) (tm2->ms - tm1->ms);
15139 * Code to manage the game clocks.
15141 * In tournament play, black starts the clock and then white makes a move.
15142 * We give the human user a slight advantage if he is playing white---the
15143 * clocks don't run until he makes his first move, so it takes zero time.
15144 * Also, we don't account for network lag, so we could get out of sync
15145 * with GNU Chess's clock -- but then, referees are always right.
15148 static TimeMark tickStartTM;
15149 static long intendedTickLength;
15152 NextTickLength(timeRemaining)
15153 long timeRemaining;
15155 long nominalTickLength, nextTickLength;
15157 if (timeRemaining > 0L && timeRemaining <= 10000L)
15158 nominalTickLength = 100L;
15160 nominalTickLength = 1000L;
15161 nextTickLength = timeRemaining % nominalTickLength;
15162 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15164 return nextTickLength;
15167 /* Adjust clock one minute up or down */
15169 AdjustClock(Boolean which, int dir)
15171 if(which) blackTimeRemaining += 60000*dir;
15172 else whiteTimeRemaining += 60000*dir;
15173 DisplayBothClocks();
15176 /* Stop clocks and reset to a fresh time control */
15180 (void) StopClockTimer();
15181 if (appData.icsActive) {
15182 whiteTimeRemaining = blackTimeRemaining = 0;
15183 } else if (searchTime) {
15184 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15185 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15186 } else { /* [HGM] correct new time quote for time odds */
15187 whiteTC = blackTC = fullTimeControlString;
15188 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15189 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15191 if (whiteFlag || blackFlag) {
15193 whiteFlag = blackFlag = FALSE;
15195 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15196 DisplayBothClocks();
15199 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15201 /* Decrement running clock by amount of time that has passed */
15205 long timeRemaining;
15206 long lastTickLength, fudge;
15209 if (!appData.clockMode) return;
15210 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15214 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15216 /* Fudge if we woke up a little too soon */
15217 fudge = intendedTickLength - lastTickLength;
15218 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15220 if (WhiteOnMove(forwardMostMove)) {
15221 if(whiteNPS >= 0) lastTickLength = 0;
15222 timeRemaining = whiteTimeRemaining -= lastTickLength;
15223 if(timeRemaining < 0 && !appData.icsActive) {
15224 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15225 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15226 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15227 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15230 DisplayWhiteClock(whiteTimeRemaining - fudge,
15231 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15233 if(blackNPS >= 0) lastTickLength = 0;
15234 timeRemaining = blackTimeRemaining -= lastTickLength;
15235 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15236 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15238 blackStartMove = forwardMostMove;
15239 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15242 DisplayBlackClock(blackTimeRemaining - fudge,
15243 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15245 if (CheckFlags()) return;
15248 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15249 StartClockTimer(intendedTickLength);
15251 /* if the time remaining has fallen below the alarm threshold, sound the
15252 * alarm. if the alarm has sounded and (due to a takeback or time control
15253 * with increment) the time remaining has increased to a level above the
15254 * threshold, reset the alarm so it can sound again.
15257 if (appData.icsActive && appData.icsAlarm) {
15259 /* make sure we are dealing with the user's clock */
15260 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15261 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15264 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15265 alarmSounded = FALSE;
15266 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15268 alarmSounded = TRUE;
15274 /* A player has just moved, so stop the previously running
15275 clock and (if in clock mode) start the other one.
15276 We redisplay both clocks in case we're in ICS mode, because
15277 ICS gives us an update to both clocks after every move.
15278 Note that this routine is called *after* forwardMostMove
15279 is updated, so the last fractional tick must be subtracted
15280 from the color that is *not* on move now.
15283 SwitchClocks(int newMoveNr)
15285 long lastTickLength;
15287 int flagged = FALSE;
15291 if (StopClockTimer() && appData.clockMode) {
15292 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15293 if (!WhiteOnMove(forwardMostMove)) {
15294 if(blackNPS >= 0) lastTickLength = 0;
15295 blackTimeRemaining -= lastTickLength;
15296 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15297 // if(pvInfoList[forwardMostMove].time == -1)
15298 pvInfoList[forwardMostMove].time = // use GUI time
15299 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15301 if(whiteNPS >= 0) lastTickLength = 0;
15302 whiteTimeRemaining -= lastTickLength;
15303 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15304 // if(pvInfoList[forwardMostMove].time == -1)
15305 pvInfoList[forwardMostMove].time =
15306 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15308 flagged = CheckFlags();
15310 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15311 CheckTimeControl();
15313 if (flagged || !appData.clockMode) return;
15315 switch (gameMode) {
15316 case MachinePlaysBlack:
15317 case MachinePlaysWhite:
15318 case BeginningOfGame:
15319 if (pausing) return;
15323 case PlayFromGameFile:
15331 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15332 if(WhiteOnMove(forwardMostMove))
15333 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15334 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15338 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15339 whiteTimeRemaining : blackTimeRemaining);
15340 StartClockTimer(intendedTickLength);
15344 /* Stop both clocks */
15348 long lastTickLength;
15351 if (!StopClockTimer()) return;
15352 if (!appData.clockMode) return;
15356 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15357 if (WhiteOnMove(forwardMostMove)) {
15358 if(whiteNPS >= 0) lastTickLength = 0;
15359 whiteTimeRemaining -= lastTickLength;
15360 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15362 if(blackNPS >= 0) lastTickLength = 0;
15363 blackTimeRemaining -= lastTickLength;
15364 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15369 /* Start clock of player on move. Time may have been reset, so
15370 if clock is already running, stop and restart it. */
15374 (void) StopClockTimer(); /* in case it was running already */
15375 DisplayBothClocks();
15376 if (CheckFlags()) return;
15378 if (!appData.clockMode) return;
15379 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15381 GetTimeMark(&tickStartTM);
15382 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15383 whiteTimeRemaining : blackTimeRemaining);
15385 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15386 whiteNPS = blackNPS = -1;
15387 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15388 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15389 whiteNPS = first.nps;
15390 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15391 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15392 blackNPS = first.nps;
15393 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15394 whiteNPS = second.nps;
15395 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15396 blackNPS = second.nps;
15397 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15399 StartClockTimer(intendedTickLength);
15406 long second, minute, hour, day;
15408 static char buf[32];
15410 if (ms > 0 && ms <= 9900) {
15411 /* convert milliseconds to tenths, rounding up */
15412 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15414 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15418 /* convert milliseconds to seconds, rounding up */
15419 /* use floating point to avoid strangeness of integer division
15420 with negative dividends on many machines */
15421 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15428 day = second / (60 * 60 * 24);
15429 second = second % (60 * 60 * 24);
15430 hour = second / (60 * 60);
15431 second = second % (60 * 60);
15432 minute = second / 60;
15433 second = second % 60;
15436 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15437 sign, day, hour, minute, second);
15439 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15441 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15448 * This is necessary because some C libraries aren't ANSI C compliant yet.
15451 StrStr(string, match)
15452 char *string, *match;
15456 length = strlen(match);
15458 for (i = strlen(string) - length; i >= 0; i--, string++)
15459 if (!strncmp(match, string, length))
15466 StrCaseStr(string, match)
15467 char *string, *match;
15471 length = strlen(match);
15473 for (i = strlen(string) - length; i >= 0; i--, string++) {
15474 for (j = 0; j < length; j++) {
15475 if (ToLower(match[j]) != ToLower(string[j]))
15478 if (j == length) return string;
15492 c1 = ToLower(*s1++);
15493 c2 = ToLower(*s2++);
15494 if (c1 > c2) return 1;
15495 if (c1 < c2) return -1;
15496 if (c1 == NULLCHAR) return 0;
15505 return isupper(c) ? tolower(c) : c;
15513 return islower(c) ? toupper(c) : c;
15515 #endif /* !_amigados */
15523 if ((ret = (char *) malloc(strlen(s) + 1)))
15525 safeStrCpy(ret, s, strlen(s)+1);
15531 StrSavePtr(s, savePtr)
15532 char *s, **savePtr;
15537 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15538 safeStrCpy(*savePtr, s, strlen(s)+1);
15550 clock = time((time_t *)NULL);
15551 tm = localtime(&clock);
15552 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15553 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15554 return StrSave(buf);
15559 PositionToFEN(move, overrideCastling)
15561 char *overrideCastling;
15563 int i, j, fromX, fromY, toX, toY;
15570 whiteToPlay = (gameMode == EditPosition) ?
15571 !blackPlaysFirst : (move % 2 == 0);
15574 /* Piece placement data */
15575 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15577 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15578 if (boards[move][i][j] == EmptySquare) {
15580 } else { ChessSquare piece = boards[move][i][j];
15581 if (emptycount > 0) {
15582 if(emptycount<10) /* [HGM] can be >= 10 */
15583 *p++ = '0' + emptycount;
15584 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15587 if(PieceToChar(piece) == '+') {
15588 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15590 piece = (ChessSquare)(DEMOTED piece);
15592 *p++ = PieceToChar(piece);
15594 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15595 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15600 if (emptycount > 0) {
15601 if(emptycount<10) /* [HGM] can be >= 10 */
15602 *p++ = '0' + emptycount;
15603 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15610 /* [HGM] print Crazyhouse or Shogi holdings */
15611 if( gameInfo.holdingsWidth ) {
15612 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15614 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15615 piece = boards[move][i][BOARD_WIDTH-1];
15616 if( piece != EmptySquare )
15617 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15618 *p++ = PieceToChar(piece);
15620 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15621 piece = boards[move][BOARD_HEIGHT-i-1][0];
15622 if( piece != EmptySquare )
15623 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15624 *p++ = PieceToChar(piece);
15627 if( q == p ) *p++ = '-';
15633 *p++ = whiteToPlay ? 'w' : 'b';
15636 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15637 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15639 if(nrCastlingRights) {
15641 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15642 /* [HGM] write directly from rights */
15643 if(boards[move][CASTLING][2] != NoRights &&
15644 boards[move][CASTLING][0] != NoRights )
15645 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15646 if(boards[move][CASTLING][2] != NoRights &&
15647 boards[move][CASTLING][1] != NoRights )
15648 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15649 if(boards[move][CASTLING][5] != NoRights &&
15650 boards[move][CASTLING][3] != NoRights )
15651 *p++ = boards[move][CASTLING][3] + AAA;
15652 if(boards[move][CASTLING][5] != NoRights &&
15653 boards[move][CASTLING][4] != NoRights )
15654 *p++ = boards[move][CASTLING][4] + AAA;
15657 /* [HGM] write true castling rights */
15658 if( nrCastlingRights == 6 ) {
15659 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15660 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15661 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15662 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15663 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15664 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15665 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15666 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15669 if (q == p) *p++ = '-'; /* No castling rights */
15673 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15674 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15675 /* En passant target square */
15676 if (move > backwardMostMove) {
15677 fromX = moveList[move - 1][0] - AAA;
15678 fromY = moveList[move - 1][1] - ONE;
15679 toX = moveList[move - 1][2] - AAA;
15680 toY = moveList[move - 1][3] - ONE;
15681 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15682 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15683 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15685 /* 2-square pawn move just happened */
15687 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15691 } else if(move == backwardMostMove) {
15692 // [HGM] perhaps we should always do it like this, and forget the above?
15693 if((signed char)boards[move][EP_STATUS] >= 0) {
15694 *p++ = boards[move][EP_STATUS] + AAA;
15695 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15706 /* [HGM] find reversible plies */
15707 { int i = 0, j=move;
15709 if (appData.debugMode) { int k;
15710 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15711 for(k=backwardMostMove; k<=forwardMostMove; k++)
15712 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15716 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15717 if( j == backwardMostMove ) i += initialRulePlies;
15718 sprintf(p, "%d ", i);
15719 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15721 /* Fullmove number */
15722 sprintf(p, "%d", (move / 2) + 1);
15724 return StrSave(buf);
15728 ParseFEN(board, blackPlaysFirst, fen)
15730 int *blackPlaysFirst;
15740 /* [HGM] by default clear Crazyhouse holdings, if present */
15741 if(gameInfo.holdingsWidth) {
15742 for(i=0; i<BOARD_HEIGHT; i++) {
15743 board[i][0] = EmptySquare; /* black holdings */
15744 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15745 board[i][1] = (ChessSquare) 0; /* black counts */
15746 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15750 /* Piece placement data */
15751 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15754 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15755 if (*p == '/') p++;
15756 emptycount = gameInfo.boardWidth - j;
15757 while (emptycount--)
15758 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15760 #if(BOARD_FILES >= 10)
15761 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15762 p++; emptycount=10;
15763 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15764 while (emptycount--)
15765 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15767 } else if (isdigit(*p)) {
15768 emptycount = *p++ - '0';
15769 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15770 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15771 while (emptycount--)
15772 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15773 } else if (*p == '+' || isalpha(*p)) {
15774 if (j >= gameInfo.boardWidth) return FALSE;
15776 piece = CharToPiece(*++p);
15777 if(piece == EmptySquare) return FALSE; /* unknown piece */
15778 piece = (ChessSquare) (PROMOTED piece ); p++;
15779 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15780 } else piece = CharToPiece(*p++);
15782 if(piece==EmptySquare) return FALSE; /* unknown piece */
15783 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15784 piece = (ChessSquare) (PROMOTED piece);
15785 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15788 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15794 while (*p == '/' || *p == ' ') p++;
15796 /* [HGM] look for Crazyhouse holdings here */
15797 while(*p==' ') p++;
15798 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15800 if(*p == '-' ) p++; /* empty holdings */ else {
15801 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15802 /* if we would allow FEN reading to set board size, we would */
15803 /* have to add holdings and shift the board read so far here */
15804 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15806 if((int) piece >= (int) BlackPawn ) {
15807 i = (int)piece - (int)BlackPawn;
15808 i = PieceToNumber((ChessSquare)i);
15809 if( i >= gameInfo.holdingsSize ) return FALSE;
15810 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15811 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15813 i = (int)piece - (int)WhitePawn;
15814 i = PieceToNumber((ChessSquare)i);
15815 if( i >= gameInfo.holdingsSize ) return FALSE;
15816 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15817 board[i][BOARD_WIDTH-2]++; /* black holdings */
15824 while(*p == ' ') p++;
15828 if(appData.colorNickNames) {
15829 if( c == appData.colorNickNames[0] ) c = 'w'; else
15830 if( c == appData.colorNickNames[1] ) c = 'b';
15834 *blackPlaysFirst = FALSE;
15837 *blackPlaysFirst = TRUE;
15843 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15844 /* return the extra info in global variiables */
15846 /* set defaults in case FEN is incomplete */
15847 board[EP_STATUS] = EP_UNKNOWN;
15848 for(i=0; i<nrCastlingRights; i++ ) {
15849 board[CASTLING][i] =
15850 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15851 } /* assume possible unless obviously impossible */
15852 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15853 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15854 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15855 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15856 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15857 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15858 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15859 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15862 while(*p==' ') p++;
15863 if(nrCastlingRights) {
15864 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15865 /* castling indicator present, so default becomes no castlings */
15866 for(i=0; i<nrCastlingRights; i++ ) {
15867 board[CASTLING][i] = NoRights;
15870 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15871 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15872 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15873 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15874 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15876 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15877 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15878 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15880 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15881 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15882 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15883 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15884 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15885 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15888 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15889 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15890 board[CASTLING][2] = whiteKingFile;
15893 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15894 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15895 board[CASTLING][2] = whiteKingFile;
15898 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15899 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15900 board[CASTLING][5] = blackKingFile;
15903 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15904 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15905 board[CASTLING][5] = blackKingFile;
15908 default: /* FRC castlings */
15909 if(c >= 'a') { /* black rights */
15910 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15911 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15912 if(i == BOARD_RGHT) break;
15913 board[CASTLING][5] = i;
15915 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15916 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15918 board[CASTLING][3] = c;
15920 board[CASTLING][4] = c;
15921 } else { /* white rights */
15922 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15923 if(board[0][i] == WhiteKing) break;
15924 if(i == BOARD_RGHT) break;
15925 board[CASTLING][2] = i;
15926 c -= AAA - 'a' + 'A';
15927 if(board[0][c] >= WhiteKing) break;
15929 board[CASTLING][0] = c;
15931 board[CASTLING][1] = c;
15935 for(i=0; i<nrCastlingRights; i++)
15936 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15937 if (appData.debugMode) {
15938 fprintf(debugFP, "FEN castling rights:");
15939 for(i=0; i<nrCastlingRights; i++)
15940 fprintf(debugFP, " %d", board[CASTLING][i]);
15941 fprintf(debugFP, "\n");
15944 while(*p==' ') p++;
15947 /* read e.p. field in games that know e.p. capture */
15948 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15949 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15951 p++; board[EP_STATUS] = EP_NONE;
15953 char c = *p++ - AAA;
15955 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15956 if(*p >= '0' && *p <='9') p++;
15957 board[EP_STATUS] = c;
15962 if(sscanf(p, "%d", &i) == 1) {
15963 FENrulePlies = i; /* 50-move ply counter */
15964 /* (The move number is still ignored) */
15971 EditPositionPasteFEN(char *fen)
15974 Board initial_position;
15976 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15977 DisplayError(_("Bad FEN position in clipboard"), 0);
15980 int savedBlackPlaysFirst = blackPlaysFirst;
15981 EditPositionEvent();
15982 blackPlaysFirst = savedBlackPlaysFirst;
15983 CopyBoard(boards[0], initial_position);
15984 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15985 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15986 DisplayBothClocks();
15987 DrawPosition(FALSE, boards[currentMove]);
15992 static char cseq[12] = "\\ ";
15994 Boolean set_cont_sequence(char *new_seq)
15999 // handle bad attempts to set the sequence
16001 return 0; // acceptable error - no debug
16003 len = strlen(new_seq);
16004 ret = (len > 0) && (len < sizeof(cseq));
16006 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16007 else if (appData.debugMode)
16008 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16013 reformat a source message so words don't cross the width boundary. internal
16014 newlines are not removed. returns the wrapped size (no null character unless
16015 included in source message). If dest is NULL, only calculate the size required
16016 for the dest buffer. lp argument indicats line position upon entry, and it's
16017 passed back upon exit.
16019 int wrap(char *dest, char *src, int count, int width, int *lp)
16021 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16023 cseq_len = strlen(cseq);
16024 old_line = line = *lp;
16025 ansi = len = clen = 0;
16027 for (i=0; i < count; i++)
16029 if (src[i] == '\033')
16032 // if we hit the width, back up
16033 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16035 // store i & len in case the word is too long
16036 old_i = i, old_len = len;
16038 // find the end of the last word
16039 while (i && src[i] != ' ' && src[i] != '\n')
16045 // word too long? restore i & len before splitting it
16046 if ((old_i-i+clen) >= width)
16053 if (i && src[i-1] == ' ')
16056 if (src[i] != ' ' && src[i] != '\n')
16063 // now append the newline and continuation sequence
16068 strncpy(dest+len, cseq, cseq_len);
16076 dest[len] = src[i];
16080 if (src[i] == '\n')
16085 if (dest && appData.debugMode)
16087 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16088 count, width, line, len, *lp);
16089 show_bytes(debugFP, src, count);
16090 fprintf(debugFP, "\ndest: ");
16091 show_bytes(debugFP, dest, len);
16092 fprintf(debugFP, "\n");
16094 *lp = dest ? line : old_line;
16099 // [HGM] vari: routines for shelving variations
16102 PushInner(int firstMove, int lastMove)
16104 int i, j, nrMoves = lastMove - firstMove;
16106 // push current tail of game on stack
16107 savedResult[storedGames] = gameInfo.result;
16108 savedDetails[storedGames] = gameInfo.resultDetails;
16109 gameInfo.resultDetails = NULL;
16110 savedFirst[storedGames] = firstMove;
16111 savedLast [storedGames] = lastMove;
16112 savedFramePtr[storedGames] = framePtr;
16113 framePtr -= nrMoves; // reserve space for the boards
16114 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16115 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16116 for(j=0; j<MOVE_LEN; j++)
16117 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16118 for(j=0; j<2*MOVE_LEN; j++)
16119 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16120 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16121 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16122 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16123 pvInfoList[firstMove+i-1].depth = 0;
16124 commentList[framePtr+i] = commentList[firstMove+i];
16125 commentList[firstMove+i] = NULL;
16129 forwardMostMove = firstMove; // truncate game so we can start variation
16133 PushTail(int firstMove, int lastMove)
16135 if(appData.icsActive) { // only in local mode
16136 forwardMostMove = currentMove; // mimic old ICS behavior
16139 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16141 PushInner(firstMove, lastMove);
16142 if(storedGames == 1) GreyRevert(FALSE);
16146 PopInner(Boolean annotate)
16149 char buf[8000], moveBuf[20];
16152 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16153 nrMoves = savedLast[storedGames] - currentMove;
16156 if(!WhiteOnMove(currentMove))
16157 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16158 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16159 for(i=currentMove; i<forwardMostMove; i++) {
16161 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16162 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16163 strcat(buf, moveBuf);
16164 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16165 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16169 for(i=1; i<=nrMoves; i++) { // copy last variation back
16170 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16171 for(j=0; j<MOVE_LEN; j++)
16172 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16173 for(j=0; j<2*MOVE_LEN; j++)
16174 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16175 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16176 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16177 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16178 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16179 commentList[currentMove+i] = commentList[framePtr+i];
16180 commentList[framePtr+i] = NULL;
16182 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16183 framePtr = savedFramePtr[storedGames];
16184 gameInfo.result = savedResult[storedGames];
16185 if(gameInfo.resultDetails != NULL) {
16186 free(gameInfo.resultDetails);
16188 gameInfo.resultDetails = savedDetails[storedGames];
16189 forwardMostMove = currentMove + nrMoves;
16193 PopTail(Boolean annotate)
16195 if(appData.icsActive) return FALSE; // only in local mode
16196 if(!storedGames) return FALSE; // sanity
16197 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16199 PopInner(annotate);
16201 if(storedGames == 0) GreyRevert(TRUE);
16207 { // remove all shelved variations
16209 for(i=0; i<storedGames; i++) {
16210 if(savedDetails[i])
16211 free(savedDetails[i]);
16212 savedDetails[i] = NULL;
16214 for(i=framePtr; i<MAX_MOVES; i++) {
16215 if(commentList[i]) free(commentList[i]);
16216 commentList[i] = NULL;
16218 framePtr = MAX_MOVES-1;
16223 LoadVariation(int index, char *text)
16224 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16225 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16226 int level = 0, move;
16228 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16229 // first find outermost bracketing variation
16230 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16231 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16232 if(*p == '{') wait = '}'; else
16233 if(*p == '[') wait = ']'; else
16234 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16235 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16237 if(*p == wait) wait = NULLCHAR; // closing ]} found
16240 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16241 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16242 end[1] = NULLCHAR; // clip off comment beyond variation
16243 ToNrEvent(currentMove-1);
16244 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16245 // kludge: use ParsePV() to append variation to game
16246 move = currentMove;
16247 ParsePV(start, TRUE);
16248 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16249 ClearPremoveHighlights();
16251 ToNrEvent(currentMove+1);