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);
9253 DisplayBook(currentMove);
9256 void SendEgtPath(ChessProgramState *cps)
9257 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9258 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9260 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9263 char c, *q = name+1, *r, *s;
9265 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9266 while(*p && *p != ',') *q++ = *p++;
9268 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9269 strcmp(name, ",nalimov:") == 0 ) {
9270 // take nalimov path from the menu-changeable option first, if it is defined
9271 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9272 SendToProgram(buf,cps); // send egtbpath command for nalimov
9274 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9275 (s = StrStr(appData.egtFormats, name)) != NULL) {
9276 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9277 s = r = StrStr(s, ":") + 1; // beginning of path info
9278 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9279 c = *r; *r = 0; // temporarily null-terminate path info
9280 *--q = 0; // strip of trailig ':' from name
9281 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9283 SendToProgram(buf,cps); // send egtbpath command for this format
9285 if(*p == ',') p++; // read away comma to position for next format name
9290 InitChessProgram(cps, setup)
9291 ChessProgramState *cps;
9292 int setup; /* [HGM] needed to setup FRC opening position */
9294 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9295 if (appData.noChessProgram) return;
9296 hintRequested = FALSE;
9297 bookRequested = FALSE;
9299 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9300 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9301 if(cps->memSize) { /* [HGM] memory */
9302 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9303 SendToProgram(buf, cps);
9305 SendEgtPath(cps); /* [HGM] EGT */
9306 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9307 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9308 SendToProgram(buf, cps);
9311 SendToProgram(cps->initString, cps);
9312 if (gameInfo.variant != VariantNormal &&
9313 gameInfo.variant != VariantLoadable
9314 /* [HGM] also send variant if board size non-standard */
9315 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9317 char *v = VariantName(gameInfo.variant);
9318 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9319 /* [HGM] in protocol 1 we have to assume all variants valid */
9320 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9321 DisplayFatalError(buf, 0, 1);
9325 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9326 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9327 if( gameInfo.variant == VariantXiangqi )
9328 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9329 if( gameInfo.variant == VariantShogi )
9330 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9331 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9332 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9333 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9334 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9335 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9336 if( gameInfo.variant == VariantCourier )
9337 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9338 if( gameInfo.variant == VariantSuper )
9339 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9340 if( gameInfo.variant == VariantGreat )
9341 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9342 if( gameInfo.variant == VariantSChess )
9343 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9346 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9347 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9348 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9349 if(StrStr(cps->variants, b) == NULL) {
9350 // specific sized variant not known, check if general sizing allowed
9351 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9352 if(StrStr(cps->variants, "boardsize") == NULL) {
9353 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9354 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9355 DisplayFatalError(buf, 0, 1);
9358 /* [HGM] here we really should compare with the maximum supported board size */
9361 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9362 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9363 SendToProgram(buf, cps);
9365 currentlyInitializedVariant = gameInfo.variant;
9367 /* [HGM] send opening position in FRC to first engine */
9369 SendToProgram("force\n", cps);
9371 /* engine is now in force mode! Set flag to wake it up after first move. */
9372 setboardSpoiledMachineBlack = 1;
9376 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9377 SendToProgram(buf, cps);
9379 cps->maybeThinking = FALSE;
9380 cps->offeredDraw = 0;
9381 if (!appData.icsActive) {
9382 SendTimeControl(cps, movesPerSession, timeControl,
9383 timeIncrement, appData.searchDepth,
9386 if (appData.showThinking
9387 // [HGM] thinking: four options require thinking output to be sent
9388 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9390 SendToProgram("post\n", cps);
9392 SendToProgram("hard\n", cps);
9393 if (!appData.ponderNextMove) {
9394 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9395 it without being sure what state we are in first. "hard"
9396 is not a toggle, so that one is OK.
9398 SendToProgram("easy\n", cps);
9401 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9402 SendToProgram(buf, cps);
9404 cps->initDone = TRUE;
9409 StartChessProgram(cps)
9410 ChessProgramState *cps;
9415 if (appData.noChessProgram) return;
9416 cps->initDone = FALSE;
9418 if (strcmp(cps->host, "localhost") == 0) {
9419 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9420 } else if (*appData.remoteShell == NULLCHAR) {
9421 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9423 if (*appData.remoteUser == NULLCHAR) {
9424 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9427 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9428 cps->host, appData.remoteUser, cps->program);
9430 err = StartChildProcess(buf, "", &cps->pr);
9434 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9435 DisplayFatalError(buf, err, 1);
9441 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9442 if (cps->protocolVersion > 1) {
9443 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9444 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9445 cps->comboCnt = 0; // and values of combo boxes
9446 SendToProgram(buf, cps);
9448 SendToProgram("xboard\n", cps);
9453 TwoMachinesEventIfReady P((void))
9455 static int curMess = 0;
9456 if (first.lastPing != first.lastPong) {
9457 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9458 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9461 if (second.lastPing != second.lastPong) {
9462 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9463 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9466 DisplayMessage("", ""); curMess = 0;
9472 CreateTourney(char *name)
9475 if(name[0] == NULLCHAR) return 0;
9476 f = fopen(appData.tourneyFile, "r");
9477 if(f) { // file exists
9478 ParseArgsFromFile(f); // parse it
9480 f = fopen(appData.tourneyFile, "w");
9481 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9482 // create a file with tournament description
9483 fprintf(f, "-participants {%s}\n", appData.participants);
9484 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9485 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9486 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9487 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9488 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9489 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9490 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9491 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9492 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9493 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9494 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9496 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9498 fprintf(f, "-mps %d\n", appData.movesPerSession);
9499 fprintf(f, "-tc %s\n", appData.timeControl);
9500 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9502 fprintf(f, "-results \"\"\n");
9506 appData.noChessProgram = FALSE;
9507 appData.clockMode = TRUE;
9512 #define MAXENGINES 1000
9513 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9515 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9517 char buf[MSG_SIZ], *p, *q;
9521 while(*p && *p != '\n') *q++ = *p++;
9523 if(engineList[i]) free(engineList[i]);
9524 engineList[i] = strdup(buf);
9526 TidyProgramName(engineList[i], "localhost", buf);
9527 if(engineMnemonic[i]) free(engineMnemonic[i]);
9528 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9530 sscanf(q + 8, "%s", buf + strlen(buf));
9533 engineMnemonic[i] = strdup(buf);
9535 if(i > MAXENGINES - 2) break;
9537 engineList[i] = NULL;
9540 // following implemented as macro to avoid type limitations
9541 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9543 void SwapEngines(int n)
9544 { // swap settings for first engine and other engine (so far only some selected options)
9549 SWAP(chessProgram, p)
9551 SWAP(hasOwnBookUCI, h)
9552 SWAP(protocolVersion, h)
9554 SWAP(scoreIsAbsolute, h)
9561 SetPlayer(int player)
9562 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9564 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9565 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9566 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9567 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9569 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9570 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9571 ParseArgsFromString(buf);
9577 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9578 { // determine players from game number
9579 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9581 if(appData.tourneyType == 0) {
9582 roundsPerCycle = (nPlayers - 1) | 1;
9583 pairingsPerRound = nPlayers / 2;
9584 } else if(appData.tourneyType > 0) {
9585 roundsPerCycle = nPlayers - appData.tourneyType;
9586 pairingsPerRound = appData.tourneyType;
9588 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9589 gamesPerCycle = gamesPerRound * roundsPerCycle;
9590 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9591 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9592 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9593 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9594 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9595 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9597 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9598 if(appData.roundSync) *syncInterval = gamesPerRound;
9600 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9602 if(appData.tourneyType == 0) {
9603 if(curPairing == (nPlayers-1)/2 ) {
9604 *whitePlayer = curRound;
9605 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9607 *whitePlayer = curRound - pairingsPerRound + curPairing;
9608 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9609 *blackPlayer = curRound + pairingsPerRound - curPairing;
9610 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9612 } else if(appData.tourneyType > 0) {
9613 *whitePlayer = curPairing;
9614 *blackPlayer = curRound + appData.tourneyType;
9617 // take care of white/black alternation per round.
9618 // For cycles and games this is already taken care of by default, derived from matchGame!
9619 return curRound & 1;
9623 NextTourneyGame(int nr, int *swapColors)
9624 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9626 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9628 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9629 tf = fopen(appData.tourneyFile, "r");
9630 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9631 ParseArgsFromFile(tf); fclose(tf);
9632 InitTimeControls(); // TC might be altered from tourney file
9634 p = appData.participants;
9635 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9636 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9639 p = q = appData.results;
9640 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9641 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9642 DisplayMessage(_("Waiting for other game(s)"),"");
9643 waitingForGame = TRUE;
9644 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9647 waitingForGame = FALSE;
9650 if(first.pr != NoProc) return 1; // engines already loaded
9652 // redefine engines, engine dir, etc.
9653 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9654 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9656 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9657 SwapEngines(1); // and make that valid for second engine by swapping
9658 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9659 InitEngine(&second, 1);
9660 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9666 { // performs game initialization that does not invoke engines, and then tries to start the game
9667 int firstWhite, swapColors = 0;
9668 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9669 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9670 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9671 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9672 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9673 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9674 Reset(FALSE, first.pr != NoProc);
9675 appData.noChessProgram = FALSE;
9676 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9680 void UserAdjudicationEvent( int result )
9682 ChessMove gameResult = GameIsDrawn;
9685 gameResult = WhiteWins;
9687 else if( result < 0 ) {
9688 gameResult = BlackWins;
9691 if( gameMode == TwoMachinesPlay ) {
9692 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9697 // [HGM] save: calculate checksum of game to make games easily identifiable
9698 int StringCheckSum(char *s)
9701 if(s==NULL) return 0;
9702 while(*s) i = i*259 + *s++;
9709 for(i=backwardMostMove; i<forwardMostMove; i++) {
9710 sum += pvInfoList[i].depth;
9711 sum += StringCheckSum(parseList[i]);
9712 sum += StringCheckSum(commentList[i]);
9715 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9716 return sum + StringCheckSum(commentList[i]);
9717 } // end of save patch
9720 GameEnds(result, resultDetails, whosays)
9722 char *resultDetails;
9725 GameMode nextGameMode;
9727 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9729 if(endingGame) return; /* [HGM] crash: forbid recursion */
9731 if(twoBoards) { // [HGM] dual: switch back to one board
9732 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9733 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9735 if (appData.debugMode) {
9736 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9737 result, resultDetails ? resultDetails : "(null)", whosays);
9740 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9742 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9743 /* If we are playing on ICS, the server decides when the
9744 game is over, but the engine can offer to draw, claim
9748 if (appData.zippyPlay && first.initDone) {
9749 if (result == GameIsDrawn) {
9750 /* In case draw still needs to be claimed */
9751 SendToICS(ics_prefix);
9752 SendToICS("draw\n");
9753 } else if (StrCaseStr(resultDetails, "resign")) {
9754 SendToICS(ics_prefix);
9755 SendToICS("resign\n");
9759 endingGame = 0; /* [HGM] crash */
9763 /* If we're loading the game from a file, stop */
9764 if (whosays == GE_FILE) {
9765 (void) StopLoadGameTimer();
9769 /* Cancel draw offers */
9770 first.offeredDraw = second.offeredDraw = 0;
9772 /* If this is an ICS game, only ICS can really say it's done;
9773 if not, anyone can. */
9774 isIcsGame = (gameMode == IcsPlayingWhite ||
9775 gameMode == IcsPlayingBlack ||
9776 gameMode == IcsObserving ||
9777 gameMode == IcsExamining);
9779 if (!isIcsGame || whosays == GE_ICS) {
9780 /* OK -- not an ICS game, or ICS said it was done */
9782 if (!isIcsGame && !appData.noChessProgram)
9783 SetUserThinkingEnables();
9785 /* [HGM] if a machine claims the game end we verify this claim */
9786 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9787 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9789 ChessMove trueResult = (ChessMove) -1;
9791 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9792 first.twoMachinesColor[0] :
9793 second.twoMachinesColor[0] ;
9795 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9796 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9797 /* [HGM] verify: engine mate claims accepted if they were flagged */
9798 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9800 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9801 /* [HGM] verify: engine mate claims accepted if they were flagged */
9802 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9804 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9805 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9808 // now verify win claims, but not in drop games, as we don't understand those yet
9809 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9810 || gameInfo.variant == VariantGreat) &&
9811 (result == WhiteWins && claimer == 'w' ||
9812 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9813 if (appData.debugMode) {
9814 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9815 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9817 if(result != trueResult) {
9818 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9819 result = claimer == 'w' ? BlackWins : WhiteWins;
9820 resultDetails = buf;
9823 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9824 && (forwardMostMove <= backwardMostMove ||
9825 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9826 (claimer=='b')==(forwardMostMove&1))
9828 /* [HGM] verify: draws that were not flagged are false claims */
9829 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9830 result = claimer == 'w' ? BlackWins : WhiteWins;
9831 resultDetails = buf;
9833 /* (Claiming a loss is accepted no questions asked!) */
9835 /* [HGM] bare: don't allow bare King to win */
9836 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9837 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9838 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9839 && result != GameIsDrawn)
9840 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9841 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9842 int p = (signed char)boards[forwardMostMove][i][j] - color;
9843 if(p >= 0 && p <= (int)WhiteKing) k++;
9845 if (appData.debugMode) {
9846 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9847 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9850 result = GameIsDrawn;
9851 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9852 resultDetails = buf;
9858 if(serverMoves != NULL && !loadFlag) { char c = '=';
9859 if(result==WhiteWins) c = '+';
9860 if(result==BlackWins) c = '-';
9861 if(resultDetails != NULL)
9862 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9864 if (resultDetails != NULL) {
9865 gameInfo.result = result;
9866 gameInfo.resultDetails = StrSave(resultDetails);
9868 /* display last move only if game was not loaded from file */
9869 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9870 DisplayMove(currentMove - 1);
9872 if (forwardMostMove != 0) {
9873 if (gameMode != PlayFromGameFile && gameMode != EditGame
9874 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9876 if (*appData.saveGameFile != NULLCHAR) {
9877 SaveGameToFile(appData.saveGameFile, TRUE);
9878 } else if (appData.autoSaveGames) {
9881 if (*appData.savePositionFile != NULLCHAR) {
9882 SavePositionToFile(appData.savePositionFile);
9887 /* Tell program how game ended in case it is learning */
9888 /* [HGM] Moved this to after saving the PGN, just in case */
9889 /* engine died and we got here through time loss. In that */
9890 /* case we will get a fatal error writing the pipe, which */
9891 /* would otherwise lose us the PGN. */
9892 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9893 /* output during GameEnds should never be fatal anymore */
9894 if (gameMode == MachinePlaysWhite ||
9895 gameMode == MachinePlaysBlack ||
9896 gameMode == TwoMachinesPlay ||
9897 gameMode == IcsPlayingWhite ||
9898 gameMode == IcsPlayingBlack ||
9899 gameMode == BeginningOfGame) {
9901 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9903 if (first.pr != NoProc) {
9904 SendToProgram(buf, &first);
9906 if (second.pr != NoProc &&
9907 gameMode == TwoMachinesPlay) {
9908 SendToProgram(buf, &second);
9913 if (appData.icsActive) {
9914 if (appData.quietPlay &&
9915 (gameMode == IcsPlayingWhite ||
9916 gameMode == IcsPlayingBlack)) {
9917 SendToICS(ics_prefix);
9918 SendToICS("set shout 1\n");
9920 nextGameMode = IcsIdle;
9921 ics_user_moved = FALSE;
9922 /* clean up premove. It's ugly when the game has ended and the
9923 * premove highlights are still on the board.
9927 ClearPremoveHighlights();
9928 DrawPosition(FALSE, boards[currentMove]);
9930 if (whosays == GE_ICS) {
9933 if (gameMode == IcsPlayingWhite)
9935 else if(gameMode == IcsPlayingBlack)
9939 if (gameMode == IcsPlayingBlack)
9941 else if(gameMode == IcsPlayingWhite)
9948 PlayIcsUnfinishedSound();
9951 } else if (gameMode == EditGame ||
9952 gameMode == PlayFromGameFile ||
9953 gameMode == AnalyzeMode ||
9954 gameMode == AnalyzeFile) {
9955 nextGameMode = gameMode;
9957 nextGameMode = EndOfGame;
9962 nextGameMode = gameMode;
9965 if (appData.noChessProgram) {
9966 gameMode = nextGameMode;
9968 endingGame = 0; /* [HGM] crash */
9973 /* Put first chess program into idle state */
9974 if (first.pr != NoProc &&
9975 (gameMode == MachinePlaysWhite ||
9976 gameMode == MachinePlaysBlack ||
9977 gameMode == TwoMachinesPlay ||
9978 gameMode == IcsPlayingWhite ||
9979 gameMode == IcsPlayingBlack ||
9980 gameMode == BeginningOfGame)) {
9981 SendToProgram("force\n", &first);
9982 if (first.usePing) {
9984 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9985 SendToProgram(buf, &first);
9988 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9989 /* Kill off first chess program */
9990 if (first.isr != NULL)
9991 RemoveInputSource(first.isr);
9994 if (first.pr != NoProc) {
9996 DoSleep( appData.delayBeforeQuit );
9997 SendToProgram("quit\n", &first);
9998 DoSleep( appData.delayAfterQuit );
9999 DestroyChildProcess(first.pr, first.useSigterm);
10003 if (second.reuse) {
10004 /* Put second chess program into idle state */
10005 if (second.pr != NoProc &&
10006 gameMode == TwoMachinesPlay) {
10007 SendToProgram("force\n", &second);
10008 if (second.usePing) {
10010 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10011 SendToProgram(buf, &second);
10014 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10015 /* Kill off second chess program */
10016 if (second.isr != NULL)
10017 RemoveInputSource(second.isr);
10020 if (second.pr != NoProc) {
10021 DoSleep( appData.delayBeforeQuit );
10022 SendToProgram("quit\n", &second);
10023 DoSleep( appData.delayAfterQuit );
10024 DestroyChildProcess(second.pr, second.useSigterm);
10026 second.pr = NoProc;
10029 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10030 char resChar = '=';
10034 if (first.twoMachinesColor[0] == 'w') {
10037 second.matchWins++;
10042 if (first.twoMachinesColor[0] == 'b') {
10045 second.matchWins++;
10048 case GameUnfinished:
10054 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10055 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10056 ReserveGame(nextGame, resChar); // sets nextGame
10057 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10058 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10060 if (nextGame <= appData.matchGames && !abortMatch) {
10061 gameMode = nextGameMode;
10062 matchGame = nextGame; // this will be overruled in tourney mode!
10063 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10064 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10065 endingGame = 0; /* [HGM] crash */
10068 gameMode = nextGameMode;
10069 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10070 first.tidy, second.tidy,
10071 first.matchWins, second.matchWins,
10072 appData.matchGames - (first.matchWins + second.matchWins));
10073 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10074 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10075 first.twoMachinesColor = "black\n";
10076 second.twoMachinesColor = "white\n";
10078 first.twoMachinesColor = "white\n";
10079 second.twoMachinesColor = "black\n";
10083 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10084 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10086 gameMode = nextGameMode;
10088 endingGame = 0; /* [HGM] crash */
10089 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10090 if(matchMode == TRUE) { // match through command line: exit with or without popup
10092 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10094 } else DisplayFatalError(buf, 0, 0);
10095 } else { // match through menu; just stop, with or without popup
10096 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10098 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10099 } else DisplayNote(buf);
10101 if(ranking) free(ranking);
10105 /* Assumes program was just initialized (initString sent).
10106 Leaves program in force mode. */
10108 FeedMovesToProgram(cps, upto)
10109 ChessProgramState *cps;
10114 if (appData.debugMode)
10115 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10116 startedFromSetupPosition ? "position and " : "",
10117 backwardMostMove, upto, cps->which);
10118 if(currentlyInitializedVariant != gameInfo.variant) {
10120 // [HGM] variantswitch: make engine aware of new variant
10121 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10122 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10123 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10124 SendToProgram(buf, cps);
10125 currentlyInitializedVariant = gameInfo.variant;
10127 SendToProgram("force\n", cps);
10128 if (startedFromSetupPosition) {
10129 SendBoard(cps, backwardMostMove);
10130 if (appData.debugMode) {
10131 fprintf(debugFP, "feedMoves\n");
10134 for (i = backwardMostMove; i < upto; i++) {
10135 SendMoveToProgram(i, cps);
10141 ResurrectChessProgram()
10143 /* The chess program may have exited.
10144 If so, restart it and feed it all the moves made so far. */
10145 static int doInit = 0;
10147 if (appData.noChessProgram) return 1;
10149 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10150 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10151 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10152 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10154 if (first.pr != NoProc) return 1;
10155 StartChessProgram(&first);
10157 InitChessProgram(&first, FALSE);
10158 FeedMovesToProgram(&first, currentMove);
10160 if (!first.sendTime) {
10161 /* can't tell gnuchess what its clock should read,
10162 so we bow to its notion. */
10164 timeRemaining[0][currentMove] = whiteTimeRemaining;
10165 timeRemaining[1][currentMove] = blackTimeRemaining;
10168 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10169 appData.icsEngineAnalyze) && first.analysisSupport) {
10170 SendToProgram("analyze\n", &first);
10171 first.analyzing = TRUE;
10177 * Button procedures
10180 Reset(redraw, init)
10185 if (appData.debugMode) {
10186 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10187 redraw, init, gameMode);
10189 CleanupTail(); // [HGM] vari: delete any stored variations
10190 pausing = pauseExamInvalid = FALSE;
10191 startedFromSetupPosition = blackPlaysFirst = FALSE;
10193 whiteFlag = blackFlag = FALSE;
10194 userOfferedDraw = FALSE;
10195 hintRequested = bookRequested = FALSE;
10196 first.maybeThinking = FALSE;
10197 second.maybeThinking = FALSE;
10198 first.bookSuspend = FALSE; // [HGM] book
10199 second.bookSuspend = FALSE;
10200 thinkOutput[0] = NULLCHAR;
10201 lastHint[0] = NULLCHAR;
10202 ClearGameInfo(&gameInfo);
10203 gameInfo.variant = StringToVariant(appData.variant);
10204 ics_user_moved = ics_clock_paused = FALSE;
10205 ics_getting_history = H_FALSE;
10207 white_holding[0] = black_holding[0] = NULLCHAR;
10208 ClearProgramStats();
10209 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10213 flipView = appData.flipView;
10214 ClearPremoveHighlights();
10215 gotPremove = FALSE;
10216 alarmSounded = FALSE;
10218 GameEnds(EndOfFile, NULL, GE_PLAYER);
10219 if(appData.serverMovesName != NULL) {
10220 /* [HGM] prepare to make moves file for broadcasting */
10221 clock_t t = clock();
10222 if(serverMoves != NULL) fclose(serverMoves);
10223 serverMoves = fopen(appData.serverMovesName, "r");
10224 if(serverMoves != NULL) {
10225 fclose(serverMoves);
10226 /* delay 15 sec before overwriting, so all clients can see end */
10227 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10229 serverMoves = fopen(appData.serverMovesName, "w");
10233 gameMode = BeginningOfGame;
10235 if(appData.icsActive) gameInfo.variant = VariantNormal;
10236 currentMove = forwardMostMove = backwardMostMove = 0;
10237 InitPosition(redraw);
10238 for (i = 0; i < MAX_MOVES; i++) {
10239 if (commentList[i] != NULL) {
10240 free(commentList[i]);
10241 commentList[i] = NULL;
10245 timeRemaining[0][0] = whiteTimeRemaining;
10246 timeRemaining[1][0] = blackTimeRemaining;
10248 if (first.pr == NULL) {
10249 StartChessProgram(&first);
10252 InitChessProgram(&first, startedFromSetupPosition);
10255 DisplayMessage("", "");
10256 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10257 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10264 if (!AutoPlayOneMove())
10266 if (matchMode || appData.timeDelay == 0)
10268 if (appData.timeDelay < 0)
10270 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10279 int fromX, fromY, toX, toY;
10281 if (appData.debugMode) {
10282 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10285 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10288 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10289 pvInfoList[currentMove].depth = programStats.depth;
10290 pvInfoList[currentMove].score = programStats.score;
10291 pvInfoList[currentMove].time = 0;
10292 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10295 if (currentMove >= forwardMostMove) {
10296 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10297 gameMode = EditGame;
10300 /* [AS] Clear current move marker at the end of a game */
10301 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10306 toX = moveList[currentMove][2] - AAA;
10307 toY = moveList[currentMove][3] - ONE;
10309 if (moveList[currentMove][1] == '@') {
10310 if (appData.highlightLastMove) {
10311 SetHighlights(-1, -1, toX, toY);
10314 fromX = moveList[currentMove][0] - AAA;
10315 fromY = moveList[currentMove][1] - ONE;
10317 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10319 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10321 if (appData.highlightLastMove) {
10322 SetHighlights(fromX, fromY, toX, toY);
10325 DisplayMove(currentMove);
10326 SendMoveToProgram(currentMove++, &first);
10327 DisplayBothClocks();
10328 DrawPosition(FALSE, boards[currentMove]);
10329 // [HGM] PV info: always display, routine tests if empty
10330 DisplayComment(currentMove - 1, commentList[currentMove]);
10336 LoadGameOneMove(readAhead)
10337 ChessMove readAhead;
10339 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10340 char promoChar = NULLCHAR;
10341 ChessMove moveType;
10342 char move[MSG_SIZ];
10345 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10346 gameMode != AnalyzeMode && gameMode != Training) {
10351 yyboardindex = forwardMostMove;
10352 if (readAhead != EndOfFile) {
10353 moveType = readAhead;
10355 if (gameFileFP == NULL)
10357 moveType = (ChessMove) Myylex();
10361 switch (moveType) {
10363 if (appData.debugMode)
10364 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10367 /* append the comment but don't display it */
10368 AppendComment(currentMove, p, FALSE);
10371 case WhiteCapturesEnPassant:
10372 case BlackCapturesEnPassant:
10373 case WhitePromotion:
10374 case BlackPromotion:
10375 case WhiteNonPromotion:
10376 case BlackNonPromotion:
10378 case WhiteKingSideCastle:
10379 case WhiteQueenSideCastle:
10380 case BlackKingSideCastle:
10381 case BlackQueenSideCastle:
10382 case WhiteKingSideCastleWild:
10383 case WhiteQueenSideCastleWild:
10384 case BlackKingSideCastleWild:
10385 case BlackQueenSideCastleWild:
10387 case WhiteHSideCastleFR:
10388 case WhiteASideCastleFR:
10389 case BlackHSideCastleFR:
10390 case BlackASideCastleFR:
10392 if (appData.debugMode)
10393 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10394 fromX = currentMoveString[0] - AAA;
10395 fromY = currentMoveString[1] - ONE;
10396 toX = currentMoveString[2] - AAA;
10397 toY = currentMoveString[3] - ONE;
10398 promoChar = currentMoveString[4];
10403 if (appData.debugMode)
10404 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10405 fromX = moveType == WhiteDrop ?
10406 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10407 (int) CharToPiece(ToLower(currentMoveString[0]));
10409 toX = currentMoveString[2] - AAA;
10410 toY = currentMoveString[3] - ONE;
10416 case GameUnfinished:
10417 if (appData.debugMode)
10418 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10419 p = strchr(yy_text, '{');
10420 if (p == NULL) p = strchr(yy_text, '(');
10423 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10425 q = strchr(p, *p == '{' ? '}' : ')');
10426 if (q != NULL) *q = NULLCHAR;
10429 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10430 GameEnds(moveType, p, GE_FILE);
10432 if (cmailMsgLoaded) {
10434 flipView = WhiteOnMove(currentMove);
10435 if (moveType == GameUnfinished) flipView = !flipView;
10436 if (appData.debugMode)
10437 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10442 if (appData.debugMode)
10443 fprintf(debugFP, "Parser hit end of file\n");
10444 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10450 if (WhiteOnMove(currentMove)) {
10451 GameEnds(BlackWins, "Black mates", GE_FILE);
10453 GameEnds(WhiteWins, "White mates", GE_FILE);
10457 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10463 case MoveNumberOne:
10464 if (lastLoadGameStart == GNUChessGame) {
10465 /* GNUChessGames have numbers, but they aren't move numbers */
10466 if (appData.debugMode)
10467 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10468 yy_text, (int) moveType);
10469 return LoadGameOneMove(EndOfFile); /* tail recursion */
10471 /* else fall thru */
10476 /* Reached start of next game in file */
10477 if (appData.debugMode)
10478 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10479 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10485 if (WhiteOnMove(currentMove)) {
10486 GameEnds(BlackWins, "Black mates", GE_FILE);
10488 GameEnds(WhiteWins, "White mates", GE_FILE);
10492 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10498 case PositionDiagram: /* should not happen; ignore */
10499 case ElapsedTime: /* ignore */
10500 case NAG: /* ignore */
10501 if (appData.debugMode)
10502 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10503 yy_text, (int) moveType);
10504 return LoadGameOneMove(EndOfFile); /* tail recursion */
10507 if (appData.testLegality) {
10508 if (appData.debugMode)
10509 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10510 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10511 (forwardMostMove / 2) + 1,
10512 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10513 DisplayError(move, 0);
10516 if (appData.debugMode)
10517 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10518 yy_text, currentMoveString);
10519 fromX = currentMoveString[0] - AAA;
10520 fromY = currentMoveString[1] - ONE;
10521 toX = currentMoveString[2] - AAA;
10522 toY = currentMoveString[3] - ONE;
10523 promoChar = currentMoveString[4];
10527 case AmbiguousMove:
10528 if (appData.debugMode)
10529 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10530 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10531 (forwardMostMove / 2) + 1,
10532 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10533 DisplayError(move, 0);
10538 case ImpossibleMove:
10539 if (appData.debugMode)
10540 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10541 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10542 (forwardMostMove / 2) + 1,
10543 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10544 DisplayError(move, 0);
10550 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10551 DrawPosition(FALSE, boards[currentMove]);
10552 DisplayBothClocks();
10553 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10554 DisplayComment(currentMove - 1, commentList[currentMove]);
10556 (void) StopLoadGameTimer();
10558 cmailOldMove = forwardMostMove;
10561 /* currentMoveString is set as a side-effect of yylex */
10563 thinkOutput[0] = NULLCHAR;
10564 MakeMove(fromX, fromY, toX, toY, promoChar);
10565 currentMove = forwardMostMove;
10570 /* Load the nth game from the given file */
10572 LoadGameFromFile(filename, n, title, useList)
10576 /*Boolean*/ int useList;
10581 if (strcmp(filename, "-") == 0) {
10585 f = fopen(filename, "rb");
10587 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10588 DisplayError(buf, errno);
10592 if (fseek(f, 0, 0) == -1) {
10593 /* f is not seekable; probably a pipe */
10596 if (useList && n == 0) {
10597 int error = GameListBuild(f);
10599 DisplayError(_("Cannot build game list"), error);
10600 } else if (!ListEmpty(&gameList) &&
10601 ((ListGame *) gameList.tailPred)->number > 1) {
10602 GameListPopUp(f, title);
10609 return LoadGame(f, n, title, FALSE);
10614 MakeRegisteredMove()
10616 int fromX, fromY, toX, toY;
10618 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10619 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10622 if (appData.debugMode)
10623 fprintf(debugFP, "Restoring %s for game %d\n",
10624 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10626 thinkOutput[0] = NULLCHAR;
10627 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10628 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10629 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10630 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10631 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10632 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10633 MakeMove(fromX, fromY, toX, toY, promoChar);
10634 ShowMove(fromX, fromY, toX, toY);
10636 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10643 if (WhiteOnMove(currentMove)) {
10644 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10646 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10651 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10658 if (WhiteOnMove(currentMove)) {
10659 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10661 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10666 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10677 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10679 CmailLoadGame(f, gameNumber, title, useList)
10687 if (gameNumber > nCmailGames) {
10688 DisplayError(_("No more games in this message"), 0);
10691 if (f == lastLoadGameFP) {
10692 int offset = gameNumber - lastLoadGameNumber;
10694 cmailMsg[0] = NULLCHAR;
10695 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10696 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10697 nCmailMovesRegistered--;
10699 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10700 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10701 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10704 if (! RegisterMove()) return FALSE;
10708 retVal = LoadGame(f, gameNumber, title, useList);
10710 /* Make move registered during previous look at this game, if any */
10711 MakeRegisteredMove();
10713 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10714 commentList[currentMove]
10715 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10716 DisplayComment(currentMove - 1, commentList[currentMove]);
10722 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10727 int gameNumber = lastLoadGameNumber + offset;
10728 if (lastLoadGameFP == NULL) {
10729 DisplayError(_("No game has been loaded yet"), 0);
10732 if (gameNumber <= 0) {
10733 DisplayError(_("Can't back up any further"), 0);
10736 if (cmailMsgLoaded) {
10737 return CmailLoadGame(lastLoadGameFP, gameNumber,
10738 lastLoadGameTitle, lastLoadGameUseList);
10740 return LoadGame(lastLoadGameFP, gameNumber,
10741 lastLoadGameTitle, lastLoadGameUseList);
10747 /* Load the nth game from open file f */
10749 LoadGame(f, gameNumber, title, useList)
10757 int gn = gameNumber;
10758 ListGame *lg = NULL;
10759 int numPGNTags = 0;
10761 GameMode oldGameMode;
10762 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10764 if (appData.debugMode)
10765 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10767 if (gameMode == Training )
10768 SetTrainingModeOff();
10770 oldGameMode = gameMode;
10771 if (gameMode != BeginningOfGame) {
10772 Reset(FALSE, TRUE);
10776 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10777 fclose(lastLoadGameFP);
10781 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10784 fseek(f, lg->offset, 0);
10785 GameListHighlight(gameNumber);
10789 DisplayError(_("Game number out of range"), 0);
10794 if (fseek(f, 0, 0) == -1) {
10795 if (f == lastLoadGameFP ?
10796 gameNumber == lastLoadGameNumber + 1 :
10800 DisplayError(_("Can't seek on game file"), 0);
10805 lastLoadGameFP = f;
10806 lastLoadGameNumber = gameNumber;
10807 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10808 lastLoadGameUseList = useList;
10812 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10813 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10814 lg->gameInfo.black);
10816 } else if (*title != NULLCHAR) {
10817 if (gameNumber > 1) {
10818 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10821 DisplayTitle(title);
10825 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10826 gameMode = PlayFromGameFile;
10830 currentMove = forwardMostMove = backwardMostMove = 0;
10831 CopyBoard(boards[0], initialPosition);
10835 * Skip the first gn-1 games in the file.
10836 * Also skip over anything that precedes an identifiable
10837 * start of game marker, to avoid being confused by
10838 * garbage at the start of the file. Currently
10839 * recognized start of game markers are the move number "1",
10840 * the pattern "gnuchess .* game", the pattern
10841 * "^[#;%] [^ ]* game file", and a PGN tag block.
10842 * A game that starts with one of the latter two patterns
10843 * will also have a move number 1, possibly
10844 * following a position diagram.
10845 * 5-4-02: Let's try being more lenient and allowing a game to
10846 * start with an unnumbered move. Does that break anything?
10848 cm = lastLoadGameStart = EndOfFile;
10850 yyboardindex = forwardMostMove;
10851 cm = (ChessMove) Myylex();
10854 if (cmailMsgLoaded) {
10855 nCmailGames = CMAIL_MAX_GAMES - gn;
10858 DisplayError(_("Game not found in file"), 0);
10865 lastLoadGameStart = cm;
10868 case MoveNumberOne:
10869 switch (lastLoadGameStart) {
10874 case MoveNumberOne:
10876 gn--; /* count this game */
10877 lastLoadGameStart = cm;
10886 switch (lastLoadGameStart) {
10889 case MoveNumberOne:
10891 gn--; /* count this game */
10892 lastLoadGameStart = cm;
10895 lastLoadGameStart = cm; /* game counted already */
10903 yyboardindex = forwardMostMove;
10904 cm = (ChessMove) Myylex();
10905 } while (cm == PGNTag || cm == Comment);
10912 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10913 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10914 != CMAIL_OLD_RESULT) {
10916 cmailResult[ CMAIL_MAX_GAMES
10917 - gn - 1] = CMAIL_OLD_RESULT;
10923 /* Only a NormalMove can be at the start of a game
10924 * without a position diagram. */
10925 if (lastLoadGameStart == EndOfFile ) {
10927 lastLoadGameStart = MoveNumberOne;
10936 if (appData.debugMode)
10937 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10939 if (cm == XBoardGame) {
10940 /* Skip any header junk before position diagram and/or move 1 */
10942 yyboardindex = forwardMostMove;
10943 cm = (ChessMove) Myylex();
10945 if (cm == EndOfFile ||
10946 cm == GNUChessGame || cm == XBoardGame) {
10947 /* Empty game; pretend end-of-file and handle later */
10952 if (cm == MoveNumberOne || cm == PositionDiagram ||
10953 cm == PGNTag || cm == Comment)
10956 } else if (cm == GNUChessGame) {
10957 if (gameInfo.event != NULL) {
10958 free(gameInfo.event);
10960 gameInfo.event = StrSave(yy_text);
10963 startedFromSetupPosition = FALSE;
10964 while (cm == PGNTag) {
10965 if (appData.debugMode)
10966 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10967 err = ParsePGNTag(yy_text, &gameInfo);
10968 if (!err) numPGNTags++;
10970 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10971 if(gameInfo.variant != oldVariant) {
10972 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10973 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10974 InitPosition(TRUE);
10975 oldVariant = gameInfo.variant;
10976 if (appData.debugMode)
10977 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10981 if (gameInfo.fen != NULL) {
10982 Board initial_position;
10983 startedFromSetupPosition = TRUE;
10984 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10986 DisplayError(_("Bad FEN position in file"), 0);
10989 CopyBoard(boards[0], initial_position);
10990 if (blackPlaysFirst) {
10991 currentMove = forwardMostMove = backwardMostMove = 1;
10992 CopyBoard(boards[1], initial_position);
10993 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10994 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10995 timeRemaining[0][1] = whiteTimeRemaining;
10996 timeRemaining[1][1] = blackTimeRemaining;
10997 if (commentList[0] != NULL) {
10998 commentList[1] = commentList[0];
10999 commentList[0] = NULL;
11002 currentMove = forwardMostMove = backwardMostMove = 0;
11004 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11006 initialRulePlies = FENrulePlies;
11007 for( i=0; i< nrCastlingRights; i++ )
11008 initialRights[i] = initial_position[CASTLING][i];
11010 yyboardindex = forwardMostMove;
11011 free(gameInfo.fen);
11012 gameInfo.fen = NULL;
11015 yyboardindex = forwardMostMove;
11016 cm = (ChessMove) Myylex();
11018 /* Handle comments interspersed among the tags */
11019 while (cm == Comment) {
11021 if (appData.debugMode)
11022 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11024 AppendComment(currentMove, p, FALSE);
11025 yyboardindex = forwardMostMove;
11026 cm = (ChessMove) Myylex();
11030 /* don't rely on existence of Event tag since if game was
11031 * pasted from clipboard the Event tag may not exist
11033 if (numPGNTags > 0){
11035 if (gameInfo.variant == VariantNormal) {
11036 VariantClass v = StringToVariant(gameInfo.event);
11037 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11038 if(v < VariantShogi) gameInfo.variant = v;
11041 if( appData.autoDisplayTags ) {
11042 tags = PGNTags(&gameInfo);
11043 TagsPopUp(tags, CmailMsg());
11048 /* Make something up, but don't display it now */
11053 if (cm == PositionDiagram) {
11056 Board initial_position;
11058 if (appData.debugMode)
11059 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11061 if (!startedFromSetupPosition) {
11063 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11064 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11075 initial_position[i][j++] = CharToPiece(*p);
11078 while (*p == ' ' || *p == '\t' ||
11079 *p == '\n' || *p == '\r') p++;
11081 if (strncmp(p, "black", strlen("black"))==0)
11082 blackPlaysFirst = TRUE;
11084 blackPlaysFirst = FALSE;
11085 startedFromSetupPosition = TRUE;
11087 CopyBoard(boards[0], initial_position);
11088 if (blackPlaysFirst) {
11089 currentMove = forwardMostMove = backwardMostMove = 1;
11090 CopyBoard(boards[1], initial_position);
11091 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11092 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11093 timeRemaining[0][1] = whiteTimeRemaining;
11094 timeRemaining[1][1] = blackTimeRemaining;
11095 if (commentList[0] != NULL) {
11096 commentList[1] = commentList[0];
11097 commentList[0] = NULL;
11100 currentMove = forwardMostMove = backwardMostMove = 0;
11103 yyboardindex = forwardMostMove;
11104 cm = (ChessMove) Myylex();
11107 if (first.pr == NoProc) {
11108 StartChessProgram(&first);
11110 InitChessProgram(&first, FALSE);
11111 SendToProgram("force\n", &first);
11112 if (startedFromSetupPosition) {
11113 SendBoard(&first, forwardMostMove);
11114 if (appData.debugMode) {
11115 fprintf(debugFP, "Load Game\n");
11117 DisplayBothClocks();
11120 /* [HGM] server: flag to write setup moves in broadcast file as one */
11121 loadFlag = appData.suppressLoadMoves;
11123 while (cm == Comment) {
11125 if (appData.debugMode)
11126 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11128 AppendComment(currentMove, p, FALSE);
11129 yyboardindex = forwardMostMove;
11130 cm = (ChessMove) Myylex();
11133 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11134 cm == WhiteWins || cm == BlackWins ||
11135 cm == GameIsDrawn || cm == GameUnfinished) {
11136 DisplayMessage("", _("No moves in game"));
11137 if (cmailMsgLoaded) {
11138 if (appData.debugMode)
11139 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11143 DrawPosition(FALSE, boards[currentMove]);
11144 DisplayBothClocks();
11145 gameMode = EditGame;
11152 // [HGM] PV info: routine tests if comment empty
11153 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11154 DisplayComment(currentMove - 1, commentList[currentMove]);
11156 if (!matchMode && appData.timeDelay != 0)
11157 DrawPosition(FALSE, boards[currentMove]);
11159 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11160 programStats.ok_to_send = 1;
11163 /* if the first token after the PGN tags is a move
11164 * and not move number 1, retrieve it from the parser
11166 if (cm != MoveNumberOne)
11167 LoadGameOneMove(cm);
11169 /* load the remaining moves from the file */
11170 while (LoadGameOneMove(EndOfFile)) {
11171 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11172 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11175 /* rewind to the start of the game */
11176 currentMove = backwardMostMove;
11178 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11180 if (oldGameMode == AnalyzeFile ||
11181 oldGameMode == AnalyzeMode) {
11182 AnalyzeFileEvent();
11185 if (matchMode || appData.timeDelay == 0) {
11187 gameMode = EditGame;
11189 } else if (appData.timeDelay > 0) {
11190 AutoPlayGameLoop();
11193 if (appData.debugMode)
11194 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11196 loadFlag = 0; /* [HGM] true game starts */
11200 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11202 ReloadPosition(offset)
11205 int positionNumber = lastLoadPositionNumber + offset;
11206 if (lastLoadPositionFP == NULL) {
11207 DisplayError(_("No position has been loaded yet"), 0);
11210 if (positionNumber <= 0) {
11211 DisplayError(_("Can't back up any further"), 0);
11214 return LoadPosition(lastLoadPositionFP, positionNumber,
11215 lastLoadPositionTitle);
11218 /* Load the nth position from the given file */
11220 LoadPositionFromFile(filename, n, title)
11228 if (strcmp(filename, "-") == 0) {
11229 return LoadPosition(stdin, n, "stdin");
11231 f = fopen(filename, "rb");
11233 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11234 DisplayError(buf, errno);
11237 return LoadPosition(f, n, title);
11242 /* Load the nth position from the given open file, and close it */
11244 LoadPosition(f, positionNumber, title)
11246 int positionNumber;
11249 char *p, line[MSG_SIZ];
11250 Board initial_position;
11251 int i, j, fenMode, pn;
11253 if (gameMode == Training )
11254 SetTrainingModeOff();
11256 if (gameMode != BeginningOfGame) {
11257 Reset(FALSE, TRUE);
11259 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11260 fclose(lastLoadPositionFP);
11262 if (positionNumber == 0) positionNumber = 1;
11263 lastLoadPositionFP = f;
11264 lastLoadPositionNumber = positionNumber;
11265 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11266 if (first.pr == NoProc) {
11267 StartChessProgram(&first);
11268 InitChessProgram(&first, FALSE);
11270 pn = positionNumber;
11271 if (positionNumber < 0) {
11272 /* Negative position number means to seek to that byte offset */
11273 if (fseek(f, -positionNumber, 0) == -1) {
11274 DisplayError(_("Can't seek on position file"), 0);
11279 if (fseek(f, 0, 0) == -1) {
11280 if (f == lastLoadPositionFP ?
11281 positionNumber == lastLoadPositionNumber + 1 :
11282 positionNumber == 1) {
11285 DisplayError(_("Can't seek on position file"), 0);
11290 /* See if this file is FEN or old-style xboard */
11291 if (fgets(line, MSG_SIZ, f) == NULL) {
11292 DisplayError(_("Position not found in file"), 0);
11295 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11296 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11299 if (fenMode || line[0] == '#') pn--;
11301 /* skip positions before number pn */
11302 if (fgets(line, MSG_SIZ, f) == NULL) {
11304 DisplayError(_("Position not found in file"), 0);
11307 if (fenMode || line[0] == '#') pn--;
11312 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11313 DisplayError(_("Bad FEN position in file"), 0);
11317 (void) fgets(line, MSG_SIZ, f);
11318 (void) fgets(line, MSG_SIZ, f);
11320 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11321 (void) fgets(line, MSG_SIZ, f);
11322 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11325 initial_position[i][j++] = CharToPiece(*p);
11329 blackPlaysFirst = FALSE;
11331 (void) fgets(line, MSG_SIZ, f);
11332 if (strncmp(line, "black", strlen("black"))==0)
11333 blackPlaysFirst = TRUE;
11336 startedFromSetupPosition = TRUE;
11338 SendToProgram("force\n", &first);
11339 CopyBoard(boards[0], initial_position);
11340 if (blackPlaysFirst) {
11341 currentMove = forwardMostMove = backwardMostMove = 1;
11342 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11343 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11344 CopyBoard(boards[1], initial_position);
11345 DisplayMessage("", _("Black to play"));
11347 currentMove = forwardMostMove = backwardMostMove = 0;
11348 DisplayMessage("", _("White to play"));
11350 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11351 SendBoard(&first, forwardMostMove);
11352 if (appData.debugMode) {
11354 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11355 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11356 fprintf(debugFP, "Load Position\n");
11359 if (positionNumber > 1) {
11360 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11361 DisplayTitle(line);
11363 DisplayTitle(title);
11365 gameMode = EditGame;
11368 timeRemaining[0][1] = whiteTimeRemaining;
11369 timeRemaining[1][1] = blackTimeRemaining;
11370 DrawPosition(FALSE, boards[currentMove]);
11377 CopyPlayerNameIntoFileName(dest, src)
11380 while (*src != NULLCHAR && *src != ',') {
11385 *(*dest)++ = *src++;
11390 char *DefaultFileName(ext)
11393 static char def[MSG_SIZ];
11396 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11398 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11400 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11402 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11409 /* Save the current game to the given file */
11411 SaveGameToFile(filename, append)
11419 if (strcmp(filename, "-") == 0) {
11420 return SaveGame(stdout, 0, NULL);
11422 f = fopen(filename, append ? "a" : "w");
11424 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11425 DisplayError(buf, errno);
11428 safeStrCpy(buf, lastMsg, MSG_SIZ);
11429 DisplayMessage(_("Waiting for access to save file"), "");
11430 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11431 DisplayMessage(_("Saving game"), "");
11432 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11433 result = SaveGame(f, 0, NULL);
11434 DisplayMessage(buf, "");
11444 static char buf[MSG_SIZ];
11447 p = strchr(str, ' ');
11448 if (p == NULL) return str;
11449 strncpy(buf, str, p - str);
11450 buf[p - str] = NULLCHAR;
11454 #define PGN_MAX_LINE 75
11456 #define PGN_SIDE_WHITE 0
11457 #define PGN_SIDE_BLACK 1
11460 static int FindFirstMoveOutOfBook( int side )
11464 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11465 int index = backwardMostMove;
11466 int has_book_hit = 0;
11468 if( (index % 2) != side ) {
11472 while( index < forwardMostMove ) {
11473 /* Check to see if engine is in book */
11474 int depth = pvInfoList[index].depth;
11475 int score = pvInfoList[index].score;
11481 else if( score == 0 && depth == 63 ) {
11482 in_book = 1; /* Zappa */
11484 else if( score == 2 && depth == 99 ) {
11485 in_book = 1; /* Abrok */
11488 has_book_hit += in_book;
11504 void GetOutOfBookInfo( char * buf )
11508 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11510 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11511 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11515 if( oob[0] >= 0 || oob[1] >= 0 ) {
11516 for( i=0; i<2; i++ ) {
11520 if( i > 0 && oob[0] >= 0 ) {
11521 strcat( buf, " " );
11524 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11525 sprintf( buf+strlen(buf), "%s%.2f",
11526 pvInfoList[idx].score >= 0 ? "+" : "",
11527 pvInfoList[idx].score / 100.0 );
11533 /* Save game in PGN style and close the file */
11538 int i, offset, linelen, newblock;
11542 int movelen, numlen, blank;
11543 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11545 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11547 tm = time((time_t *) NULL);
11549 PrintPGNTags(f, &gameInfo);
11551 if (backwardMostMove > 0 || startedFromSetupPosition) {
11552 char *fen = PositionToFEN(backwardMostMove, NULL);
11553 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11554 fprintf(f, "\n{--------------\n");
11555 PrintPosition(f, backwardMostMove);
11556 fprintf(f, "--------------}\n");
11560 /* [AS] Out of book annotation */
11561 if( appData.saveOutOfBookInfo ) {
11564 GetOutOfBookInfo( buf );
11566 if( buf[0] != '\0' ) {
11567 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11574 i = backwardMostMove;
11578 while (i < forwardMostMove) {
11579 /* Print comments preceding this move */
11580 if (commentList[i] != NULL) {
11581 if (linelen > 0) fprintf(f, "\n");
11582 fprintf(f, "%s", commentList[i]);
11587 /* Format move number */
11589 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11592 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11594 numtext[0] = NULLCHAR;
11596 numlen = strlen(numtext);
11599 /* Print move number */
11600 blank = linelen > 0 && numlen > 0;
11601 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11610 fprintf(f, "%s", numtext);
11614 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11615 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11618 blank = linelen > 0 && movelen > 0;
11619 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11628 fprintf(f, "%s", move_buffer);
11629 linelen += movelen;
11631 /* [AS] Add PV info if present */
11632 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11633 /* [HGM] add time */
11634 char buf[MSG_SIZ]; int seconds;
11636 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11642 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11645 seconds = (seconds + 4)/10; // round to full seconds
11647 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11649 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11652 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11653 pvInfoList[i].score >= 0 ? "+" : "",
11654 pvInfoList[i].score / 100.0,
11655 pvInfoList[i].depth,
11658 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11660 /* Print score/depth */
11661 blank = linelen > 0 && movelen > 0;
11662 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11671 fprintf(f, "%s", move_buffer);
11672 linelen += movelen;
11678 /* Start a new line */
11679 if (linelen > 0) fprintf(f, "\n");
11681 /* Print comments after last move */
11682 if (commentList[i] != NULL) {
11683 fprintf(f, "%s\n", commentList[i]);
11687 if (gameInfo.resultDetails != NULL &&
11688 gameInfo.resultDetails[0] != NULLCHAR) {
11689 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11690 PGNResult(gameInfo.result));
11692 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11696 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11700 /* Save game in old style and close the file */
11702 SaveGameOldStyle(f)
11708 tm = time((time_t *) NULL);
11710 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11713 if (backwardMostMove > 0 || startedFromSetupPosition) {
11714 fprintf(f, "\n[--------------\n");
11715 PrintPosition(f, backwardMostMove);
11716 fprintf(f, "--------------]\n");
11721 i = backwardMostMove;
11722 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11724 while (i < forwardMostMove) {
11725 if (commentList[i] != NULL) {
11726 fprintf(f, "[%s]\n", commentList[i]);
11729 if ((i % 2) == 1) {
11730 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11733 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11735 if (commentList[i] != NULL) {
11739 if (i >= forwardMostMove) {
11743 fprintf(f, "%s\n", parseList[i]);
11748 if (commentList[i] != NULL) {
11749 fprintf(f, "[%s]\n", commentList[i]);
11752 /* This isn't really the old style, but it's close enough */
11753 if (gameInfo.resultDetails != NULL &&
11754 gameInfo.resultDetails[0] != NULLCHAR) {
11755 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11756 gameInfo.resultDetails);
11758 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11765 /* Save the current game to open file f and close the file */
11767 SaveGame(f, dummy, dummy2)
11772 if (gameMode == EditPosition) EditPositionDone(TRUE);
11773 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11774 if (appData.oldSaveStyle)
11775 return SaveGameOldStyle(f);
11777 return SaveGamePGN(f);
11780 /* Save the current position to the given file */
11782 SavePositionToFile(filename)
11788 if (strcmp(filename, "-") == 0) {
11789 return SavePosition(stdout, 0, NULL);
11791 f = fopen(filename, "a");
11793 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11794 DisplayError(buf, errno);
11797 safeStrCpy(buf, lastMsg, MSG_SIZ);
11798 DisplayMessage(_("Waiting for access to save file"), "");
11799 flock(fileno(f), LOCK_EX); // [HGM] lock
11800 DisplayMessage(_("Saving position"), "");
11801 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11802 SavePosition(f, 0, NULL);
11803 DisplayMessage(buf, "");
11809 /* Save the current position to the given open file and close the file */
11811 SavePosition(f, dummy, dummy2)
11819 if (gameMode == EditPosition) EditPositionDone(TRUE);
11820 if (appData.oldSaveStyle) {
11821 tm = time((time_t *) NULL);
11823 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11825 fprintf(f, "[--------------\n");
11826 PrintPosition(f, currentMove);
11827 fprintf(f, "--------------]\n");
11829 fen = PositionToFEN(currentMove, NULL);
11830 fprintf(f, "%s\n", fen);
11838 ReloadCmailMsgEvent(unregister)
11842 static char *inFilename = NULL;
11843 static char *outFilename;
11845 struct stat inbuf, outbuf;
11848 /* Any registered moves are unregistered if unregister is set, */
11849 /* i.e. invoked by the signal handler */
11851 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11852 cmailMoveRegistered[i] = FALSE;
11853 if (cmailCommentList[i] != NULL) {
11854 free(cmailCommentList[i]);
11855 cmailCommentList[i] = NULL;
11858 nCmailMovesRegistered = 0;
11861 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11862 cmailResult[i] = CMAIL_NOT_RESULT;
11866 if (inFilename == NULL) {
11867 /* Because the filenames are static they only get malloced once */
11868 /* and they never get freed */
11869 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11870 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11872 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11873 sprintf(outFilename, "%s.out", appData.cmailGameName);
11876 status = stat(outFilename, &outbuf);
11878 cmailMailedMove = FALSE;
11880 status = stat(inFilename, &inbuf);
11881 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11884 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11885 counts the games, notes how each one terminated, etc.
11887 It would be nice to remove this kludge and instead gather all
11888 the information while building the game list. (And to keep it
11889 in the game list nodes instead of having a bunch of fixed-size
11890 parallel arrays.) Note this will require getting each game's
11891 termination from the PGN tags, as the game list builder does
11892 not process the game moves. --mann
11894 cmailMsgLoaded = TRUE;
11895 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11897 /* Load first game in the file or popup game menu */
11898 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11900 #endif /* !WIN32 */
11908 char string[MSG_SIZ];
11910 if ( cmailMailedMove
11911 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11912 return TRUE; /* Allow free viewing */
11915 /* Unregister move to ensure that we don't leave RegisterMove */
11916 /* with the move registered when the conditions for registering no */
11918 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11919 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11920 nCmailMovesRegistered --;
11922 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11924 free(cmailCommentList[lastLoadGameNumber - 1]);
11925 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11929 if (cmailOldMove == -1) {
11930 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11934 if (currentMove > cmailOldMove + 1) {
11935 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11939 if (currentMove < cmailOldMove) {
11940 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11944 if (forwardMostMove > currentMove) {
11945 /* Silently truncate extra moves */
11949 if ( (currentMove == cmailOldMove + 1)
11950 || ( (currentMove == cmailOldMove)
11951 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11952 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11953 if (gameInfo.result != GameUnfinished) {
11954 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11957 if (commentList[currentMove] != NULL) {
11958 cmailCommentList[lastLoadGameNumber - 1]
11959 = StrSave(commentList[currentMove]);
11961 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11963 if (appData.debugMode)
11964 fprintf(debugFP, "Saving %s for game %d\n",
11965 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11967 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11969 f = fopen(string, "w");
11970 if (appData.oldSaveStyle) {
11971 SaveGameOldStyle(f); /* also closes the file */
11973 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11974 f = fopen(string, "w");
11975 SavePosition(f, 0, NULL); /* also closes the file */
11977 fprintf(f, "{--------------\n");
11978 PrintPosition(f, currentMove);
11979 fprintf(f, "--------------}\n\n");
11981 SaveGame(f, 0, NULL); /* also closes the file*/
11984 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11985 nCmailMovesRegistered ++;
11986 } else if (nCmailGames == 1) {
11987 DisplayError(_("You have not made a move yet"), 0);
11998 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11999 FILE *commandOutput;
12000 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12001 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12007 if (! cmailMsgLoaded) {
12008 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12012 if (nCmailGames == nCmailResults) {
12013 DisplayError(_("No unfinished games"), 0);
12017 #if CMAIL_PROHIBIT_REMAIL
12018 if (cmailMailedMove) {
12019 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);
12020 DisplayError(msg, 0);
12025 if (! (cmailMailedMove || RegisterMove())) return;
12027 if ( cmailMailedMove
12028 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12029 snprintf(string, MSG_SIZ, partCommandString,
12030 appData.debugMode ? " -v" : "", appData.cmailGameName);
12031 commandOutput = popen(string, "r");
12033 if (commandOutput == NULL) {
12034 DisplayError(_("Failed to invoke cmail"), 0);
12036 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12037 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12039 if (nBuffers > 1) {
12040 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12041 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12042 nBytes = MSG_SIZ - 1;
12044 (void) memcpy(msg, buffer, nBytes);
12046 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12048 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12049 cmailMailedMove = TRUE; /* Prevent >1 moves */
12052 for (i = 0; i < nCmailGames; i ++) {
12053 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12058 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12060 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12062 appData.cmailGameName,
12064 LoadGameFromFile(buffer, 1, buffer, FALSE);
12065 cmailMsgLoaded = FALSE;
12069 DisplayInformation(msg);
12070 pclose(commandOutput);
12073 if ((*cmailMsg) != '\0') {
12074 DisplayInformation(cmailMsg);
12079 #endif /* !WIN32 */
12088 int prependComma = 0;
12090 char string[MSG_SIZ]; /* Space for game-list */
12093 if (!cmailMsgLoaded) return "";
12095 if (cmailMailedMove) {
12096 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12098 /* Create a list of games left */
12099 snprintf(string, MSG_SIZ, "[");
12100 for (i = 0; i < nCmailGames; i ++) {
12101 if (! ( cmailMoveRegistered[i]
12102 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12103 if (prependComma) {
12104 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12106 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12110 strcat(string, number);
12113 strcat(string, "]");
12115 if (nCmailMovesRegistered + nCmailResults == 0) {
12116 switch (nCmailGames) {
12118 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12122 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12126 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12131 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12133 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12138 if (nCmailResults == nCmailGames) {
12139 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12141 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12146 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12158 if (gameMode == Training)
12159 SetTrainingModeOff();
12162 cmailMsgLoaded = FALSE;
12163 if (appData.icsActive) {
12164 SendToICS(ics_prefix);
12165 SendToICS("refresh\n");
12175 /* Give up on clean exit */
12179 /* Keep trying for clean exit */
12183 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12185 if (telnetISR != NULL) {
12186 RemoveInputSource(telnetISR);
12188 if (icsPR != NoProc) {
12189 DestroyChildProcess(icsPR, TRUE);
12192 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12193 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12195 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12196 /* make sure this other one finishes before killing it! */
12197 if(endingGame) { int count = 0;
12198 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12199 while(endingGame && count++ < 10) DoSleep(1);
12200 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12203 /* Kill off chess programs */
12204 if (first.pr != NoProc) {
12207 DoSleep( appData.delayBeforeQuit );
12208 SendToProgram("quit\n", &first);
12209 DoSleep( appData.delayAfterQuit );
12210 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12212 if (second.pr != NoProc) {
12213 DoSleep( appData.delayBeforeQuit );
12214 SendToProgram("quit\n", &second);
12215 DoSleep( appData.delayAfterQuit );
12216 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12218 if (first.isr != NULL) {
12219 RemoveInputSource(first.isr);
12221 if (second.isr != NULL) {
12222 RemoveInputSource(second.isr);
12225 ShutDownFrontEnd();
12232 if (appData.debugMode)
12233 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12237 if (gameMode == MachinePlaysWhite ||
12238 gameMode == MachinePlaysBlack) {
12241 DisplayBothClocks();
12243 if (gameMode == PlayFromGameFile) {
12244 if (appData.timeDelay >= 0)
12245 AutoPlayGameLoop();
12246 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12247 Reset(FALSE, TRUE);
12248 SendToICS(ics_prefix);
12249 SendToICS("refresh\n");
12250 } else if (currentMove < forwardMostMove) {
12251 ForwardInner(forwardMostMove);
12253 pauseExamInvalid = FALSE;
12255 switch (gameMode) {
12259 pauseExamForwardMostMove = forwardMostMove;
12260 pauseExamInvalid = FALSE;
12263 case IcsPlayingWhite:
12264 case IcsPlayingBlack:
12268 case PlayFromGameFile:
12269 (void) StopLoadGameTimer();
12273 case BeginningOfGame:
12274 if (appData.icsActive) return;
12275 /* else fall through */
12276 case MachinePlaysWhite:
12277 case MachinePlaysBlack:
12278 case TwoMachinesPlay:
12279 if (forwardMostMove == 0)
12280 return; /* don't pause if no one has moved */
12281 if ((gameMode == MachinePlaysWhite &&
12282 !WhiteOnMove(forwardMostMove)) ||
12283 (gameMode == MachinePlaysBlack &&
12284 WhiteOnMove(forwardMostMove))) {
12297 char title[MSG_SIZ];
12299 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12300 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12302 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12303 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12304 parseList[currentMove - 1]);
12307 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12314 char *tags = PGNTags(&gameInfo);
12316 EditTagsPopUp(tags, NULL);
12323 if (appData.noChessProgram || gameMode == AnalyzeMode)
12326 if (gameMode != AnalyzeFile) {
12327 if (!appData.icsEngineAnalyze) {
12329 if (gameMode != EditGame) return;
12331 ResurrectChessProgram();
12332 SendToProgram("analyze\n", &first);
12333 first.analyzing = TRUE;
12334 /*first.maybeThinking = TRUE;*/
12335 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12336 EngineOutputPopUp();
12338 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12343 StartAnalysisClock();
12344 GetTimeMark(&lastNodeCountTime);
12351 if (appData.noChessProgram || gameMode == AnalyzeFile)
12354 if (gameMode != AnalyzeMode) {
12356 if (gameMode != EditGame) return;
12357 ResurrectChessProgram();
12358 SendToProgram("analyze\n", &first);
12359 first.analyzing = TRUE;
12360 /*first.maybeThinking = TRUE;*/
12361 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12362 EngineOutputPopUp();
12364 gameMode = AnalyzeFile;
12369 StartAnalysisClock();
12370 GetTimeMark(&lastNodeCountTime);
12375 MachineWhiteEvent()
12378 char *bookHit = NULL;
12380 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12384 if (gameMode == PlayFromGameFile ||
12385 gameMode == TwoMachinesPlay ||
12386 gameMode == Training ||
12387 gameMode == AnalyzeMode ||
12388 gameMode == EndOfGame)
12391 if (gameMode == EditPosition)
12392 EditPositionDone(TRUE);
12394 if (!WhiteOnMove(currentMove)) {
12395 DisplayError(_("It is not White's turn"), 0);
12399 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12402 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12403 gameMode == AnalyzeFile)
12406 ResurrectChessProgram(); /* in case it isn't running */
12407 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12408 gameMode = MachinePlaysWhite;
12411 gameMode = MachinePlaysWhite;
12415 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12417 if (first.sendName) {
12418 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12419 SendToProgram(buf, &first);
12421 if (first.sendTime) {
12422 if (first.useColors) {
12423 SendToProgram("black\n", &first); /*gnu kludge*/
12425 SendTimeRemaining(&first, TRUE);
12427 if (first.useColors) {
12428 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12430 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12431 SetMachineThinkingEnables();
12432 first.maybeThinking = TRUE;
12436 if (appData.autoFlipView && !flipView) {
12437 flipView = !flipView;
12438 DrawPosition(FALSE, NULL);
12439 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12442 if(bookHit) { // [HGM] book: simulate book reply
12443 static char bookMove[MSG_SIZ]; // a bit generous?
12445 programStats.nodes = programStats.depth = programStats.time =
12446 programStats.score = programStats.got_only_move = 0;
12447 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12449 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12450 strcat(bookMove, bookHit);
12451 HandleMachineMove(bookMove, &first);
12456 MachineBlackEvent()
12459 char *bookHit = NULL;
12461 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12465 if (gameMode == PlayFromGameFile ||
12466 gameMode == TwoMachinesPlay ||
12467 gameMode == Training ||
12468 gameMode == AnalyzeMode ||
12469 gameMode == EndOfGame)
12472 if (gameMode == EditPosition)
12473 EditPositionDone(TRUE);
12475 if (WhiteOnMove(currentMove)) {
12476 DisplayError(_("It is not Black's turn"), 0);
12480 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12483 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12484 gameMode == AnalyzeFile)
12487 ResurrectChessProgram(); /* in case it isn't running */
12488 gameMode = MachinePlaysBlack;
12492 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12494 if (first.sendName) {
12495 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12496 SendToProgram(buf, &first);
12498 if (first.sendTime) {
12499 if (first.useColors) {
12500 SendToProgram("white\n", &first); /*gnu kludge*/
12502 SendTimeRemaining(&first, FALSE);
12504 if (first.useColors) {
12505 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12507 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12508 SetMachineThinkingEnables();
12509 first.maybeThinking = TRUE;
12512 if (appData.autoFlipView && flipView) {
12513 flipView = !flipView;
12514 DrawPosition(FALSE, NULL);
12515 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12517 if(bookHit) { // [HGM] book: simulate book reply
12518 static char bookMove[MSG_SIZ]; // a bit generous?
12520 programStats.nodes = programStats.depth = programStats.time =
12521 programStats.score = programStats.got_only_move = 0;
12522 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12524 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12525 strcat(bookMove, bookHit);
12526 HandleMachineMove(bookMove, &first);
12532 DisplayTwoMachinesTitle()
12535 if (appData.matchGames > 0) {
12536 if (first.twoMachinesColor[0] == 'w') {
12537 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12538 gameInfo.white, gameInfo.black,
12539 first.matchWins, second.matchWins,
12540 matchGame - 1 - (first.matchWins + second.matchWins));
12542 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12543 gameInfo.white, gameInfo.black,
12544 second.matchWins, first.matchWins,
12545 matchGame - 1 - (first.matchWins + second.matchWins));
12548 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12554 SettingsMenuIfReady()
12556 if (second.lastPing != second.lastPong) {
12557 DisplayMessage("", _("Waiting for second chess program"));
12558 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12562 DisplayMessage("", "");
12563 SettingsPopUp(&second);
12567 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12570 if (cps->pr == NULL) {
12571 StartChessProgram(cps);
12572 if (cps->protocolVersion == 1) {
12575 /* kludge: allow timeout for initial "feature" command */
12577 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12578 DisplayMessage("", buf);
12579 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12587 TwoMachinesEvent P((void))
12591 ChessProgramState *onmove;
12592 char *bookHit = NULL;
12593 static int stalling = 0;
12597 if (appData.noChessProgram) return;
12599 switch (gameMode) {
12600 case TwoMachinesPlay:
12602 case MachinePlaysWhite:
12603 case MachinePlaysBlack:
12604 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12605 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12609 case BeginningOfGame:
12610 case PlayFromGameFile:
12613 if (gameMode != EditGame) return;
12616 EditPositionDone(TRUE);
12627 // forwardMostMove = currentMove;
12628 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12630 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12632 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12633 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12634 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12638 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12639 SendToProgram("force\n", &second);
12641 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12644 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12645 if(appData.matchPause>10000 || appData.matchPause<10)
12646 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12647 wait = SubtractTimeMarks(&now, &pauseStart);
12648 if(wait < appData.matchPause) {
12649 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12653 DisplayMessage("", "");
12654 if (startedFromSetupPosition) {
12655 SendBoard(&second, backwardMostMove);
12656 if (appData.debugMode) {
12657 fprintf(debugFP, "Two Machines\n");
12660 for (i = backwardMostMove; i < forwardMostMove; i++) {
12661 SendMoveToProgram(i, &second);
12664 gameMode = TwoMachinesPlay;
12668 DisplayTwoMachinesTitle();
12670 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12675 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12676 SendToProgram(first.computerString, &first);
12677 if (first.sendName) {
12678 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12679 SendToProgram(buf, &first);
12681 SendToProgram(second.computerString, &second);
12682 if (second.sendName) {
12683 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12684 SendToProgram(buf, &second);
12688 if (!first.sendTime || !second.sendTime) {
12689 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12690 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12692 if (onmove->sendTime) {
12693 if (onmove->useColors) {
12694 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12696 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12698 if (onmove->useColors) {
12699 SendToProgram(onmove->twoMachinesColor, onmove);
12701 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12702 // SendToProgram("go\n", onmove);
12703 onmove->maybeThinking = TRUE;
12704 SetMachineThinkingEnables();
12708 if(bookHit) { // [HGM] book: simulate book reply
12709 static char bookMove[MSG_SIZ]; // a bit generous?
12711 programStats.nodes = programStats.depth = programStats.time =
12712 programStats.score = programStats.got_only_move = 0;
12713 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12715 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12716 strcat(bookMove, bookHit);
12717 savedMessage = bookMove; // args for deferred call
12718 savedState = onmove;
12719 ScheduleDelayedEvent(DeferredBookMove, 1);
12726 if (gameMode == Training) {
12727 SetTrainingModeOff();
12728 gameMode = PlayFromGameFile;
12729 DisplayMessage("", _("Training mode off"));
12731 gameMode = Training;
12732 animateTraining = appData.animate;
12734 /* make sure we are not already at the end of the game */
12735 if (currentMove < forwardMostMove) {
12736 SetTrainingModeOn();
12737 DisplayMessage("", _("Training mode on"));
12739 gameMode = PlayFromGameFile;
12740 DisplayError(_("Already at end of game"), 0);
12749 if (!appData.icsActive) return;
12750 switch (gameMode) {
12751 case IcsPlayingWhite:
12752 case IcsPlayingBlack:
12755 case BeginningOfGame:
12763 EditPositionDone(TRUE);
12776 gameMode = IcsIdle;
12787 switch (gameMode) {
12789 SetTrainingModeOff();
12791 case MachinePlaysWhite:
12792 case MachinePlaysBlack:
12793 case BeginningOfGame:
12794 SendToProgram("force\n", &first);
12795 SetUserThinkingEnables();
12797 case PlayFromGameFile:
12798 (void) StopLoadGameTimer();
12799 if (gameFileFP != NULL) {
12804 EditPositionDone(TRUE);
12809 SendToProgram("force\n", &first);
12811 case TwoMachinesPlay:
12812 GameEnds(EndOfFile, NULL, GE_PLAYER);
12813 ResurrectChessProgram();
12814 SetUserThinkingEnables();
12817 ResurrectChessProgram();
12819 case IcsPlayingBlack:
12820 case IcsPlayingWhite:
12821 DisplayError(_("Warning: You are still playing a game"), 0);
12824 DisplayError(_("Warning: You are still observing a game"), 0);
12827 DisplayError(_("Warning: You are still examining a game"), 0);
12838 first.offeredDraw = second.offeredDraw = 0;
12840 if (gameMode == PlayFromGameFile) {
12841 whiteTimeRemaining = timeRemaining[0][currentMove];
12842 blackTimeRemaining = timeRemaining[1][currentMove];
12846 if (gameMode == MachinePlaysWhite ||
12847 gameMode == MachinePlaysBlack ||
12848 gameMode == TwoMachinesPlay ||
12849 gameMode == EndOfGame) {
12850 i = forwardMostMove;
12851 while (i > currentMove) {
12852 SendToProgram("undo\n", &first);
12855 whiteTimeRemaining = timeRemaining[0][currentMove];
12856 blackTimeRemaining = timeRemaining[1][currentMove];
12857 DisplayBothClocks();
12858 if (whiteFlag || blackFlag) {
12859 whiteFlag = blackFlag = 0;
12864 gameMode = EditGame;
12871 EditPositionEvent()
12873 if (gameMode == EditPosition) {
12879 if (gameMode != EditGame) return;
12881 gameMode = EditPosition;
12884 if (currentMove > 0)
12885 CopyBoard(boards[0], boards[currentMove]);
12887 blackPlaysFirst = !WhiteOnMove(currentMove);
12889 currentMove = forwardMostMove = backwardMostMove = 0;
12890 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12897 /* [DM] icsEngineAnalyze - possible call from other functions */
12898 if (appData.icsEngineAnalyze) {
12899 appData.icsEngineAnalyze = FALSE;
12901 DisplayMessage("",_("Close ICS engine analyze..."));
12903 if (first.analysisSupport && first.analyzing) {
12904 SendToProgram("exit\n", &first);
12905 first.analyzing = FALSE;
12907 thinkOutput[0] = NULLCHAR;
12911 EditPositionDone(Boolean fakeRights)
12913 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12915 startedFromSetupPosition = TRUE;
12916 InitChessProgram(&first, FALSE);
12917 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12918 boards[0][EP_STATUS] = EP_NONE;
12919 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12920 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12921 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12922 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12923 } else boards[0][CASTLING][2] = NoRights;
12924 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12925 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12926 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12927 } else boards[0][CASTLING][5] = NoRights;
12929 SendToProgram("force\n", &first);
12930 if (blackPlaysFirst) {
12931 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12932 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12933 currentMove = forwardMostMove = backwardMostMove = 1;
12934 CopyBoard(boards[1], boards[0]);
12936 currentMove = forwardMostMove = backwardMostMove = 0;
12938 SendBoard(&first, forwardMostMove);
12939 if (appData.debugMode) {
12940 fprintf(debugFP, "EditPosDone\n");
12943 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12944 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12945 gameMode = EditGame;
12947 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12948 ClearHighlights(); /* [AS] */
12951 /* Pause for `ms' milliseconds */
12952 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12962 } while (SubtractTimeMarks(&m2, &m1) < ms);
12965 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12967 SendMultiLineToICS(buf)
12970 char temp[MSG_SIZ+1], *p;
12977 strncpy(temp, buf, len);
12982 if (*p == '\n' || *p == '\r')
12987 strcat(temp, "\n");
12989 SendToPlayer(temp, strlen(temp));
12993 SetWhiteToPlayEvent()
12995 if (gameMode == EditPosition) {
12996 blackPlaysFirst = FALSE;
12997 DisplayBothClocks(); /* works because currentMove is 0 */
12998 } else if (gameMode == IcsExamining) {
12999 SendToICS(ics_prefix);
13000 SendToICS("tomove white\n");
13005 SetBlackToPlayEvent()
13007 if (gameMode == EditPosition) {
13008 blackPlaysFirst = TRUE;
13009 currentMove = 1; /* kludge */
13010 DisplayBothClocks();
13012 } else if (gameMode == IcsExamining) {
13013 SendToICS(ics_prefix);
13014 SendToICS("tomove black\n");
13019 EditPositionMenuEvent(selection, x, y)
13020 ChessSquare selection;
13024 ChessSquare piece = boards[0][y][x];
13026 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13028 switch (selection) {
13030 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13031 SendToICS(ics_prefix);
13032 SendToICS("bsetup clear\n");
13033 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13034 SendToICS(ics_prefix);
13035 SendToICS("clearboard\n");
13037 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13038 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13039 for (y = 0; y < BOARD_HEIGHT; y++) {
13040 if (gameMode == IcsExamining) {
13041 if (boards[currentMove][y][x] != EmptySquare) {
13042 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13047 boards[0][y][x] = p;
13052 if (gameMode == EditPosition) {
13053 DrawPosition(FALSE, boards[0]);
13058 SetWhiteToPlayEvent();
13062 SetBlackToPlayEvent();
13066 if (gameMode == IcsExamining) {
13067 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13068 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13071 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13072 if(x == BOARD_LEFT-2) {
13073 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13074 boards[0][y][1] = 0;
13076 if(x == BOARD_RGHT+1) {
13077 if(y >= gameInfo.holdingsSize) break;
13078 boards[0][y][BOARD_WIDTH-2] = 0;
13081 boards[0][y][x] = EmptySquare;
13082 DrawPosition(FALSE, boards[0]);
13087 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13088 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13089 selection = (ChessSquare) (PROMOTED piece);
13090 } else if(piece == EmptySquare) selection = WhiteSilver;
13091 else selection = (ChessSquare)((int)piece - 1);
13095 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13096 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13097 selection = (ChessSquare) (DEMOTED piece);
13098 } else if(piece == EmptySquare) selection = BlackSilver;
13099 else selection = (ChessSquare)((int)piece + 1);
13104 if(gameInfo.variant == VariantShatranj ||
13105 gameInfo.variant == VariantXiangqi ||
13106 gameInfo.variant == VariantCourier ||
13107 gameInfo.variant == VariantMakruk )
13108 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13113 if(gameInfo.variant == VariantXiangqi)
13114 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13115 if(gameInfo.variant == VariantKnightmate)
13116 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13119 if (gameMode == IcsExamining) {
13120 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13121 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13122 PieceToChar(selection), AAA + x, ONE + y);
13125 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13127 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13128 n = PieceToNumber(selection - BlackPawn);
13129 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13130 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13131 boards[0][BOARD_HEIGHT-1-n][1]++;
13133 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13134 n = PieceToNumber(selection);
13135 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13136 boards[0][n][BOARD_WIDTH-1] = selection;
13137 boards[0][n][BOARD_WIDTH-2]++;
13140 boards[0][y][x] = selection;
13141 DrawPosition(TRUE, boards[0]);
13149 DropMenuEvent(selection, x, y)
13150 ChessSquare selection;
13153 ChessMove moveType;
13155 switch (gameMode) {
13156 case IcsPlayingWhite:
13157 case MachinePlaysBlack:
13158 if (!WhiteOnMove(currentMove)) {
13159 DisplayMoveError(_("It is Black's turn"));
13162 moveType = WhiteDrop;
13164 case IcsPlayingBlack:
13165 case MachinePlaysWhite:
13166 if (WhiteOnMove(currentMove)) {
13167 DisplayMoveError(_("It is White's turn"));
13170 moveType = BlackDrop;
13173 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13179 if (moveType == BlackDrop && selection < BlackPawn) {
13180 selection = (ChessSquare) ((int) selection
13181 + (int) BlackPawn - (int) WhitePawn);
13183 if (boards[currentMove][y][x] != EmptySquare) {
13184 DisplayMoveError(_("That square is occupied"));
13188 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13194 /* Accept a pending offer of any kind from opponent */
13196 if (appData.icsActive) {
13197 SendToICS(ics_prefix);
13198 SendToICS("accept\n");
13199 } else if (cmailMsgLoaded) {
13200 if (currentMove == cmailOldMove &&
13201 commentList[cmailOldMove] != NULL &&
13202 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13203 "Black offers a draw" : "White offers a draw")) {
13205 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13206 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13208 DisplayError(_("There is no pending offer on this move"), 0);
13209 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13212 /* Not used for offers from chess program */
13219 /* Decline a pending offer of any kind from opponent */
13221 if (appData.icsActive) {
13222 SendToICS(ics_prefix);
13223 SendToICS("decline\n");
13224 } else if (cmailMsgLoaded) {
13225 if (currentMove == cmailOldMove &&
13226 commentList[cmailOldMove] != NULL &&
13227 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13228 "Black offers a draw" : "White offers a draw")) {
13230 AppendComment(cmailOldMove, "Draw declined", TRUE);
13231 DisplayComment(cmailOldMove - 1, "Draw declined");
13234 DisplayError(_("There is no pending offer on this move"), 0);
13237 /* Not used for offers from chess program */
13244 /* Issue ICS rematch command */
13245 if (appData.icsActive) {
13246 SendToICS(ics_prefix);
13247 SendToICS("rematch\n");
13254 /* Call your opponent's flag (claim a win on time) */
13255 if (appData.icsActive) {
13256 SendToICS(ics_prefix);
13257 SendToICS("flag\n");
13259 switch (gameMode) {
13262 case MachinePlaysWhite:
13265 GameEnds(GameIsDrawn, "Both players ran out of time",
13268 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13270 DisplayError(_("Your opponent is not out of time"), 0);
13273 case MachinePlaysBlack:
13276 GameEnds(GameIsDrawn, "Both players ran out of time",
13279 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13281 DisplayError(_("Your opponent is not out of time"), 0);
13289 ClockClick(int which)
13290 { // [HGM] code moved to back-end from winboard.c
13291 if(which) { // black clock
13292 if (gameMode == EditPosition || gameMode == IcsExamining) {
13293 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13294 SetBlackToPlayEvent();
13295 } else if (gameMode == EditGame || shiftKey) {
13296 AdjustClock(which, -1);
13297 } else if (gameMode == IcsPlayingWhite ||
13298 gameMode == MachinePlaysBlack) {
13301 } else { // white clock
13302 if (gameMode == EditPosition || gameMode == IcsExamining) {
13303 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13304 SetWhiteToPlayEvent();
13305 } else if (gameMode == EditGame || shiftKey) {
13306 AdjustClock(which, -1);
13307 } else if (gameMode == IcsPlayingBlack ||
13308 gameMode == MachinePlaysWhite) {
13317 /* Offer draw or accept pending draw offer from opponent */
13319 if (appData.icsActive) {
13320 /* Note: tournament rules require draw offers to be
13321 made after you make your move but before you punch
13322 your clock. Currently ICS doesn't let you do that;
13323 instead, you immediately punch your clock after making
13324 a move, but you can offer a draw at any time. */
13326 SendToICS(ics_prefix);
13327 SendToICS("draw\n");
13328 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13329 } else if (cmailMsgLoaded) {
13330 if (currentMove == cmailOldMove &&
13331 commentList[cmailOldMove] != NULL &&
13332 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13333 "Black offers a draw" : "White offers a draw")) {
13334 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13335 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13336 } else if (currentMove == cmailOldMove + 1) {
13337 char *offer = WhiteOnMove(cmailOldMove) ?
13338 "White offers a draw" : "Black offers a draw";
13339 AppendComment(currentMove, offer, TRUE);
13340 DisplayComment(currentMove - 1, offer);
13341 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13343 DisplayError(_("You must make your move before offering a draw"), 0);
13344 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13346 } else if (first.offeredDraw) {
13347 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13349 if (first.sendDrawOffers) {
13350 SendToProgram("draw\n", &first);
13351 userOfferedDraw = TRUE;
13359 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13361 if (appData.icsActive) {
13362 SendToICS(ics_prefix);
13363 SendToICS("adjourn\n");
13365 /* Currently GNU Chess doesn't offer or accept Adjourns */
13373 /* Offer Abort or accept pending Abort offer from opponent */
13375 if (appData.icsActive) {
13376 SendToICS(ics_prefix);
13377 SendToICS("abort\n");
13379 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13386 /* Resign. You can do this even if it's not your turn. */
13388 if (appData.icsActive) {
13389 SendToICS(ics_prefix);
13390 SendToICS("resign\n");
13392 switch (gameMode) {
13393 case MachinePlaysWhite:
13394 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13396 case MachinePlaysBlack:
13397 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13400 if (cmailMsgLoaded) {
13402 if (WhiteOnMove(cmailOldMove)) {
13403 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13405 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13407 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13418 StopObservingEvent()
13420 /* Stop observing current games */
13421 SendToICS(ics_prefix);
13422 SendToICS("unobserve\n");
13426 StopExaminingEvent()
13428 /* Stop observing current game */
13429 SendToICS(ics_prefix);
13430 SendToICS("unexamine\n");
13434 ForwardInner(target)
13439 if (appData.debugMode)
13440 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13441 target, currentMove, forwardMostMove);
13443 if (gameMode == EditPosition)
13446 if (gameMode == PlayFromGameFile && !pausing)
13449 if (gameMode == IcsExamining && pausing)
13450 limit = pauseExamForwardMostMove;
13452 limit = forwardMostMove;
13454 if (target > limit) target = limit;
13456 if (target > 0 && moveList[target - 1][0]) {
13457 int fromX, fromY, toX, toY;
13458 toX = moveList[target - 1][2] - AAA;
13459 toY = moveList[target - 1][3] - ONE;
13460 if (moveList[target - 1][1] == '@') {
13461 if (appData.highlightLastMove) {
13462 SetHighlights(-1, -1, toX, toY);
13465 fromX = moveList[target - 1][0] - AAA;
13466 fromY = moveList[target - 1][1] - ONE;
13467 if (target == currentMove + 1) {
13468 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13470 if (appData.highlightLastMove) {
13471 SetHighlights(fromX, fromY, toX, toY);
13475 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13476 gameMode == Training || gameMode == PlayFromGameFile ||
13477 gameMode == AnalyzeFile) {
13478 while (currentMove < target) {
13479 SendMoveToProgram(currentMove++, &first);
13482 currentMove = target;
13485 if (gameMode == EditGame || gameMode == EndOfGame) {
13486 whiteTimeRemaining = timeRemaining[0][currentMove];
13487 blackTimeRemaining = timeRemaining[1][currentMove];
13489 DisplayBothClocks();
13490 DisplayMove(currentMove - 1);
13491 DrawPosition(FALSE, boards[currentMove]);
13492 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13493 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13494 DisplayComment(currentMove - 1, commentList[currentMove]);
13496 DisplayBook(currentMove);
13503 if (gameMode == IcsExamining && !pausing) {
13504 SendToICS(ics_prefix);
13505 SendToICS("forward\n");
13507 ForwardInner(currentMove + 1);
13514 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13515 /* to optimze, we temporarily turn off analysis mode while we feed
13516 * the remaining moves to the engine. Otherwise we get analysis output
13519 if (first.analysisSupport) {
13520 SendToProgram("exit\nforce\n", &first);
13521 first.analyzing = FALSE;
13525 if (gameMode == IcsExamining && !pausing) {
13526 SendToICS(ics_prefix);
13527 SendToICS("forward 999999\n");
13529 ForwardInner(forwardMostMove);
13532 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13533 /* we have fed all the moves, so reactivate analysis mode */
13534 SendToProgram("analyze\n", &first);
13535 first.analyzing = TRUE;
13536 /*first.maybeThinking = TRUE;*/
13537 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13542 BackwardInner(target)
13545 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13547 if (appData.debugMode)
13548 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13549 target, currentMove, forwardMostMove);
13551 if (gameMode == EditPosition) return;
13552 if (currentMove <= backwardMostMove) {
13554 DrawPosition(full_redraw, boards[currentMove]);
13557 if (gameMode == PlayFromGameFile && !pausing)
13560 if (moveList[target][0]) {
13561 int fromX, fromY, toX, toY;
13562 toX = moveList[target][2] - AAA;
13563 toY = moveList[target][3] - ONE;
13564 if (moveList[target][1] == '@') {
13565 if (appData.highlightLastMove) {
13566 SetHighlights(-1, -1, toX, toY);
13569 fromX = moveList[target][0] - AAA;
13570 fromY = moveList[target][1] - ONE;
13571 if (target == currentMove - 1) {
13572 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13574 if (appData.highlightLastMove) {
13575 SetHighlights(fromX, fromY, toX, toY);
13579 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13580 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13581 while (currentMove > target) {
13582 SendToProgram("undo\n", &first);
13586 currentMove = target;
13589 if (gameMode == EditGame || gameMode == EndOfGame) {
13590 whiteTimeRemaining = timeRemaining[0][currentMove];
13591 blackTimeRemaining = timeRemaining[1][currentMove];
13593 DisplayBothClocks();
13594 DisplayMove(currentMove - 1);
13595 DrawPosition(full_redraw, boards[currentMove]);
13596 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13597 // [HGM] PV info: routine tests if comment empty
13598 DisplayComment(currentMove - 1, commentList[currentMove]);
13599 DisplayBook(currentMove);
13605 if (gameMode == IcsExamining && !pausing) {
13606 SendToICS(ics_prefix);
13607 SendToICS("backward\n");
13609 BackwardInner(currentMove - 1);
13616 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13617 /* to optimize, we temporarily turn off analysis mode while we undo
13618 * all the moves. Otherwise we get analysis output after each undo.
13620 if (first.analysisSupport) {
13621 SendToProgram("exit\nforce\n", &first);
13622 first.analyzing = FALSE;
13626 if (gameMode == IcsExamining && !pausing) {
13627 SendToICS(ics_prefix);
13628 SendToICS("backward 999999\n");
13630 BackwardInner(backwardMostMove);
13633 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13634 /* we have fed all the moves, so reactivate analysis mode */
13635 SendToProgram("analyze\n", &first);
13636 first.analyzing = TRUE;
13637 /*first.maybeThinking = TRUE;*/
13638 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13645 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13646 if (to >= forwardMostMove) to = forwardMostMove;
13647 if (to <= backwardMostMove) to = backwardMostMove;
13648 if (to < currentMove) {
13656 RevertEvent(Boolean annotate)
13658 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13661 if (gameMode != IcsExamining) {
13662 DisplayError(_("You are not examining a game"), 0);
13666 DisplayError(_("You can't revert while pausing"), 0);
13669 SendToICS(ics_prefix);
13670 SendToICS("revert\n");
13676 switch (gameMode) {
13677 case MachinePlaysWhite:
13678 case MachinePlaysBlack:
13679 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13680 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13683 if (forwardMostMove < 2) return;
13684 currentMove = forwardMostMove = forwardMostMove - 2;
13685 whiteTimeRemaining = timeRemaining[0][currentMove];
13686 blackTimeRemaining = timeRemaining[1][currentMove];
13687 DisplayBothClocks();
13688 DisplayMove(currentMove - 1);
13689 ClearHighlights();/*!! could figure this out*/
13690 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13691 SendToProgram("remove\n", &first);
13692 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13695 case BeginningOfGame:
13699 case IcsPlayingWhite:
13700 case IcsPlayingBlack:
13701 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13702 SendToICS(ics_prefix);
13703 SendToICS("takeback 2\n");
13705 SendToICS(ics_prefix);
13706 SendToICS("takeback 1\n");
13715 ChessProgramState *cps;
13717 switch (gameMode) {
13718 case MachinePlaysWhite:
13719 if (!WhiteOnMove(forwardMostMove)) {
13720 DisplayError(_("It is your turn"), 0);
13725 case MachinePlaysBlack:
13726 if (WhiteOnMove(forwardMostMove)) {
13727 DisplayError(_("It is your turn"), 0);
13732 case TwoMachinesPlay:
13733 if (WhiteOnMove(forwardMostMove) ==
13734 (first.twoMachinesColor[0] == 'w')) {
13740 case BeginningOfGame:
13744 SendToProgram("?\n", cps);
13748 TruncateGameEvent()
13751 if (gameMode != EditGame) return;
13758 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13759 if (forwardMostMove > currentMove) {
13760 if (gameInfo.resultDetails != NULL) {
13761 free(gameInfo.resultDetails);
13762 gameInfo.resultDetails = NULL;
13763 gameInfo.result = GameUnfinished;
13765 forwardMostMove = currentMove;
13766 HistorySet(parseList, backwardMostMove, forwardMostMove,
13774 if (appData.noChessProgram) return;
13775 switch (gameMode) {
13776 case MachinePlaysWhite:
13777 if (WhiteOnMove(forwardMostMove)) {
13778 DisplayError(_("Wait until your turn"), 0);
13782 case BeginningOfGame:
13783 case MachinePlaysBlack:
13784 if (!WhiteOnMove(forwardMostMove)) {
13785 DisplayError(_("Wait until your turn"), 0);
13790 DisplayError(_("No hint available"), 0);
13793 SendToProgram("hint\n", &first);
13794 hintRequested = TRUE;
13800 if (appData.noChessProgram) return;
13801 switch (gameMode) {
13802 case MachinePlaysWhite:
13803 if (WhiteOnMove(forwardMostMove)) {
13804 DisplayError(_("Wait until your turn"), 0);
13808 case BeginningOfGame:
13809 case MachinePlaysBlack:
13810 if (!WhiteOnMove(forwardMostMove)) {
13811 DisplayError(_("Wait until your turn"), 0);
13816 EditPositionDone(TRUE);
13818 case TwoMachinesPlay:
13823 SendToProgram("bk\n", &first);
13824 bookOutput[0] = NULLCHAR;
13825 bookRequested = TRUE;
13831 char *tags = PGNTags(&gameInfo);
13832 TagsPopUp(tags, CmailMsg());
13836 /* end button procedures */
13839 PrintPosition(fp, move)
13845 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13846 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13847 char c = PieceToChar(boards[move][i][j]);
13848 fputc(c == 'x' ? '.' : c, fp);
13849 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13852 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13853 fprintf(fp, "white to play\n");
13855 fprintf(fp, "black to play\n");
13862 if (gameInfo.white != NULL) {
13863 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13869 /* Find last component of program's own name, using some heuristics */
13871 TidyProgramName(prog, host, buf)
13872 char *prog, *host, buf[MSG_SIZ];
13875 int local = (strcmp(host, "localhost") == 0);
13876 while (!local && (p = strchr(prog, ';')) != NULL) {
13878 while (*p == ' ') p++;
13881 if (*prog == '"' || *prog == '\'') {
13882 q = strchr(prog + 1, *prog);
13884 q = strchr(prog, ' ');
13886 if (q == NULL) q = prog + strlen(prog);
13888 while (p >= prog && *p != '/' && *p != '\\') p--;
13890 if(p == prog && *p == '"') p++;
13891 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13892 memcpy(buf, p, q - p);
13893 buf[q - p] = NULLCHAR;
13901 TimeControlTagValue()
13904 if (!appData.clockMode) {
13905 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13906 } else if (movesPerSession > 0) {
13907 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13908 } else if (timeIncrement == 0) {
13909 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13911 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13913 return StrSave(buf);
13919 /* This routine is used only for certain modes */
13920 VariantClass v = gameInfo.variant;
13921 ChessMove r = GameUnfinished;
13924 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13925 r = gameInfo.result;
13926 p = gameInfo.resultDetails;
13927 gameInfo.resultDetails = NULL;
13929 ClearGameInfo(&gameInfo);
13930 gameInfo.variant = v;
13932 switch (gameMode) {
13933 case MachinePlaysWhite:
13934 gameInfo.event = StrSave( appData.pgnEventHeader );
13935 gameInfo.site = StrSave(HostName());
13936 gameInfo.date = PGNDate();
13937 gameInfo.round = StrSave("-");
13938 gameInfo.white = StrSave(first.tidy);
13939 gameInfo.black = StrSave(UserName());
13940 gameInfo.timeControl = TimeControlTagValue();
13943 case MachinePlaysBlack:
13944 gameInfo.event = StrSave( appData.pgnEventHeader );
13945 gameInfo.site = StrSave(HostName());
13946 gameInfo.date = PGNDate();
13947 gameInfo.round = StrSave("-");
13948 gameInfo.white = StrSave(UserName());
13949 gameInfo.black = StrSave(first.tidy);
13950 gameInfo.timeControl = TimeControlTagValue();
13953 case TwoMachinesPlay:
13954 gameInfo.event = StrSave( appData.pgnEventHeader );
13955 gameInfo.site = StrSave(HostName());
13956 gameInfo.date = PGNDate();
13959 snprintf(buf, MSG_SIZ, "%d", roundNr);
13960 gameInfo.round = StrSave(buf);
13962 gameInfo.round = StrSave("-");
13964 if (first.twoMachinesColor[0] == 'w') {
13965 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13966 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13968 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13969 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13971 gameInfo.timeControl = TimeControlTagValue();
13975 gameInfo.event = StrSave("Edited game");
13976 gameInfo.site = StrSave(HostName());
13977 gameInfo.date = PGNDate();
13978 gameInfo.round = StrSave("-");
13979 gameInfo.white = StrSave("-");
13980 gameInfo.black = StrSave("-");
13981 gameInfo.result = r;
13982 gameInfo.resultDetails = p;
13986 gameInfo.event = StrSave("Edited position");
13987 gameInfo.site = StrSave(HostName());
13988 gameInfo.date = PGNDate();
13989 gameInfo.round = StrSave("-");
13990 gameInfo.white = StrSave("-");
13991 gameInfo.black = StrSave("-");
13994 case IcsPlayingWhite:
13995 case IcsPlayingBlack:
14000 case PlayFromGameFile:
14001 gameInfo.event = StrSave("Game from non-PGN file");
14002 gameInfo.site = StrSave(HostName());
14003 gameInfo.date = PGNDate();
14004 gameInfo.round = StrSave("-");
14005 gameInfo.white = StrSave("?");
14006 gameInfo.black = StrSave("?");
14015 ReplaceComment(index, text)
14023 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14024 pvInfoList[index-1].depth == len &&
14025 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14026 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14027 while (*text == '\n') text++;
14028 len = strlen(text);
14029 while (len > 0 && text[len - 1] == '\n') len--;
14031 if (commentList[index] != NULL)
14032 free(commentList[index]);
14035 commentList[index] = NULL;
14038 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14039 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14040 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14041 commentList[index] = (char *) malloc(len + 2);
14042 strncpy(commentList[index], text, len);
14043 commentList[index][len] = '\n';
14044 commentList[index][len + 1] = NULLCHAR;
14046 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14048 commentList[index] = (char *) malloc(len + 7);
14049 safeStrCpy(commentList[index], "{\n", 3);
14050 safeStrCpy(commentList[index]+2, text, len+1);
14051 commentList[index][len+2] = NULLCHAR;
14052 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14053 strcat(commentList[index], "\n}\n");
14067 if (ch == '\r') continue;
14069 } while (ch != '\0');
14073 AppendComment(index, text, addBraces)
14076 Boolean addBraces; // [HGM] braces: tells if we should add {}
14081 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14082 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14085 while (*text == '\n') text++;
14086 len = strlen(text);
14087 while (len > 0 && text[len - 1] == '\n') len--;
14089 if (len == 0) return;
14091 if (commentList[index] != NULL) {
14092 old = commentList[index];
14093 oldlen = strlen(old);
14094 while(commentList[index][oldlen-1] == '\n')
14095 commentList[index][--oldlen] = NULLCHAR;
14096 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14097 safeStrCpy(commentList[index], old, oldlen + len + 6);
14099 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14100 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14101 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14102 while (*text == '\n') { text++; len--; }
14103 commentList[index][--oldlen] = NULLCHAR;
14105 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14106 else strcat(commentList[index], "\n");
14107 strcat(commentList[index], text);
14108 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14109 else strcat(commentList[index], "\n");
14111 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14113 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14114 else commentList[index][0] = NULLCHAR;
14115 strcat(commentList[index], text);
14116 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14117 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14121 static char * FindStr( char * text, char * sub_text )
14123 char * result = strstr( text, sub_text );
14125 if( result != NULL ) {
14126 result += strlen( sub_text );
14132 /* [AS] Try to extract PV info from PGN comment */
14133 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14134 char *GetInfoFromComment( int index, char * text )
14136 char * sep = text, *p;
14138 if( text != NULL && index > 0 ) {
14141 int time = -1, sec = 0, deci;
14142 char * s_eval = FindStr( text, "[%eval " );
14143 char * s_emt = FindStr( text, "[%emt " );
14145 if( s_eval != NULL || s_emt != NULL ) {
14149 if( s_eval != NULL ) {
14150 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14154 if( delim != ']' ) {
14159 if( s_emt != NULL ) {
14164 /* We expect something like: [+|-]nnn.nn/dd */
14167 if(*text != '{') return text; // [HGM] braces: must be normal comment
14169 sep = strchr( text, '/' );
14170 if( sep == NULL || sep < (text+4) ) {
14175 if(p[1] == '(') { // comment starts with PV
14176 p = strchr(p, ')'); // locate end of PV
14177 if(p == NULL || sep < p+5) return text;
14178 // at this point we have something like "{(.*) +0.23/6 ..."
14179 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14180 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14181 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14183 time = -1; sec = -1; deci = -1;
14184 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14185 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14186 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14187 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14191 if( score_lo < 0 || score_lo >= 100 ) {
14195 if(sec >= 0) time = 600*time + 10*sec; else
14196 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14198 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14200 /* [HGM] PV time: now locate end of PV info */
14201 while( *++sep >= '0' && *sep <= '9'); // strip depth
14203 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14205 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14207 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14208 while(*sep == ' ') sep++;
14219 pvInfoList[index-1].depth = depth;
14220 pvInfoList[index-1].score = score;
14221 pvInfoList[index-1].time = 10*time; // centi-sec
14222 if(*sep == '}') *sep = 0; else *--sep = '{';
14223 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14229 SendToProgram(message, cps)
14231 ChessProgramState *cps;
14233 int count, outCount, error;
14236 if (cps->pr == NULL) return;
14239 if (appData.debugMode) {
14242 fprintf(debugFP, "%ld >%-6s: %s",
14243 SubtractTimeMarks(&now, &programStartTime),
14244 cps->which, message);
14247 count = strlen(message);
14248 outCount = OutputToProcess(cps->pr, message, count, &error);
14249 if (outCount < count && !exiting
14250 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14251 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14252 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14253 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14254 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14255 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14256 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14257 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14259 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14260 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14261 gameInfo.result = res;
14263 gameInfo.resultDetails = StrSave(buf);
14265 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14266 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14271 ReceiveFromProgram(isr, closure, message, count, error)
14272 InputSourceRef isr;
14280 ChessProgramState *cps = (ChessProgramState *)closure;
14282 if (isr != cps->isr) return; /* Killed intentionally */
14285 RemoveInputSource(cps->isr);
14286 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14287 _(cps->which), cps->program);
14288 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14289 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14290 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14291 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14292 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14294 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14295 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14296 gameInfo.result = res;
14298 gameInfo.resultDetails = StrSave(buf);
14300 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14301 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14303 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14304 _(cps->which), cps->program);
14305 RemoveInputSource(cps->isr);
14307 /* [AS] Program is misbehaving badly... kill it */
14308 if( count == -2 ) {
14309 DestroyChildProcess( cps->pr, 9 );
14313 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14318 if ((end_str = strchr(message, '\r')) != NULL)
14319 *end_str = NULLCHAR;
14320 if ((end_str = strchr(message, '\n')) != NULL)
14321 *end_str = NULLCHAR;
14323 if (appData.debugMode) {
14324 TimeMark now; int print = 1;
14325 char *quote = ""; char c; int i;
14327 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14328 char start = message[0];
14329 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14330 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14331 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14332 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14333 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14334 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14335 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14336 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14337 sscanf(message, "hint: %c", &c)!=1 &&
14338 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14339 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14340 print = (appData.engineComments >= 2);
14342 message[0] = start; // restore original message
14346 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14347 SubtractTimeMarks(&now, &programStartTime), cps->which,
14353 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14354 if (appData.icsEngineAnalyze) {
14355 if (strstr(message, "whisper") != NULL ||
14356 strstr(message, "kibitz") != NULL ||
14357 strstr(message, "tellics") != NULL) return;
14360 HandleMachineMove(message, cps);
14365 SendTimeControl(cps, mps, tc, inc, sd, st)
14366 ChessProgramState *cps;
14367 int mps, inc, sd, st;
14373 if( timeControl_2 > 0 ) {
14374 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14375 tc = timeControl_2;
14378 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14379 inc /= cps->timeOdds;
14380 st /= cps->timeOdds;
14382 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14385 /* Set exact time per move, normally using st command */
14386 if (cps->stKludge) {
14387 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14389 if (seconds == 0) {
14390 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14392 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14395 snprintf(buf, MSG_SIZ, "st %d\n", st);
14398 /* Set conventional or incremental time control, using level command */
14399 if (seconds == 0) {
14400 /* Note old gnuchess bug -- minutes:seconds used to not work.
14401 Fixed in later versions, but still avoid :seconds
14402 when seconds is 0. */
14403 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14405 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14406 seconds, inc/1000.);
14409 SendToProgram(buf, cps);
14411 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14412 /* Orthogonally, limit search to given depth */
14414 if (cps->sdKludge) {
14415 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14417 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14419 SendToProgram(buf, cps);
14422 if(cps->nps >= 0) { /* [HGM] nps */
14423 if(cps->supportsNPS == FALSE)
14424 cps->nps = -1; // don't use if engine explicitly says not supported!
14426 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14427 SendToProgram(buf, cps);
14432 ChessProgramState *WhitePlayer()
14433 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14435 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14436 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14442 SendTimeRemaining(cps, machineWhite)
14443 ChessProgramState *cps;
14444 int /*boolean*/ machineWhite;
14446 char message[MSG_SIZ];
14449 /* Note: this routine must be called when the clocks are stopped
14450 or when they have *just* been set or switched; otherwise
14451 it will be off by the time since the current tick started.
14453 if (machineWhite) {
14454 time = whiteTimeRemaining / 10;
14455 otime = blackTimeRemaining / 10;
14457 time = blackTimeRemaining / 10;
14458 otime = whiteTimeRemaining / 10;
14460 /* [HGM] translate opponent's time by time-odds factor */
14461 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14462 if (appData.debugMode) {
14463 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14466 if (time <= 0) time = 1;
14467 if (otime <= 0) otime = 1;
14469 snprintf(message, MSG_SIZ, "time %ld\n", time);
14470 SendToProgram(message, cps);
14472 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14473 SendToProgram(message, cps);
14477 BoolFeature(p, name, loc, cps)
14481 ChessProgramState *cps;
14484 int len = strlen(name);
14487 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14489 sscanf(*p, "%d", &val);
14491 while (**p && **p != ' ')
14493 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14494 SendToProgram(buf, cps);
14501 IntFeature(p, name, loc, cps)
14505 ChessProgramState *cps;
14508 int len = strlen(name);
14509 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14511 sscanf(*p, "%d", loc);
14512 while (**p && **p != ' ') (*p)++;
14513 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14514 SendToProgram(buf, cps);
14521 StringFeature(p, name, loc, cps)
14525 ChessProgramState *cps;
14528 int len = strlen(name);
14529 if (strncmp((*p), name, len) == 0
14530 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14532 sscanf(*p, "%[^\"]", loc);
14533 while (**p && **p != '\"') (*p)++;
14534 if (**p == '\"') (*p)++;
14535 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14536 SendToProgram(buf, cps);
14543 ParseOption(Option *opt, ChessProgramState *cps)
14544 // [HGM] options: process the string that defines an engine option, and determine
14545 // name, type, default value, and allowed value range
14547 char *p, *q, buf[MSG_SIZ];
14548 int n, min = (-1)<<31, max = 1<<31, def;
14550 if(p = strstr(opt->name, " -spin ")) {
14551 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14552 if(max < min) max = min; // enforce consistency
14553 if(def < min) def = min;
14554 if(def > max) def = max;
14559 } else if((p = strstr(opt->name, " -slider "))) {
14560 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14561 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14562 if(max < min) max = min; // enforce consistency
14563 if(def < min) def = min;
14564 if(def > max) def = max;
14568 opt->type = Spin; // Slider;
14569 } else if((p = strstr(opt->name, " -string "))) {
14570 opt->textValue = p+9;
14571 opt->type = TextBox;
14572 } else if((p = strstr(opt->name, " -file "))) {
14573 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14574 opt->textValue = p+7;
14575 opt->type = FileName; // FileName;
14576 } else if((p = strstr(opt->name, " -path "))) {
14577 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14578 opt->textValue = p+7;
14579 opt->type = PathName; // PathName;
14580 } else if(p = strstr(opt->name, " -check ")) {
14581 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14582 opt->value = (def != 0);
14583 opt->type = CheckBox;
14584 } else if(p = strstr(opt->name, " -combo ")) {
14585 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14586 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14587 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14588 opt->value = n = 0;
14589 while(q = StrStr(q, " /// ")) {
14590 n++; *q = 0; // count choices, and null-terminate each of them
14592 if(*q == '*') { // remember default, which is marked with * prefix
14596 cps->comboList[cps->comboCnt++] = q;
14598 cps->comboList[cps->comboCnt++] = NULL;
14600 opt->type = ComboBox;
14601 } else if(p = strstr(opt->name, " -button")) {
14602 opt->type = Button;
14603 } else if(p = strstr(opt->name, " -save")) {
14604 opt->type = SaveButton;
14605 } else return FALSE;
14606 *p = 0; // terminate option name
14607 // now look if the command-line options define a setting for this engine option.
14608 if(cps->optionSettings && cps->optionSettings[0])
14609 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14610 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14611 snprintf(buf, MSG_SIZ, "option %s", p);
14612 if(p = strstr(buf, ",")) *p = 0;
14613 if(q = strchr(buf, '=')) switch(opt->type) {
14615 for(n=0; n<opt->max; n++)
14616 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14619 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14623 opt->value = atoi(q+1);
14628 SendToProgram(buf, cps);
14634 FeatureDone(cps, val)
14635 ChessProgramState* cps;
14638 DelayedEventCallback cb = GetDelayedEvent();
14639 if ((cb == InitBackEnd3 && cps == &first) ||
14640 (cb == SettingsMenuIfReady && cps == &second) ||
14641 (cb == LoadEngine) ||
14642 (cb == TwoMachinesEventIfReady)) {
14643 CancelDelayedEvent();
14644 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14646 cps->initDone = val;
14649 /* Parse feature command from engine */
14651 ParseFeatures(args, cps)
14653 ChessProgramState *cps;
14661 while (*p == ' ') p++;
14662 if (*p == NULLCHAR) return;
14664 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14665 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14666 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14667 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14668 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14669 if (BoolFeature(&p, "reuse", &val, cps)) {
14670 /* Engine can disable reuse, but can't enable it if user said no */
14671 if (!val) cps->reuse = FALSE;
14674 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14675 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14676 if (gameMode == TwoMachinesPlay) {
14677 DisplayTwoMachinesTitle();
14683 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14684 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14685 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14686 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14687 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14688 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14689 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14690 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14691 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14692 if (IntFeature(&p, "done", &val, cps)) {
14693 FeatureDone(cps, val);
14696 /* Added by Tord: */
14697 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14698 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14699 /* End of additions by Tord */
14701 /* [HGM] added features: */
14702 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14703 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14704 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14705 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14706 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14707 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14708 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14709 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14710 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14711 SendToProgram(buf, cps);
14714 if(cps->nrOptions >= MAX_OPTIONS) {
14716 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14717 DisplayError(buf, 0);
14721 /* End of additions by HGM */
14723 /* unknown feature: complain and skip */
14725 while (*q && *q != '=') q++;
14726 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14727 SendToProgram(buf, cps);
14733 while (*p && *p != '\"') p++;
14734 if (*p == '\"') p++;
14736 while (*p && *p != ' ') p++;
14744 PeriodicUpdatesEvent(newState)
14747 if (newState == appData.periodicUpdates)
14750 appData.periodicUpdates=newState;
14752 /* Display type changes, so update it now */
14753 // DisplayAnalysis();
14755 /* Get the ball rolling again... */
14757 AnalysisPeriodicEvent(1);
14758 StartAnalysisClock();
14763 PonderNextMoveEvent(newState)
14766 if (newState == appData.ponderNextMove) return;
14767 if (gameMode == EditPosition) EditPositionDone(TRUE);
14769 SendToProgram("hard\n", &first);
14770 if (gameMode == TwoMachinesPlay) {
14771 SendToProgram("hard\n", &second);
14774 SendToProgram("easy\n", &first);
14775 thinkOutput[0] = NULLCHAR;
14776 if (gameMode == TwoMachinesPlay) {
14777 SendToProgram("easy\n", &second);
14780 appData.ponderNextMove = newState;
14784 NewSettingEvent(option, feature, command, value)
14786 int option, value, *feature;
14790 if (gameMode == EditPosition) EditPositionDone(TRUE);
14791 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14792 if(feature == NULL || *feature) SendToProgram(buf, &first);
14793 if (gameMode == TwoMachinesPlay) {
14794 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14799 ShowThinkingEvent()
14800 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14802 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14803 int newState = appData.showThinking
14804 // [HGM] thinking: other features now need thinking output as well
14805 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14807 if (oldState == newState) return;
14808 oldState = newState;
14809 if (gameMode == EditPosition) EditPositionDone(TRUE);
14811 SendToProgram("post\n", &first);
14812 if (gameMode == TwoMachinesPlay) {
14813 SendToProgram("post\n", &second);
14816 SendToProgram("nopost\n", &first);
14817 thinkOutput[0] = NULLCHAR;
14818 if (gameMode == TwoMachinesPlay) {
14819 SendToProgram("nopost\n", &second);
14822 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14826 AskQuestionEvent(title, question, replyPrefix, which)
14827 char *title; char *question; char *replyPrefix; char *which;
14829 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14830 if (pr == NoProc) return;
14831 AskQuestion(title, question, replyPrefix, pr);
14835 TypeInEvent(char firstChar)
14837 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14838 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14839 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14840 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14841 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14842 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14843 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14844 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14845 gameMode == Training) PopUpMoveDialog(firstChar);
14849 TypeInDoneEvent(char *move)
14852 int n, fromX, fromY, toX, toY;
14854 ChessMove moveType;
\r
14857 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14858 EditPositionPasteFEN(move);
\r
14861 // [HGM] movenum: allow move number to be typed in any mode
\r
14862 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14863 ToNrEvent(2*n-1);
\r
14867 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14868 gameMode != Training) {
\r
14869 DisplayMoveError(_("Displayed move is not current"));
\r
14871 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14872 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14873 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14874 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14875 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14876 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14878 DisplayMoveError(_("Could not parse move"));
\r
14884 DisplayMove(moveNumber)
14887 char message[MSG_SIZ];
14889 char cpThinkOutput[MSG_SIZ];
14891 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14893 if (moveNumber == forwardMostMove - 1 ||
14894 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14896 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14898 if (strchr(cpThinkOutput, '\n')) {
14899 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14902 *cpThinkOutput = NULLCHAR;
14905 /* [AS] Hide thinking from human user */
14906 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14907 *cpThinkOutput = NULLCHAR;
14908 if( thinkOutput[0] != NULLCHAR ) {
14911 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14912 cpThinkOutput[i] = '.';
14914 cpThinkOutput[i] = NULLCHAR;
14915 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14919 if (moveNumber == forwardMostMove - 1 &&
14920 gameInfo.resultDetails != NULL) {
14921 if (gameInfo.resultDetails[0] == NULLCHAR) {
14922 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14924 snprintf(res, MSG_SIZ, " {%s} %s",
14925 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14931 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14932 DisplayMessage(res, cpThinkOutput);
14934 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14935 WhiteOnMove(moveNumber) ? " " : ".. ",
14936 parseList[moveNumber], res);
14937 DisplayMessage(message, cpThinkOutput);
14942 DisplayComment(moveNumber, text)
14946 char title[MSG_SIZ];
14947 char buf[8000]; // comment can be long!
14950 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14951 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14953 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14954 WhiteOnMove(moveNumber) ? " " : ".. ",
14955 parseList[moveNumber]);
14957 // [HGM] PV info: display PV info together with (or as) comment
14958 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14959 if(text == NULL) text = "";
14960 score = pvInfoList[moveNumber].score;
14961 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14962 depth, (pvInfoList[moveNumber].time+50)/100, text);
14965 if (text != NULL && (appData.autoDisplayComment || commentUp))
14966 CommentPopUp(title, text);
14969 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14970 * might be busy thinking or pondering. It can be omitted if your
14971 * gnuchess is configured to stop thinking immediately on any user
14972 * input. However, that gnuchess feature depends on the FIONREAD
14973 * ioctl, which does not work properly on some flavors of Unix.
14977 ChessProgramState *cps;
14980 if (!cps->useSigint) return;
14981 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14982 switch (gameMode) {
14983 case MachinePlaysWhite:
14984 case MachinePlaysBlack:
14985 case TwoMachinesPlay:
14986 case IcsPlayingWhite:
14987 case IcsPlayingBlack:
14990 /* Skip if we know it isn't thinking */
14991 if (!cps->maybeThinking) return;
14992 if (appData.debugMode)
14993 fprintf(debugFP, "Interrupting %s\n", cps->which);
14994 InterruptChildProcess(cps->pr);
14995 cps->maybeThinking = FALSE;
15000 #endif /*ATTENTION*/
15006 if (whiteTimeRemaining <= 0) {
15009 if (appData.icsActive) {
15010 if (appData.autoCallFlag &&
15011 gameMode == IcsPlayingBlack && !blackFlag) {
15012 SendToICS(ics_prefix);
15013 SendToICS("flag\n");
15017 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15019 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15020 if (appData.autoCallFlag) {
15021 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15028 if (blackTimeRemaining <= 0) {
15031 if (appData.icsActive) {
15032 if (appData.autoCallFlag &&
15033 gameMode == IcsPlayingWhite && !whiteFlag) {
15034 SendToICS(ics_prefix);
15035 SendToICS("flag\n");
15039 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15041 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15042 if (appData.autoCallFlag) {
15043 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15056 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15057 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15060 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15062 if ( !WhiteOnMove(forwardMostMove) ) {
15063 /* White made time control */
15064 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15065 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15066 /* [HGM] time odds: correct new time quota for time odds! */
15067 / WhitePlayer()->timeOdds;
15068 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15070 lastBlack -= blackTimeRemaining;
15071 /* Black made time control */
15072 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15073 / WhitePlayer()->other->timeOdds;
15074 lastWhite = whiteTimeRemaining;
15079 DisplayBothClocks()
15081 int wom = gameMode == EditPosition ?
15082 !blackPlaysFirst : WhiteOnMove(currentMove);
15083 DisplayWhiteClock(whiteTimeRemaining, wom);
15084 DisplayBlackClock(blackTimeRemaining, !wom);
15088 /* Timekeeping seems to be a portability nightmare. I think everyone
15089 has ftime(), but I'm really not sure, so I'm including some ifdefs
15090 to use other calls if you don't. Clocks will be less accurate if
15091 you have neither ftime nor gettimeofday.
15094 /* VS 2008 requires the #include outside of the function */
15095 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15096 #include <sys/timeb.h>
15099 /* Get the current time as a TimeMark */
15104 #if HAVE_GETTIMEOFDAY
15106 struct timeval timeVal;
15107 struct timezone timeZone;
15109 gettimeofday(&timeVal, &timeZone);
15110 tm->sec = (long) timeVal.tv_sec;
15111 tm->ms = (int) (timeVal.tv_usec / 1000L);
15113 #else /*!HAVE_GETTIMEOFDAY*/
15116 // include <sys/timeb.h> / moved to just above start of function
15117 struct timeb timeB;
15120 tm->sec = (long) timeB.time;
15121 tm->ms = (int) timeB.millitm;
15123 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15124 tm->sec = (long) time(NULL);
15130 /* Return the difference in milliseconds between two
15131 time marks. We assume the difference will fit in a long!
15134 SubtractTimeMarks(tm2, tm1)
15135 TimeMark *tm2, *tm1;
15137 return 1000L*(tm2->sec - tm1->sec) +
15138 (long) (tm2->ms - tm1->ms);
15143 * Code to manage the game clocks.
15145 * In tournament play, black starts the clock and then white makes a move.
15146 * We give the human user a slight advantage if he is playing white---the
15147 * clocks don't run until he makes his first move, so it takes zero time.
15148 * Also, we don't account for network lag, so we could get out of sync
15149 * with GNU Chess's clock -- but then, referees are always right.
15152 static TimeMark tickStartTM;
15153 static long intendedTickLength;
15156 NextTickLength(timeRemaining)
15157 long timeRemaining;
15159 long nominalTickLength, nextTickLength;
15161 if (timeRemaining > 0L && timeRemaining <= 10000L)
15162 nominalTickLength = 100L;
15164 nominalTickLength = 1000L;
15165 nextTickLength = timeRemaining % nominalTickLength;
15166 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15168 return nextTickLength;
15171 /* Adjust clock one minute up or down */
15173 AdjustClock(Boolean which, int dir)
15175 if(which) blackTimeRemaining += 60000*dir;
15176 else whiteTimeRemaining += 60000*dir;
15177 DisplayBothClocks();
15180 /* Stop clocks and reset to a fresh time control */
15184 (void) StopClockTimer();
15185 if (appData.icsActive) {
15186 whiteTimeRemaining = blackTimeRemaining = 0;
15187 } else if (searchTime) {
15188 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15189 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15190 } else { /* [HGM] correct new time quote for time odds */
15191 whiteTC = blackTC = fullTimeControlString;
15192 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15193 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15195 if (whiteFlag || blackFlag) {
15197 whiteFlag = blackFlag = FALSE;
15199 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15200 DisplayBothClocks();
15203 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15205 /* Decrement running clock by amount of time that has passed */
15209 long timeRemaining;
15210 long lastTickLength, fudge;
15213 if (!appData.clockMode) return;
15214 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15218 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15220 /* Fudge if we woke up a little too soon */
15221 fudge = intendedTickLength - lastTickLength;
15222 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15224 if (WhiteOnMove(forwardMostMove)) {
15225 if(whiteNPS >= 0) lastTickLength = 0;
15226 timeRemaining = whiteTimeRemaining -= lastTickLength;
15227 if(timeRemaining < 0 && !appData.icsActive) {
15228 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15229 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15230 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15231 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15234 DisplayWhiteClock(whiteTimeRemaining - fudge,
15235 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15237 if(blackNPS >= 0) lastTickLength = 0;
15238 timeRemaining = blackTimeRemaining -= lastTickLength;
15239 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15240 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15242 blackStartMove = forwardMostMove;
15243 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15246 DisplayBlackClock(blackTimeRemaining - fudge,
15247 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15249 if (CheckFlags()) return;
15252 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15253 StartClockTimer(intendedTickLength);
15255 /* if the time remaining has fallen below the alarm threshold, sound the
15256 * alarm. if the alarm has sounded and (due to a takeback or time control
15257 * with increment) the time remaining has increased to a level above the
15258 * threshold, reset the alarm so it can sound again.
15261 if (appData.icsActive && appData.icsAlarm) {
15263 /* make sure we are dealing with the user's clock */
15264 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15265 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15268 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15269 alarmSounded = FALSE;
15270 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15272 alarmSounded = TRUE;
15278 /* A player has just moved, so stop the previously running
15279 clock and (if in clock mode) start the other one.
15280 We redisplay both clocks in case we're in ICS mode, because
15281 ICS gives us an update to both clocks after every move.
15282 Note that this routine is called *after* forwardMostMove
15283 is updated, so the last fractional tick must be subtracted
15284 from the color that is *not* on move now.
15287 SwitchClocks(int newMoveNr)
15289 long lastTickLength;
15291 int flagged = FALSE;
15295 if (StopClockTimer() && appData.clockMode) {
15296 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15297 if (!WhiteOnMove(forwardMostMove)) {
15298 if(blackNPS >= 0) lastTickLength = 0;
15299 blackTimeRemaining -= lastTickLength;
15300 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15301 // if(pvInfoList[forwardMostMove].time == -1)
15302 pvInfoList[forwardMostMove].time = // use GUI time
15303 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15305 if(whiteNPS >= 0) lastTickLength = 0;
15306 whiteTimeRemaining -= lastTickLength;
15307 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15308 // if(pvInfoList[forwardMostMove].time == -1)
15309 pvInfoList[forwardMostMove].time =
15310 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15312 flagged = CheckFlags();
15314 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15315 CheckTimeControl();
15317 if (flagged || !appData.clockMode) return;
15319 switch (gameMode) {
15320 case MachinePlaysBlack:
15321 case MachinePlaysWhite:
15322 case BeginningOfGame:
15323 if (pausing) return;
15327 case PlayFromGameFile:
15335 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15336 if(WhiteOnMove(forwardMostMove))
15337 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15338 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15342 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15343 whiteTimeRemaining : blackTimeRemaining);
15344 StartClockTimer(intendedTickLength);
15348 /* Stop both clocks */
15352 long lastTickLength;
15355 if (!StopClockTimer()) return;
15356 if (!appData.clockMode) return;
15360 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15361 if (WhiteOnMove(forwardMostMove)) {
15362 if(whiteNPS >= 0) lastTickLength = 0;
15363 whiteTimeRemaining -= lastTickLength;
15364 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15366 if(blackNPS >= 0) lastTickLength = 0;
15367 blackTimeRemaining -= lastTickLength;
15368 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15373 /* Start clock of player on move. Time may have been reset, so
15374 if clock is already running, stop and restart it. */
15378 (void) StopClockTimer(); /* in case it was running already */
15379 DisplayBothClocks();
15380 if (CheckFlags()) return;
15382 if (!appData.clockMode) return;
15383 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15385 GetTimeMark(&tickStartTM);
15386 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15387 whiteTimeRemaining : blackTimeRemaining);
15389 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15390 whiteNPS = blackNPS = -1;
15391 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15392 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15393 whiteNPS = first.nps;
15394 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15395 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15396 blackNPS = first.nps;
15397 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15398 whiteNPS = second.nps;
15399 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15400 blackNPS = second.nps;
15401 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15403 StartClockTimer(intendedTickLength);
15410 long second, minute, hour, day;
15412 static char buf[32];
15414 if (ms > 0 && ms <= 9900) {
15415 /* convert milliseconds to tenths, rounding up */
15416 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15418 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15422 /* convert milliseconds to seconds, rounding up */
15423 /* use floating point to avoid strangeness of integer division
15424 with negative dividends on many machines */
15425 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15432 day = second / (60 * 60 * 24);
15433 second = second % (60 * 60 * 24);
15434 hour = second / (60 * 60);
15435 second = second % (60 * 60);
15436 minute = second / 60;
15437 second = second % 60;
15440 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15441 sign, day, hour, minute, second);
15443 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15445 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15452 * This is necessary because some C libraries aren't ANSI C compliant yet.
15455 StrStr(string, match)
15456 char *string, *match;
15460 length = strlen(match);
15462 for (i = strlen(string) - length; i >= 0; i--, string++)
15463 if (!strncmp(match, string, length))
15470 StrCaseStr(string, match)
15471 char *string, *match;
15475 length = strlen(match);
15477 for (i = strlen(string) - length; i >= 0; i--, string++) {
15478 for (j = 0; j < length; j++) {
15479 if (ToLower(match[j]) != ToLower(string[j]))
15482 if (j == length) return string;
15496 c1 = ToLower(*s1++);
15497 c2 = ToLower(*s2++);
15498 if (c1 > c2) return 1;
15499 if (c1 < c2) return -1;
15500 if (c1 == NULLCHAR) return 0;
15509 return isupper(c) ? tolower(c) : c;
15517 return islower(c) ? toupper(c) : c;
15519 #endif /* !_amigados */
15527 if ((ret = (char *) malloc(strlen(s) + 1)))
15529 safeStrCpy(ret, s, strlen(s)+1);
15535 StrSavePtr(s, savePtr)
15536 char *s, **savePtr;
15541 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15542 safeStrCpy(*savePtr, s, strlen(s)+1);
15554 clock = time((time_t *)NULL);
15555 tm = localtime(&clock);
15556 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15557 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15558 return StrSave(buf);
15563 PositionToFEN(move, overrideCastling)
15565 char *overrideCastling;
15567 int i, j, fromX, fromY, toX, toY;
15574 whiteToPlay = (gameMode == EditPosition) ?
15575 !blackPlaysFirst : (move % 2 == 0);
15578 /* Piece placement data */
15579 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15581 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15582 if (boards[move][i][j] == EmptySquare) {
15584 } else { ChessSquare piece = boards[move][i][j];
15585 if (emptycount > 0) {
15586 if(emptycount<10) /* [HGM] can be >= 10 */
15587 *p++ = '0' + emptycount;
15588 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15591 if(PieceToChar(piece) == '+') {
15592 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15594 piece = (ChessSquare)(DEMOTED piece);
15596 *p++ = PieceToChar(piece);
15598 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15599 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15604 if (emptycount > 0) {
15605 if(emptycount<10) /* [HGM] can be >= 10 */
15606 *p++ = '0' + emptycount;
15607 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15614 /* [HGM] print Crazyhouse or Shogi holdings */
15615 if( gameInfo.holdingsWidth ) {
15616 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15618 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15619 piece = boards[move][i][BOARD_WIDTH-1];
15620 if( piece != EmptySquare )
15621 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15622 *p++ = PieceToChar(piece);
15624 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15625 piece = boards[move][BOARD_HEIGHT-i-1][0];
15626 if( piece != EmptySquare )
15627 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15628 *p++ = PieceToChar(piece);
15631 if( q == p ) *p++ = '-';
15637 *p++ = whiteToPlay ? 'w' : 'b';
15640 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15641 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15643 if(nrCastlingRights) {
15645 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15646 /* [HGM] write directly from rights */
15647 if(boards[move][CASTLING][2] != NoRights &&
15648 boards[move][CASTLING][0] != NoRights )
15649 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15650 if(boards[move][CASTLING][2] != NoRights &&
15651 boards[move][CASTLING][1] != NoRights )
15652 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15653 if(boards[move][CASTLING][5] != NoRights &&
15654 boards[move][CASTLING][3] != NoRights )
15655 *p++ = boards[move][CASTLING][3] + AAA;
15656 if(boards[move][CASTLING][5] != NoRights &&
15657 boards[move][CASTLING][4] != NoRights )
15658 *p++ = boards[move][CASTLING][4] + AAA;
15661 /* [HGM] write true castling rights */
15662 if( nrCastlingRights == 6 ) {
15663 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15664 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15665 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15666 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15667 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15668 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15669 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15670 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15673 if (q == p) *p++ = '-'; /* No castling rights */
15677 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15678 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15679 /* En passant target square */
15680 if (move > backwardMostMove) {
15681 fromX = moveList[move - 1][0] - AAA;
15682 fromY = moveList[move - 1][1] - ONE;
15683 toX = moveList[move - 1][2] - AAA;
15684 toY = moveList[move - 1][3] - ONE;
15685 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15686 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15687 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15689 /* 2-square pawn move just happened */
15691 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15695 } else if(move == backwardMostMove) {
15696 // [HGM] perhaps we should always do it like this, and forget the above?
15697 if((signed char)boards[move][EP_STATUS] >= 0) {
15698 *p++ = boards[move][EP_STATUS] + AAA;
15699 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15710 /* [HGM] find reversible plies */
15711 { int i = 0, j=move;
15713 if (appData.debugMode) { int k;
15714 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15715 for(k=backwardMostMove; k<=forwardMostMove; k++)
15716 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15720 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15721 if( j == backwardMostMove ) i += initialRulePlies;
15722 sprintf(p, "%d ", i);
15723 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15725 /* Fullmove number */
15726 sprintf(p, "%d", (move / 2) + 1);
15728 return StrSave(buf);
15732 ParseFEN(board, blackPlaysFirst, fen)
15734 int *blackPlaysFirst;
15744 /* [HGM] by default clear Crazyhouse holdings, if present */
15745 if(gameInfo.holdingsWidth) {
15746 for(i=0; i<BOARD_HEIGHT; i++) {
15747 board[i][0] = EmptySquare; /* black holdings */
15748 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15749 board[i][1] = (ChessSquare) 0; /* black counts */
15750 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15754 /* Piece placement data */
15755 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15758 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15759 if (*p == '/') p++;
15760 emptycount = gameInfo.boardWidth - j;
15761 while (emptycount--)
15762 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15764 #if(BOARD_FILES >= 10)
15765 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15766 p++; emptycount=10;
15767 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15768 while (emptycount--)
15769 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15771 } else if (isdigit(*p)) {
15772 emptycount = *p++ - '0';
15773 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15774 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15775 while (emptycount--)
15776 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15777 } else if (*p == '+' || isalpha(*p)) {
15778 if (j >= gameInfo.boardWidth) return FALSE;
15780 piece = CharToPiece(*++p);
15781 if(piece == EmptySquare) return FALSE; /* unknown piece */
15782 piece = (ChessSquare) (PROMOTED piece ); p++;
15783 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15784 } else piece = CharToPiece(*p++);
15786 if(piece==EmptySquare) return FALSE; /* unknown piece */
15787 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15788 piece = (ChessSquare) (PROMOTED piece);
15789 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15792 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15798 while (*p == '/' || *p == ' ') p++;
15800 /* [HGM] look for Crazyhouse holdings here */
15801 while(*p==' ') p++;
15802 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15804 if(*p == '-' ) p++; /* empty holdings */ else {
15805 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15806 /* if we would allow FEN reading to set board size, we would */
15807 /* have to add holdings and shift the board read so far here */
15808 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15810 if((int) piece >= (int) BlackPawn ) {
15811 i = (int)piece - (int)BlackPawn;
15812 i = PieceToNumber((ChessSquare)i);
15813 if( i >= gameInfo.holdingsSize ) return FALSE;
15814 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15815 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15817 i = (int)piece - (int)WhitePawn;
15818 i = PieceToNumber((ChessSquare)i);
15819 if( i >= gameInfo.holdingsSize ) return FALSE;
15820 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15821 board[i][BOARD_WIDTH-2]++; /* black holdings */
15828 while(*p == ' ') p++;
15832 if(appData.colorNickNames) {
15833 if( c == appData.colorNickNames[0] ) c = 'w'; else
15834 if( c == appData.colorNickNames[1] ) c = 'b';
15838 *blackPlaysFirst = FALSE;
15841 *blackPlaysFirst = TRUE;
15847 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15848 /* return the extra info in global variiables */
15850 /* set defaults in case FEN is incomplete */
15851 board[EP_STATUS] = EP_UNKNOWN;
15852 for(i=0; i<nrCastlingRights; i++ ) {
15853 board[CASTLING][i] =
15854 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15855 } /* assume possible unless obviously impossible */
15856 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15857 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15858 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15859 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15860 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15861 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15862 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15863 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15866 while(*p==' ') p++;
15867 if(nrCastlingRights) {
15868 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15869 /* castling indicator present, so default becomes no castlings */
15870 for(i=0; i<nrCastlingRights; i++ ) {
15871 board[CASTLING][i] = NoRights;
15874 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15875 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15876 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15877 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15878 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15880 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15881 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15882 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15884 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15885 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15886 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15887 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15888 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15889 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15892 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15893 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15894 board[CASTLING][2] = whiteKingFile;
15897 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15898 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15899 board[CASTLING][2] = whiteKingFile;
15902 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15903 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15904 board[CASTLING][5] = blackKingFile;
15907 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15908 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15909 board[CASTLING][5] = blackKingFile;
15912 default: /* FRC castlings */
15913 if(c >= 'a') { /* black rights */
15914 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15915 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15916 if(i == BOARD_RGHT) break;
15917 board[CASTLING][5] = i;
15919 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15920 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15922 board[CASTLING][3] = c;
15924 board[CASTLING][4] = c;
15925 } else { /* white rights */
15926 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15927 if(board[0][i] == WhiteKing) break;
15928 if(i == BOARD_RGHT) break;
15929 board[CASTLING][2] = i;
15930 c -= AAA - 'a' + 'A';
15931 if(board[0][c] >= WhiteKing) break;
15933 board[CASTLING][0] = c;
15935 board[CASTLING][1] = c;
15939 for(i=0; i<nrCastlingRights; i++)
15940 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15941 if (appData.debugMode) {
15942 fprintf(debugFP, "FEN castling rights:");
15943 for(i=0; i<nrCastlingRights; i++)
15944 fprintf(debugFP, " %d", board[CASTLING][i]);
15945 fprintf(debugFP, "\n");
15948 while(*p==' ') p++;
15951 /* read e.p. field in games that know e.p. capture */
15952 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15953 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15955 p++; board[EP_STATUS] = EP_NONE;
15957 char c = *p++ - AAA;
15959 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15960 if(*p >= '0' && *p <='9') p++;
15961 board[EP_STATUS] = c;
15966 if(sscanf(p, "%d", &i) == 1) {
15967 FENrulePlies = i; /* 50-move ply counter */
15968 /* (The move number is still ignored) */
15975 EditPositionPasteFEN(char *fen)
15978 Board initial_position;
15980 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15981 DisplayError(_("Bad FEN position in clipboard"), 0);
15984 int savedBlackPlaysFirst = blackPlaysFirst;
15985 EditPositionEvent();
15986 blackPlaysFirst = savedBlackPlaysFirst;
15987 CopyBoard(boards[0], initial_position);
15988 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15989 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15990 DisplayBothClocks();
15991 DrawPosition(FALSE, boards[currentMove]);
15996 static char cseq[12] = "\\ ";
15998 Boolean set_cont_sequence(char *new_seq)
16003 // handle bad attempts to set the sequence
16005 return 0; // acceptable error - no debug
16007 len = strlen(new_seq);
16008 ret = (len > 0) && (len < sizeof(cseq));
16010 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16011 else if (appData.debugMode)
16012 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16017 reformat a source message so words don't cross the width boundary. internal
16018 newlines are not removed. returns the wrapped size (no null character unless
16019 included in source message). If dest is NULL, only calculate the size required
16020 for the dest buffer. lp argument indicats line position upon entry, and it's
16021 passed back upon exit.
16023 int wrap(char *dest, char *src, int count, int width, int *lp)
16025 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16027 cseq_len = strlen(cseq);
16028 old_line = line = *lp;
16029 ansi = len = clen = 0;
16031 for (i=0; i < count; i++)
16033 if (src[i] == '\033')
16036 // if we hit the width, back up
16037 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16039 // store i & len in case the word is too long
16040 old_i = i, old_len = len;
16042 // find the end of the last word
16043 while (i && src[i] != ' ' && src[i] != '\n')
16049 // word too long? restore i & len before splitting it
16050 if ((old_i-i+clen) >= width)
16057 if (i && src[i-1] == ' ')
16060 if (src[i] != ' ' && src[i] != '\n')
16067 // now append the newline and continuation sequence
16072 strncpy(dest+len, cseq, cseq_len);
16080 dest[len] = src[i];
16084 if (src[i] == '\n')
16089 if (dest && appData.debugMode)
16091 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16092 count, width, line, len, *lp);
16093 show_bytes(debugFP, src, count);
16094 fprintf(debugFP, "\ndest: ");
16095 show_bytes(debugFP, dest, len);
16096 fprintf(debugFP, "\n");
16098 *lp = dest ? line : old_line;
16103 // [HGM] vari: routines for shelving variations
16106 PushInner(int firstMove, int lastMove)
16108 int i, j, nrMoves = lastMove - firstMove;
16110 // push current tail of game on stack
16111 savedResult[storedGames] = gameInfo.result;
16112 savedDetails[storedGames] = gameInfo.resultDetails;
16113 gameInfo.resultDetails = NULL;
16114 savedFirst[storedGames] = firstMove;
16115 savedLast [storedGames] = lastMove;
16116 savedFramePtr[storedGames] = framePtr;
16117 framePtr -= nrMoves; // reserve space for the boards
16118 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16119 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16120 for(j=0; j<MOVE_LEN; j++)
16121 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16122 for(j=0; j<2*MOVE_LEN; j++)
16123 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16124 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16125 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16126 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16127 pvInfoList[firstMove+i-1].depth = 0;
16128 commentList[framePtr+i] = commentList[firstMove+i];
16129 commentList[firstMove+i] = NULL;
16133 forwardMostMove = firstMove; // truncate game so we can start variation
16137 PushTail(int firstMove, int lastMove)
16139 if(appData.icsActive) { // only in local mode
16140 forwardMostMove = currentMove; // mimic old ICS behavior
16143 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16145 PushInner(firstMove, lastMove);
16146 if(storedGames == 1) GreyRevert(FALSE);
16150 PopInner(Boolean annotate)
16153 char buf[8000], moveBuf[20];
16156 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16157 nrMoves = savedLast[storedGames] - currentMove;
16160 if(!WhiteOnMove(currentMove))
16161 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16162 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16163 for(i=currentMove; i<forwardMostMove; i++) {
16165 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16166 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16167 strcat(buf, moveBuf);
16168 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16169 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16173 for(i=1; i<=nrMoves; i++) { // copy last variation back
16174 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16175 for(j=0; j<MOVE_LEN; j++)
16176 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16177 for(j=0; j<2*MOVE_LEN; j++)
16178 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16179 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16180 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16181 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16182 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16183 commentList[currentMove+i] = commentList[framePtr+i];
16184 commentList[framePtr+i] = NULL;
16186 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16187 framePtr = savedFramePtr[storedGames];
16188 gameInfo.result = savedResult[storedGames];
16189 if(gameInfo.resultDetails != NULL) {
16190 free(gameInfo.resultDetails);
16192 gameInfo.resultDetails = savedDetails[storedGames];
16193 forwardMostMove = currentMove + nrMoves;
16197 PopTail(Boolean annotate)
16199 if(appData.icsActive) return FALSE; // only in local mode
16200 if(!storedGames) return FALSE; // sanity
16201 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16203 PopInner(annotate);
16205 if(storedGames == 0) GreyRevert(TRUE);
16211 { // remove all shelved variations
16213 for(i=0; i<storedGames; i++) {
16214 if(savedDetails[i])
16215 free(savedDetails[i]);
16216 savedDetails[i] = NULL;
16218 for(i=framePtr; i<MAX_MOVES; i++) {
16219 if(commentList[i]) free(commentList[i]);
16220 commentList[i] = NULL;
16222 framePtr = MAX_MOVES-1;
16227 LoadVariation(int index, char *text)
16228 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16229 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16230 int level = 0, move;
16232 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16233 // first find outermost bracketing variation
16234 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16235 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16236 if(*p == '{') wait = '}'; else
16237 if(*p == '[') wait = ']'; else
16238 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16239 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16241 if(*p == wait) wait = NULLCHAR; // closing ]} found
16244 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16245 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16246 end[1] = NULLCHAR; // clip off comment beyond variation
16247 ToNrEvent(currentMove-1);
16248 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16249 // kludge: use ParsePV() to append variation to game
16250 move = currentMove;
16251 ParsePV(start, TRUE);
16252 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16253 ClearPremoveHighlights();
16255 ToNrEvent(currentMove+1);