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, Boolean atEnd)
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 = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
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 MultiPV(ChessProgramState *cps)
5320 { // check if engine supports MultiPV, and if so, return the nmber of the option that sets it
5322 for(i=0; i<cps->nrOptions; i++)
5323 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5329 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5331 int startPV, multi, lineStart, origIndex = index;
5332 char *p, buf2[MSG_SIZ];
5334 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5335 lastX = x; lastY = y;
5336 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5337 lineStart = startPV = index;
5338 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5339 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5341 do{ while(buf[index] && buf[index] != '\n') index++;
5342 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5344 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5345 int n = first.option[multi].value;
5346 if(origIndex < 10) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5347 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5348 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5349 first.option[multi].value = n;
5353 ParsePV(buf+startPV, FALSE, !shiftKey);
5354 *start = startPV; *end = index-1;
5359 LoadPV(int x, int y)
5360 { // called on right mouse click to load PV
5361 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5362 lastX = x; lastY = y;
5363 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5370 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5371 if(endPV < 0) return;
5373 if(shiftKey && gameMode == AnalyzeMode) {
5374 if(pushed) storedGames--; // abandon shelved tail of original game
5376 forwardMostMove = currentMove;
5377 currentMove = oldFMM;
5378 ToNrEvent(forwardMostMove);
5380 currentMove = forwardMostMove;
5381 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5382 ClearPremoveHighlights();
5383 DrawPosition(TRUE, boards[currentMove]);
5387 MovePV(int x, int y, int h)
5388 { // step through PV based on mouse coordinates (called on mouse move)
5389 int margin = h>>3, step = 0;
5391 // we must somehow check if right button is still down (might be released off board!)
5392 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5393 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5394 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5396 lastX = x; lastY = y;
5398 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5399 if(endPV < 0) return;
5400 if(y < margin) step = 1; else
5401 if(y > h - margin) step = -1;
5402 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5403 currentMove += step;
5404 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5405 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5406 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5407 DrawPosition(FALSE, boards[currentMove]);
5411 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5412 // All positions will have equal probability, but the current method will not provide a unique
5413 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5419 int piecesLeft[(int)BlackPawn];
5420 int seed, nrOfShuffles;
5422 void GetPositionNumber()
5423 { // sets global variable seed
5426 seed = appData.defaultFrcPosition;
5427 if(seed < 0) { // randomize based on time for negative FRC position numbers
5428 for(i=0; i<50; i++) seed += random();
5429 seed = random() ^ random() >> 8 ^ random() << 8;
5430 if(seed<0) seed = -seed;
5434 int put(Board board, int pieceType, int rank, int n, int shade)
5435 // put the piece on the (n-1)-th empty squares of the given shade
5439 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5440 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5441 board[rank][i] = (ChessSquare) pieceType;
5442 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5444 piecesLeft[pieceType]--;
5452 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5453 // calculate where the next piece goes, (any empty square), and put it there
5457 i = seed % squaresLeft[shade];
5458 nrOfShuffles *= squaresLeft[shade];
5459 seed /= squaresLeft[shade];
5460 put(board, pieceType, rank, i, shade);
5463 void AddTwoPieces(Board board, int pieceType, int rank)
5464 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5466 int i, n=squaresLeft[ANY], j=n-1, k;
5468 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5469 i = seed % k; // pick one
5472 while(i >= j) i -= j--;
5473 j = n - 1 - j; i += j;
5474 put(board, pieceType, rank, j, ANY);
5475 put(board, pieceType, rank, i, ANY);
5478 void SetUpShuffle(Board board, int number)
5482 GetPositionNumber(); nrOfShuffles = 1;
5484 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5485 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5486 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5488 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5490 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5491 p = (int) board[0][i];
5492 if(p < (int) BlackPawn) piecesLeft[p] ++;
5493 board[0][i] = EmptySquare;
5496 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5497 // shuffles restricted to allow normal castling put KRR first
5498 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5499 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5500 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5501 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5502 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5503 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5504 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5505 put(board, WhiteRook, 0, 0, ANY);
5506 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5509 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5510 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5511 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5512 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5513 while(piecesLeft[p] >= 2) {
5514 AddOnePiece(board, p, 0, LITE);
5515 AddOnePiece(board, p, 0, DARK);
5517 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5520 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5521 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5522 // but we leave King and Rooks for last, to possibly obey FRC restriction
5523 if(p == (int)WhiteRook) continue;
5524 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5525 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5528 // now everything is placed, except perhaps King (Unicorn) and Rooks
5530 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5531 // Last King gets castling rights
5532 while(piecesLeft[(int)WhiteUnicorn]) {
5533 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5534 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5537 while(piecesLeft[(int)WhiteKing]) {
5538 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5539 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5544 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5545 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5548 // Only Rooks can be left; simply place them all
5549 while(piecesLeft[(int)WhiteRook]) {
5550 i = put(board, WhiteRook, 0, 0, ANY);
5551 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5554 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5556 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5559 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5560 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5563 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5566 int SetCharTable( char *table, const char * map )
5567 /* [HGM] moved here from winboard.c because of its general usefulness */
5568 /* Basically a safe strcpy that uses the last character as King */
5570 int result = FALSE; int NrPieces;
5572 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5573 && NrPieces >= 12 && !(NrPieces&1)) {
5574 int i; /* [HGM] Accept even length from 12 to 34 */
5576 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5577 for( i=0; i<NrPieces/2-1; i++ ) {
5579 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5581 table[(int) WhiteKing] = map[NrPieces/2-1];
5582 table[(int) BlackKing] = map[NrPieces-1];
5590 void Prelude(Board board)
5591 { // [HGM] superchess: random selection of exo-pieces
5592 int i, j, k; ChessSquare p;
5593 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5595 GetPositionNumber(); // use FRC position number
5597 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5598 SetCharTable(pieceToChar, appData.pieceToCharTable);
5599 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5600 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5603 j = seed%4; seed /= 4;
5604 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5605 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5606 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5607 j = seed%3 + (seed%3 >= j); seed /= 3;
5608 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5609 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5610 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5611 j = seed%3; seed /= 3;
5612 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5613 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5614 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5615 j = seed%2 + (seed%2 >= j); seed /= 2;
5616 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5617 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5618 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5619 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5620 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5621 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5622 put(board, exoPieces[0], 0, 0, ANY);
5623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5627 InitPosition(redraw)
5630 ChessSquare (* pieces)[BOARD_FILES];
5631 int i, j, pawnRow, overrule,
5632 oldx = gameInfo.boardWidth,
5633 oldy = gameInfo.boardHeight,
5634 oldh = gameInfo.holdingsWidth;
5637 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5639 /* [AS] Initialize pv info list [HGM] and game status */
5641 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5642 pvInfoList[i].depth = 0;
5643 boards[i][EP_STATUS] = EP_NONE;
5644 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5647 initialRulePlies = 0; /* 50-move counter start */
5649 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5650 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5654 /* [HGM] logic here is completely changed. In stead of full positions */
5655 /* the initialized data only consist of the two backranks. The switch */
5656 /* selects which one we will use, which is than copied to the Board */
5657 /* initialPosition, which for the rest is initialized by Pawns and */
5658 /* empty squares. This initial position is then copied to boards[0], */
5659 /* possibly after shuffling, so that it remains available. */
5661 gameInfo.holdingsWidth = 0; /* default board sizes */
5662 gameInfo.boardWidth = 8;
5663 gameInfo.boardHeight = 8;
5664 gameInfo.holdingsSize = 0;
5665 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5666 for(i=0; i<BOARD_FILES-2; i++)
5667 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5668 initialPosition[EP_STATUS] = EP_NONE;
5669 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5670 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5671 SetCharTable(pieceNickName, appData.pieceNickNames);
5672 else SetCharTable(pieceNickName, "............");
5675 switch (gameInfo.variant) {
5676 case VariantFischeRandom:
5677 shuffleOpenings = TRUE;
5680 case VariantShatranj:
5681 pieces = ShatranjArray;
5682 nrCastlingRights = 0;
5683 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5686 pieces = makrukArray;
5687 nrCastlingRights = 0;
5688 startedFromSetupPosition = TRUE;
5689 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5691 case VariantTwoKings:
5692 pieces = twoKingsArray;
5694 case VariantCapaRandom:
5695 shuffleOpenings = TRUE;
5696 case VariantCapablanca:
5697 pieces = CapablancaArray;
5698 gameInfo.boardWidth = 10;
5699 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5702 pieces = GothicArray;
5703 gameInfo.boardWidth = 10;
5704 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5707 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5708 gameInfo.holdingsSize = 7;
5711 pieces = JanusArray;
5712 gameInfo.boardWidth = 10;
5713 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5714 nrCastlingRights = 6;
5715 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5716 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5717 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5718 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5719 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5720 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5723 pieces = FalconArray;
5724 gameInfo.boardWidth = 10;
5725 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5727 case VariantXiangqi:
5728 pieces = XiangqiArray;
5729 gameInfo.boardWidth = 9;
5730 gameInfo.boardHeight = 10;
5731 nrCastlingRights = 0;
5732 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5735 pieces = ShogiArray;
5736 gameInfo.boardWidth = 9;
5737 gameInfo.boardHeight = 9;
5738 gameInfo.holdingsSize = 7;
5739 nrCastlingRights = 0;
5740 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5742 case VariantCourier:
5743 pieces = CourierArray;
5744 gameInfo.boardWidth = 12;
5745 nrCastlingRights = 0;
5746 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5748 case VariantKnightmate:
5749 pieces = KnightmateArray;
5750 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5752 case VariantSpartan:
5753 pieces = SpartanArray;
5754 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5757 pieces = fairyArray;
5758 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5761 pieces = GreatArray;
5762 gameInfo.boardWidth = 10;
5763 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5764 gameInfo.holdingsSize = 8;
5768 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5769 gameInfo.holdingsSize = 8;
5770 startedFromSetupPosition = TRUE;
5772 case VariantCrazyhouse:
5773 case VariantBughouse:
5775 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5776 gameInfo.holdingsSize = 5;
5778 case VariantWildCastle:
5780 /* !!?shuffle with kings guaranteed to be on d or e file */
5781 shuffleOpenings = 1;
5783 case VariantNoCastle:
5785 nrCastlingRights = 0;
5786 /* !!?unconstrained back-rank shuffle */
5787 shuffleOpenings = 1;
5792 if(appData.NrFiles >= 0) {
5793 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5794 gameInfo.boardWidth = appData.NrFiles;
5796 if(appData.NrRanks >= 0) {
5797 gameInfo.boardHeight = appData.NrRanks;
5799 if(appData.holdingsSize >= 0) {
5800 i = appData.holdingsSize;
5801 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5802 gameInfo.holdingsSize = i;
5804 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5805 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5806 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5808 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5809 if(pawnRow < 1) pawnRow = 1;
5810 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5812 /* User pieceToChar list overrules defaults */
5813 if(appData.pieceToCharTable != NULL)
5814 SetCharTable(pieceToChar, appData.pieceToCharTable);
5816 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5818 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5819 s = (ChessSquare) 0; /* account holding counts in guard band */
5820 for( i=0; i<BOARD_HEIGHT; i++ )
5821 initialPosition[i][j] = s;
5823 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5824 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5825 initialPosition[pawnRow][j] = WhitePawn;
5826 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5827 if(gameInfo.variant == VariantXiangqi) {
5829 initialPosition[pawnRow][j] =
5830 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5831 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5832 initialPosition[2][j] = WhiteCannon;
5833 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5837 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5839 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5842 initialPosition[1][j] = WhiteBishop;
5843 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5845 initialPosition[1][j] = WhiteRook;
5846 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5849 if( nrCastlingRights == -1) {
5850 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5851 /* This sets default castling rights from none to normal corners */
5852 /* Variants with other castling rights must set them themselves above */
5853 nrCastlingRights = 6;
5855 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5856 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5857 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5858 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5859 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5860 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5863 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5864 if(gameInfo.variant == VariantGreat) { // promotion commoners
5865 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5866 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5867 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5868 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5870 if( gameInfo.variant == VariantSChess ) {
5871 initialPosition[1][0] = BlackMarshall;
5872 initialPosition[2][0] = BlackAngel;
5873 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5874 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5875 initialPosition[1][1] = initialPosition[2][1] =
5876 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5878 if (appData.debugMode) {
5879 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5881 if(shuffleOpenings) {
5882 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5883 startedFromSetupPosition = TRUE;
5885 if(startedFromPositionFile) {
5886 /* [HGM] loadPos: use PositionFile for every new game */
5887 CopyBoard(initialPosition, filePosition);
5888 for(i=0; i<nrCastlingRights; i++)
5889 initialRights[i] = filePosition[CASTLING][i];
5890 startedFromSetupPosition = TRUE;
5893 CopyBoard(boards[0], initialPosition);
5895 if(oldx != gameInfo.boardWidth ||
5896 oldy != gameInfo.boardHeight ||
5897 oldv != gameInfo.variant ||
5898 oldh != gameInfo.holdingsWidth
5900 InitDrawingSizes(-2 ,0);
5902 oldv = gameInfo.variant;
5904 DrawPosition(TRUE, boards[currentMove]);
5908 SendBoard(cps, moveNum)
5909 ChessProgramState *cps;
5912 char message[MSG_SIZ];
5914 if (cps->useSetboard) {
5915 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5916 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5917 SendToProgram(message, cps);
5923 /* Kludge to set black to move, avoiding the troublesome and now
5924 * deprecated "black" command.
5926 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5927 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5929 SendToProgram("edit\n", cps);
5930 SendToProgram("#\n", cps);
5931 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5932 bp = &boards[moveNum][i][BOARD_LEFT];
5933 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5934 if ((int) *bp < (int) BlackPawn) {
5935 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5937 if(message[0] == '+' || message[0] == '~') {
5938 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5939 PieceToChar((ChessSquare)(DEMOTED *bp)),
5942 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5943 message[1] = BOARD_RGHT - 1 - j + '1';
5944 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5946 SendToProgram(message, cps);
5951 SendToProgram("c\n", cps);
5952 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5953 bp = &boards[moveNum][i][BOARD_LEFT];
5954 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5955 if (((int) *bp != (int) EmptySquare)
5956 && ((int) *bp >= (int) BlackPawn)) {
5957 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5959 if(message[0] == '+' || message[0] == '~') {
5960 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5961 PieceToChar((ChessSquare)(DEMOTED *bp)),
5964 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5965 message[1] = BOARD_RGHT - 1 - j + '1';
5966 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5968 SendToProgram(message, cps);
5973 SendToProgram(".\n", cps);
5975 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5979 DefaultPromoChoice(int white)
5982 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5983 result = WhiteFerz; // no choice
5984 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5985 result= WhiteKing; // in Suicide Q is the last thing we want
5986 else if(gameInfo.variant == VariantSpartan)
5987 result = white ? WhiteQueen : WhiteAngel;
5988 else result = WhiteQueen;
5989 if(!white) result = WHITE_TO_BLACK result;
5993 static int autoQueen; // [HGM] oneclick
5996 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5998 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5999 /* [HGM] add Shogi promotions */
6000 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6005 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6006 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6008 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6009 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6012 piece = boards[currentMove][fromY][fromX];
6013 if(gameInfo.variant == VariantShogi) {
6014 promotionZoneSize = BOARD_HEIGHT/3;
6015 highestPromotingPiece = (int)WhiteFerz;
6016 } else if(gameInfo.variant == VariantMakruk) {
6017 promotionZoneSize = 3;
6020 // Treat Lance as Pawn when it is not representing Amazon
6021 if(gameInfo.variant != VariantSuper) {
6022 if(piece == WhiteLance) piece = WhitePawn; else
6023 if(piece == BlackLance) piece = BlackPawn;
6026 // next weed out all moves that do not touch the promotion zone at all
6027 if((int)piece >= BlackPawn) {
6028 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6030 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6032 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6033 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6036 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6038 // weed out mandatory Shogi promotions
6039 if(gameInfo.variant == VariantShogi) {
6040 if(piece >= BlackPawn) {
6041 if(toY == 0 && piece == BlackPawn ||
6042 toY == 0 && piece == BlackQueen ||
6043 toY <= 1 && piece == BlackKnight) {
6048 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6049 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6050 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6057 // weed out obviously illegal Pawn moves
6058 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6059 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6060 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6061 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6062 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6063 // note we are not allowed to test for valid (non-)capture, due to premove
6066 // we either have a choice what to promote to, or (in Shogi) whether to promote
6067 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6068 *promoChoice = PieceToChar(BlackFerz); // no choice
6071 // no sense asking what we must promote to if it is going to explode...
6072 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6073 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6076 // give caller the default choice even if we will not make it
6077 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6078 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6079 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6080 && gameInfo.variant != VariantShogi
6081 && gameInfo.variant != VariantSuper) return FALSE;
6082 if(autoQueen) return FALSE; // predetermined
6084 // suppress promotion popup on illegal moves that are not premoves
6085 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6086 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6087 if(appData.testLegality && !premove) {
6088 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6089 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6090 if(moveType != WhitePromotion && moveType != BlackPromotion)
6098 InPalace(row, column)
6100 { /* [HGM] for Xiangqi */
6101 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6102 column < (BOARD_WIDTH + 4)/2 &&
6103 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6108 PieceForSquare (x, y)
6112 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6115 return boards[currentMove][y][x];
6119 OKToStartUserMove(x, y)
6122 ChessSquare from_piece;
6125 if (matchMode) return FALSE;
6126 if (gameMode == EditPosition) return TRUE;
6128 if (x >= 0 && y >= 0)
6129 from_piece = boards[currentMove][y][x];
6131 from_piece = EmptySquare;
6133 if (from_piece == EmptySquare) return FALSE;
6135 white_piece = (int)from_piece >= (int)WhitePawn &&
6136 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6139 case PlayFromGameFile:
6141 case TwoMachinesPlay:
6149 case MachinePlaysWhite:
6150 case IcsPlayingBlack:
6151 if (appData.zippyPlay) return FALSE;
6153 DisplayMoveError(_("You are playing Black"));
6158 case MachinePlaysBlack:
6159 case IcsPlayingWhite:
6160 if (appData.zippyPlay) return FALSE;
6162 DisplayMoveError(_("You are playing White"));
6168 if (!white_piece && WhiteOnMove(currentMove)) {
6169 DisplayMoveError(_("It is White's turn"));
6172 if (white_piece && !WhiteOnMove(currentMove)) {
6173 DisplayMoveError(_("It is Black's turn"));
6176 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6177 /* Editing correspondence game history */
6178 /* Could disallow this or prompt for confirmation */
6183 case BeginningOfGame:
6184 if (appData.icsActive) return FALSE;
6185 if (!appData.noChessProgram) {
6187 DisplayMoveError(_("You are playing White"));
6194 if (!white_piece && WhiteOnMove(currentMove)) {
6195 DisplayMoveError(_("It is White's turn"));
6198 if (white_piece && !WhiteOnMove(currentMove)) {
6199 DisplayMoveError(_("It is Black's turn"));
6208 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6209 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6210 && gameMode != AnalyzeFile && gameMode != Training) {
6211 DisplayMoveError(_("Displayed position is not current"));
6218 OnlyMove(int *x, int *y, Boolean captures) {
6219 DisambiguateClosure cl;
6220 if (appData.zippyPlay) return FALSE;
6222 case MachinePlaysBlack:
6223 case IcsPlayingWhite:
6224 case BeginningOfGame:
6225 if(!WhiteOnMove(currentMove)) return FALSE;
6227 case MachinePlaysWhite:
6228 case IcsPlayingBlack:
6229 if(WhiteOnMove(currentMove)) return FALSE;
6236 cl.pieceIn = EmptySquare;
6241 cl.promoCharIn = NULLCHAR;
6242 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6243 if( cl.kind == NormalMove ||
6244 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6245 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6246 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6253 if(cl.kind != ImpossibleMove) return FALSE;
6254 cl.pieceIn = EmptySquare;
6259 cl.promoCharIn = NULLCHAR;
6260 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6261 if( cl.kind == NormalMove ||
6262 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6263 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6264 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6269 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6275 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6276 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6277 int lastLoadGameUseList = FALSE;
6278 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6279 ChessMove lastLoadGameStart = EndOfFile;
6282 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6283 int fromX, fromY, toX, toY;
6287 ChessSquare pdown, pup;
6289 /* Check if the user is playing in turn. This is complicated because we
6290 let the user "pick up" a piece before it is his turn. So the piece he
6291 tried to pick up may have been captured by the time he puts it down!
6292 Therefore we use the color the user is supposed to be playing in this
6293 test, not the color of the piece that is currently on the starting
6294 square---except in EditGame mode, where the user is playing both
6295 sides; fortunately there the capture race can't happen. (It can
6296 now happen in IcsExamining mode, but that's just too bad. The user
6297 will get a somewhat confusing message in that case.)
6301 case PlayFromGameFile:
6303 case TwoMachinesPlay:
6307 /* We switched into a game mode where moves are not accepted,
6308 perhaps while the mouse button was down. */
6311 case MachinePlaysWhite:
6312 /* User is moving for Black */
6313 if (WhiteOnMove(currentMove)) {
6314 DisplayMoveError(_("It is White's turn"));
6319 case MachinePlaysBlack:
6320 /* User is moving for White */
6321 if (!WhiteOnMove(currentMove)) {
6322 DisplayMoveError(_("It is Black's turn"));
6329 case BeginningOfGame:
6332 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6333 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6334 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6335 /* User is moving for Black */
6336 if (WhiteOnMove(currentMove)) {
6337 DisplayMoveError(_("It is White's turn"));
6341 /* User is moving for White */
6342 if (!WhiteOnMove(currentMove)) {
6343 DisplayMoveError(_("It is Black's turn"));
6349 case IcsPlayingBlack:
6350 /* User is moving for Black */
6351 if (WhiteOnMove(currentMove)) {
6352 if (!appData.premove) {
6353 DisplayMoveError(_("It is White's turn"));
6354 } else if (toX >= 0 && toY >= 0) {
6357 premoveFromX = fromX;
6358 premoveFromY = fromY;
6359 premovePromoChar = promoChar;
6361 if (appData.debugMode)
6362 fprintf(debugFP, "Got premove: fromX %d,"
6363 "fromY %d, toX %d, toY %d\n",
6364 fromX, fromY, toX, toY);
6370 case IcsPlayingWhite:
6371 /* User is moving for White */
6372 if (!WhiteOnMove(currentMove)) {
6373 if (!appData.premove) {
6374 DisplayMoveError(_("It is Black's turn"));
6375 } else if (toX >= 0 && toY >= 0) {
6378 premoveFromX = fromX;
6379 premoveFromY = fromY;
6380 premovePromoChar = promoChar;
6382 if (appData.debugMode)
6383 fprintf(debugFP, "Got premove: fromX %d,"
6384 "fromY %d, toX %d, toY %d\n",
6385 fromX, fromY, toX, toY);
6395 /* EditPosition, empty square, or different color piece;
6396 click-click move is possible */
6397 if (toX == -2 || toY == -2) {
6398 boards[0][fromY][fromX] = EmptySquare;
6399 DrawPosition(FALSE, boards[currentMove]);
6401 } else if (toX >= 0 && toY >= 0) {
6402 boards[0][toY][toX] = boards[0][fromY][fromX];
6403 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6404 if(boards[0][fromY][0] != EmptySquare) {
6405 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6406 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6409 if(fromX == BOARD_RGHT+1) {
6410 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6411 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6412 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6415 boards[0][fromY][fromX] = EmptySquare;
6416 DrawPosition(FALSE, boards[currentMove]);
6422 if(toX < 0 || toY < 0) return;
6423 pdown = boards[currentMove][fromY][fromX];
6424 pup = boards[currentMove][toY][toX];
6426 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6427 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6428 if( pup != EmptySquare ) return;
6429 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6430 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6431 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6432 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6433 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6434 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6435 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6439 /* [HGM] always test for legality, to get promotion info */
6440 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6441 fromY, fromX, toY, toX, promoChar);
6442 /* [HGM] but possibly ignore an IllegalMove result */
6443 if (appData.testLegality) {
6444 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6445 DisplayMoveError(_("Illegal move"));
6450 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6453 /* Common tail of UserMoveEvent and DropMenuEvent */
6455 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6457 int fromX, fromY, toX, toY;
6458 /*char*/int promoChar;
6462 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6463 // [HGM] superchess: suppress promotions to non-available piece
6464 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6465 if(WhiteOnMove(currentMove)) {
6466 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6468 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6472 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6473 move type in caller when we know the move is a legal promotion */
6474 if(moveType == NormalMove && promoChar)
6475 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6477 /* [HGM] <popupFix> The following if has been moved here from
6478 UserMoveEvent(). Because it seemed to belong here (why not allow
6479 piece drops in training games?), and because it can only be
6480 performed after it is known to what we promote. */
6481 if (gameMode == Training) {
6482 /* compare the move played on the board to the next move in the
6483 * game. If they match, display the move and the opponent's response.
6484 * If they don't match, display an error message.
6488 CopyBoard(testBoard, boards[currentMove]);
6489 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6491 if (CompareBoards(testBoard, boards[currentMove+1])) {
6492 ForwardInner(currentMove+1);
6494 /* Autoplay the opponent's response.
6495 * if appData.animate was TRUE when Training mode was entered,
6496 * the response will be animated.
6498 saveAnimate = appData.animate;
6499 appData.animate = animateTraining;
6500 ForwardInner(currentMove+1);
6501 appData.animate = saveAnimate;
6503 /* check for the end of the game */
6504 if (currentMove >= forwardMostMove) {
6505 gameMode = PlayFromGameFile;
6507 SetTrainingModeOff();
6508 DisplayInformation(_("End of game"));
6511 DisplayError(_("Incorrect move"), 0);
6516 /* Ok, now we know that the move is good, so we can kill
6517 the previous line in Analysis Mode */
6518 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6519 && currentMove < forwardMostMove) {
6520 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6521 else forwardMostMove = currentMove;
6524 /* If we need the chess program but it's dead, restart it */
6525 ResurrectChessProgram();
6527 /* A user move restarts a paused game*/
6531 thinkOutput[0] = NULLCHAR;
6533 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6535 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6536 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540 if (gameMode == BeginningOfGame) {
6541 if (appData.noChessProgram) {
6542 gameMode = EditGame;
6546 gameMode = MachinePlaysBlack;
6549 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6551 if (first.sendName) {
6552 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6553 SendToProgram(buf, &first);
6560 /* Relay move to ICS or chess engine */
6561 if (appData.icsActive) {
6562 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6563 gameMode == IcsExamining) {
6564 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6565 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6567 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6569 // also send plain move, in case ICS does not understand atomic claims
6570 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6574 if (first.sendTime && (gameMode == BeginningOfGame ||
6575 gameMode == MachinePlaysWhite ||
6576 gameMode == MachinePlaysBlack)) {
6577 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6579 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6580 // [HGM] book: if program might be playing, let it use book
6581 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6582 first.maybeThinking = TRUE;
6583 } else SendMoveToProgram(forwardMostMove-1, &first);
6584 if (currentMove == cmailOldMove + 1) {
6585 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6589 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6593 if(appData.testLegality)
6594 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6600 if (WhiteOnMove(currentMove)) {
6601 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6603 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6607 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6612 case MachinePlaysBlack:
6613 case MachinePlaysWhite:
6614 /* disable certain menu options while machine is thinking */
6615 SetMachineThinkingEnables();
6622 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6623 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6625 if(bookHit) { // [HGM] book: simulate book reply
6626 static char bookMove[MSG_SIZ]; // a bit generous?
6628 programStats.nodes = programStats.depth = programStats.time =
6629 programStats.score = programStats.got_only_move = 0;
6630 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6632 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6633 strcat(bookMove, bookHit);
6634 HandleMachineMove(bookMove, &first);
6640 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6647 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6648 Markers *m = (Markers *) closure;
6649 if(rf == fromY && ff == fromX)
6650 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6651 || kind == WhiteCapturesEnPassant
6652 || kind == BlackCapturesEnPassant);
6653 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6657 MarkTargetSquares(int clear)
6660 if(!appData.markers || !appData.highlightDragging ||
6661 !appData.testLegality || gameMode == EditPosition) return;
6663 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6666 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6667 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6668 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6670 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6673 DrawPosition(TRUE, NULL);
6677 Explode(Board board, int fromX, int fromY, int toX, int toY)
6679 if(gameInfo.variant == VariantAtomic &&
6680 (board[toY][toX] != EmptySquare || // capture?
6681 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6682 board[fromY][fromX] == BlackPawn )
6684 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6690 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6692 int CanPromote(ChessSquare piece, int y)
6694 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6695 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6696 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6697 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6698 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6699 gameInfo.variant == VariantMakruk) return FALSE;
6700 return (piece == BlackPawn && y == 1 ||
6701 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6702 piece == BlackLance && y == 1 ||
6703 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6706 void LeftClick(ClickType clickType, int xPix, int yPix)
6709 Boolean saveAnimate;
6710 static int second = 0, promotionChoice = 0, clearFlag = 0;
6711 char promoChoice = NULLCHAR;
6714 if(appData.seekGraph && appData.icsActive && loggedOn &&
6715 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6716 SeekGraphClick(clickType, xPix, yPix, 0);
6720 if (clickType == Press) ErrorPopDown();
6721 MarkTargetSquares(1);
6723 x = EventToSquare(xPix, BOARD_WIDTH);
6724 y = EventToSquare(yPix, BOARD_HEIGHT);
6725 if (!flipView && y >= 0) {
6726 y = BOARD_HEIGHT - 1 - y;
6728 if (flipView && x >= 0) {
6729 x = BOARD_WIDTH - 1 - x;
6732 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6733 defaultPromoChoice = promoSweep;
6734 promoSweep = EmptySquare; // terminate sweep
6735 promoDefaultAltered = TRUE;
6736 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6739 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6740 if(clickType == Release) return; // ignore upclick of click-click destination
6741 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6742 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6743 if(gameInfo.holdingsWidth &&
6744 (WhiteOnMove(currentMove)
6745 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6746 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6747 // click in right holdings, for determining promotion piece
6748 ChessSquare p = boards[currentMove][y][x];
6749 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6750 if(p != EmptySquare) {
6751 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6756 DrawPosition(FALSE, boards[currentMove]);
6760 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6761 if(clickType == Press
6762 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6763 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6764 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6767 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6768 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6770 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6771 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6772 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6773 defaultPromoChoice = DefaultPromoChoice(side);
6776 autoQueen = appData.alwaysPromoteToQueen;
6780 gatingPiece = EmptySquare;
6781 if (clickType != Press) {
6782 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6783 DragPieceEnd(xPix, yPix); dragging = 0;
6784 DrawPosition(FALSE, NULL);
6788 fromX = x; fromY = y;
6789 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6790 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6791 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6793 if (OKToStartUserMove(fromX, fromY)) {
6795 MarkTargetSquares(0);
6796 DragPieceBegin(xPix, yPix); dragging = 1;
6797 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6798 promoSweep = defaultPromoChoice;
6799 selectFlag = 0; lastX = xPix; lastY = yPix;
6800 Sweep(0); // Pawn that is going to promote: preview promotion piece
6801 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6803 if (appData.highlightDragging) {
6804 SetHighlights(fromX, fromY, -1, -1);
6806 } else fromX = fromY = -1;
6812 if (clickType == Press && gameMode != EditPosition) {
6817 // ignore off-board to clicks
6818 if(y < 0 || x < 0) return;
6820 /* Check if clicking again on the same color piece */
6821 fromP = boards[currentMove][fromY][fromX];
6822 toP = boards[currentMove][y][x];
6823 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6824 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6825 WhitePawn <= toP && toP <= WhiteKing &&
6826 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6827 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6828 (BlackPawn <= fromP && fromP <= BlackKing &&
6829 BlackPawn <= toP && toP <= BlackKing &&
6830 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6831 !(fromP == BlackKing && toP == BlackRook && frc))) {
6832 /* Clicked again on same color piece -- changed his mind */
6833 second = (x == fromX && y == fromY);
6834 promoDefaultAltered = FALSE;
6835 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6836 if (appData.highlightDragging) {
6837 SetHighlights(x, y, -1, -1);
6841 if (OKToStartUserMove(x, y)) {
6842 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6843 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6844 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6845 gatingPiece = boards[currentMove][fromY][fromX];
6846 else gatingPiece = EmptySquare;
6848 fromY = y; dragging = 1;
6849 MarkTargetSquares(0);
6850 DragPieceBegin(xPix, yPix);
6851 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6852 promoSweep = defaultPromoChoice;
6853 selectFlag = 0; lastX = xPix; lastY = yPix;
6854 Sweep(0); // Pawn that is going to promote: preview promotion piece
6858 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6861 // ignore clicks on holdings
6862 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6865 if (clickType == Release && x == fromX && y == fromY) {
6866 DragPieceEnd(xPix, yPix); dragging = 0;
6868 // a deferred attempt to click-click move an empty square on top of a piece
6869 boards[currentMove][y][x] = EmptySquare;
6871 DrawPosition(FALSE, boards[currentMove]);
6872 fromX = fromY = -1; clearFlag = 0;
6875 if (appData.animateDragging) {
6876 /* Undo animation damage if any */
6877 DrawPosition(FALSE, NULL);
6880 /* Second up/down in same square; just abort move */
6883 gatingPiece = EmptySquare;
6886 ClearPremoveHighlights();
6888 /* First upclick in same square; start click-click mode */
6889 SetHighlights(x, y, -1, -1);
6896 /* we now have a different from- and (possibly off-board) to-square */
6897 /* Completed move */
6900 saveAnimate = appData.animate;
6901 if (clickType == Press) {
6902 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6903 // must be Edit Position mode with empty-square selected
6904 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6905 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6908 /* Finish clickclick move */
6909 if (appData.animate || appData.highlightLastMove) {
6910 SetHighlights(fromX, fromY, toX, toY);
6915 /* Finish drag move */
6916 if (appData.highlightLastMove) {
6917 SetHighlights(fromX, fromY, toX, toY);
6921 DragPieceEnd(xPix, yPix); dragging = 0;
6922 /* Don't animate move and drag both */
6923 appData.animate = FALSE;
6926 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6927 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6928 ChessSquare piece = boards[currentMove][fromY][fromX];
6929 if(gameMode == EditPosition && piece != EmptySquare &&
6930 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6933 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6934 n = PieceToNumber(piece - (int)BlackPawn);
6935 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6936 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6937 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6939 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6940 n = PieceToNumber(piece);
6941 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6942 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6943 boards[currentMove][n][BOARD_WIDTH-2]++;
6945 boards[currentMove][fromY][fromX] = EmptySquare;
6949 DrawPosition(TRUE, boards[currentMove]);
6953 // off-board moves should not be highlighted
6954 if(x < 0 || y < 0) ClearHighlights();
6956 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6958 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6959 SetHighlights(fromX, fromY, toX, toY);
6960 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6961 // [HGM] super: promotion to captured piece selected from holdings
6962 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6963 promotionChoice = TRUE;
6964 // kludge follows to temporarily execute move on display, without promoting yet
6965 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6966 boards[currentMove][toY][toX] = p;
6967 DrawPosition(FALSE, boards[currentMove]);
6968 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6969 boards[currentMove][toY][toX] = q;
6970 DisplayMessage("Click in holdings to choose piece", "");
6975 int oldMove = currentMove;
6976 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6977 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6978 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6979 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6980 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6981 DrawPosition(TRUE, boards[currentMove]);
6984 appData.animate = saveAnimate;
6985 if (appData.animate || appData.animateDragging) {
6986 /* Undo animation damage if needed */
6987 DrawPosition(FALSE, NULL);
6991 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6992 { // front-end-free part taken out of PieceMenuPopup
6993 int whichMenu; int xSqr, ySqr;
6995 if(seekGraphUp) { // [HGM] seekgraph
6996 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6997 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7001 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7002 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7003 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7004 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7005 if(action == Press) {
7006 originalFlip = flipView;
7007 flipView = !flipView; // temporarily flip board to see game from partners perspective
7008 DrawPosition(TRUE, partnerBoard);
7009 DisplayMessage(partnerStatus, "");
7011 } else if(action == Release) {
7012 flipView = originalFlip;
7013 DrawPosition(TRUE, boards[currentMove]);
7019 xSqr = EventToSquare(x, BOARD_WIDTH);
7020 ySqr = EventToSquare(y, BOARD_HEIGHT);
7021 if (action == Release) {
7022 if(pieceSweep != EmptySquare) {
7023 EditPositionMenuEvent(pieceSweep, toX, toY);
7024 pieceSweep = EmptySquare;
7025 } else UnLoadPV(); // [HGM] pv
7027 if (action != Press) return -2; // return code to be ignored
7030 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7032 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7033 if (xSqr < 0 || ySqr < 0) return -1;
7034 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7035 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7036 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7037 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7041 if(!appData.icsEngineAnalyze) return -1;
7042 case IcsPlayingWhite:
7043 case IcsPlayingBlack:
7044 if(!appData.zippyPlay) goto noZip;
7047 case MachinePlaysWhite:
7048 case MachinePlaysBlack:
7049 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7050 if (!appData.dropMenu) {
7052 return 2; // flag front-end to grab mouse events
7054 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7055 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7058 if (xSqr < 0 || ySqr < 0) return -1;
7059 if (!appData.dropMenu || appData.testLegality &&
7060 gameInfo.variant != VariantBughouse &&
7061 gameInfo.variant != VariantCrazyhouse) return -1;
7062 whichMenu = 1; // drop menu
7068 if (((*fromX = xSqr) < 0) ||
7069 ((*fromY = ySqr) < 0)) {
7070 *fromX = *fromY = -1;
7074 *fromX = BOARD_WIDTH - 1 - *fromX;
7076 *fromY = BOARD_HEIGHT - 1 - *fromY;
7081 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7083 // char * hint = lastHint;
7084 FrontEndProgramStats stats;
7086 stats.which = cps == &first ? 0 : 1;
7087 stats.depth = cpstats->depth;
7088 stats.nodes = cpstats->nodes;
7089 stats.score = cpstats->score;
7090 stats.time = cpstats->time;
7091 stats.pv = cpstats->movelist;
7092 stats.hint = lastHint;
7093 stats.an_move_index = 0;
7094 stats.an_move_count = 0;
7096 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7097 stats.hint = cpstats->move_name;
7098 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7099 stats.an_move_count = cpstats->nr_moves;
7102 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
7104 SetProgramStats( &stats );
7107 #define MAXPLAYERS 500
7110 TourneyStandings(int display)
7112 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7113 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7114 char result, *p, *names[MAXPLAYERS];
7116 names[0] = p = strdup(appData.participants);
7117 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7119 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7121 while(result = appData.results[nr]) {
7122 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7123 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7124 wScore = bScore = 0;
7126 case '+': wScore = 2; break;
7127 case '-': bScore = 2; break;
7128 case '=': wScore = bScore = 1; break;
7130 case '*': return strdup("busy"); // tourney not finished
7138 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7139 for(w=0; w<nPlayers; w++) {
7141 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7142 ranking[w] = b; points[w] = bScore; score[b] = -2;
7144 p = malloc(nPlayers*34+1);
7145 for(w=0; w<nPlayers && w<display; w++)
7146 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7152 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7153 { // count all piece types
7155 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7156 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7157 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7160 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7161 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7162 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7163 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7164 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7165 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7170 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7172 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7173 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7175 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7176 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7177 if(myPawns == 2 && nMine == 3) // KPP
7178 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7179 if(myPawns == 1 && nMine == 2) // KP
7180 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7181 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7182 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7183 if(myPawns) return FALSE;
7184 if(pCnt[WhiteRook+side])
7185 return pCnt[BlackRook-side] ||
7186 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7187 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7188 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7189 if(pCnt[WhiteCannon+side]) {
7190 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7191 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7193 if(pCnt[WhiteKnight+side])
7194 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7199 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7201 VariantClass v = gameInfo.variant;
7203 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7204 if(v == VariantShatranj) return TRUE; // always winnable through baring
7205 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7206 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7208 if(v == VariantXiangqi) {
7209 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7211 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7212 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7213 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7214 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7215 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7216 if(stale) // we have at least one last-rank P plus perhaps C
7217 return majors // KPKX
7218 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7220 return pCnt[WhiteFerz+side] // KCAK
7221 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7222 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7223 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7225 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7226 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7228 if(nMine == 1) return FALSE; // bare King
7229 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
7230 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7231 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7232 // by now we have King + 1 piece (or multiple Bishops on the same color)
7233 if(pCnt[WhiteKnight+side])
7234 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7235 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7236 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7238 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7239 if(pCnt[WhiteAlfil+side])
7240 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7241 if(pCnt[WhiteWazir+side])
7242 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7249 Adjudicate(ChessProgramState *cps)
7250 { // [HGM] some adjudications useful with buggy engines
7251 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7252 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7253 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7254 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7255 int k, count = 0; static int bare = 1;
7256 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7257 Boolean canAdjudicate = !appData.icsActive;
7259 // most tests only when we understand the game, i.e. legality-checking on
7260 if( appData.testLegality )
7261 { /* [HGM] Some more adjudications for obstinate engines */
7262 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7263 static int moveCount = 6;
7265 char *reason = NULL;
7267 /* Count what is on board. */
7268 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7270 /* Some material-based adjudications that have to be made before stalemate test */
7271 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7272 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7273 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7274 if(canAdjudicate && appData.checkMates) {
7276 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7277 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7278 "Xboard adjudication: King destroyed", GE_XBOARD );
7283 /* Bare King in Shatranj (loses) or Losers (wins) */
7284 if( nrW == 1 || nrB == 1) {
7285 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7286 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7287 if(canAdjudicate && appData.checkMates) {
7289 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7290 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7291 "Xboard adjudication: Bare king", GE_XBOARD );
7295 if( gameInfo.variant == VariantShatranj && --bare < 0)
7297 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7298 if(canAdjudicate && appData.checkMates) {
7299 /* but only adjudicate if adjudication enabled */
7301 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7302 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7303 "Xboard adjudication: Bare king", GE_XBOARD );
7310 // don't wait for engine to announce game end if we can judge ourselves
7311 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7313 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7314 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7315 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7316 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7319 reason = "Xboard adjudication: 3rd check";
7320 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7330 reason = "Xboard adjudication: Stalemate";
7331 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7332 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7333 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7334 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7335 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7336 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7337 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7338 EP_CHECKMATE : EP_WINS);
7339 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7340 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7344 reason = "Xboard adjudication: Checkmate";
7345 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7349 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7351 result = GameIsDrawn; break;
7353 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7355 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7359 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7361 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7362 GameEnds( result, reason, GE_XBOARD );
7366 /* Next absolutely insufficient mating material. */
7367 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7368 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7369 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7371 /* always flag draws, for judging claims */
7372 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7374 if(canAdjudicate && appData.materialDraws) {
7375 /* but only adjudicate them if adjudication enabled */
7376 if(engineOpponent) {
7377 SendToProgram("force\n", engineOpponent); // suppress reply
7378 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7380 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7385 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7386 if(gameInfo.variant == VariantXiangqi ?
7387 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7389 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7390 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7391 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7392 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7394 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7395 { /* if the first 3 moves do not show a tactical win, declare draw */
7396 if(engineOpponent) {
7397 SendToProgram("force\n", engineOpponent); // suppress reply
7398 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7400 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7403 } else moveCount = 6;
7405 if (appData.debugMode) { int i;
7406 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7407 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7408 appData.drawRepeats);
7409 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7410 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7414 // Repetition draws and 50-move rule can be applied independently of legality testing
7416 /* Check for rep-draws */
7418 for(k = forwardMostMove-2;
7419 k>=backwardMostMove && k>=forwardMostMove-100 &&
7420 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7421 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7424 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7425 /* compare castling rights */
7426 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7427 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7428 rights++; /* King lost rights, while rook still had them */
7429 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7430 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7431 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7432 rights++; /* but at least one rook lost them */
7434 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7435 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7437 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7438 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7439 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7442 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7443 && appData.drawRepeats > 1) {
7444 /* adjudicate after user-specified nr of repeats */
7445 int result = GameIsDrawn;
7446 char *details = "XBoard adjudication: repetition draw";
7447 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7448 // [HGM] xiangqi: check for forbidden perpetuals
7449 int m, ourPerpetual = 1, hisPerpetual = 1;
7450 for(m=forwardMostMove; m>k; m-=2) {
7451 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7452 ourPerpetual = 0; // the current mover did not always check
7453 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7454 hisPerpetual = 0; // the opponent did not always check
7456 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7457 ourPerpetual, hisPerpetual);
7458 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7459 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7460 details = "Xboard adjudication: perpetual checking";
7462 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7463 break; // (or we would have caught him before). Abort repetition-checking loop.
7465 // Now check for perpetual chases
7466 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7467 hisPerpetual = PerpetualChase(k, forwardMostMove);
7468 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7469 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7470 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7471 details = "Xboard adjudication: perpetual chasing";
7473 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7474 break; // Abort repetition-checking loop.
7476 // if neither of us is checking or chasing all the time, or both are, it is draw
7478 if(engineOpponent) {
7479 SendToProgram("force\n", engineOpponent); // suppress reply
7480 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7482 GameEnds( result, details, GE_XBOARD );
7485 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7486 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7490 /* Now we test for 50-move draws. Determine ply count */
7491 count = forwardMostMove;
7492 /* look for last irreversble move */
7493 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7495 /* if we hit starting position, add initial plies */
7496 if( count == backwardMostMove )
7497 count -= initialRulePlies;
7498 count = forwardMostMove - count;
7499 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7500 // adjust reversible move counter for checks in Xiangqi
7501 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7502 if(i < backwardMostMove) i = backwardMostMove;
7503 while(i <= forwardMostMove) {
7504 lastCheck = inCheck; // check evasion does not count
7505 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7506 if(inCheck || lastCheck) count--; // check does not count
7511 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7512 /* this is used to judge if draw claims are legal */
7513 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7514 if(engineOpponent) {
7515 SendToProgram("force\n", engineOpponent); // suppress reply
7516 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7518 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7522 /* if draw offer is pending, treat it as a draw claim
7523 * when draw condition present, to allow engines a way to
7524 * claim draws before making their move to avoid a race
7525 * condition occurring after their move
7527 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7529 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7530 p = "Draw claim: 50-move rule";
7531 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7532 p = "Draw claim: 3-fold repetition";
7533 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7534 p = "Draw claim: insufficient mating material";
7535 if( p != NULL && canAdjudicate) {
7536 if(engineOpponent) {
7537 SendToProgram("force\n", engineOpponent); // suppress reply
7538 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7540 GameEnds( GameIsDrawn, p, GE_XBOARD );
7545 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7546 if(engineOpponent) {
7547 SendToProgram("force\n", engineOpponent); // suppress reply
7548 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7550 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7556 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7557 { // [HGM] book: this routine intercepts moves to simulate book replies
7558 char *bookHit = NULL;
7560 //first determine if the incoming move brings opponent into his book
7561 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7562 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7563 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7564 if(bookHit != NULL && !cps->bookSuspend) {
7565 // make sure opponent is not going to reply after receiving move to book position
7566 SendToProgram("force\n", cps);
7567 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7569 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7570 // now arrange restart after book miss
7572 // after a book hit we never send 'go', and the code after the call to this routine
7573 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7575 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7576 SendToProgram(buf, cps);
7577 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7578 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7579 SendToProgram("go\n", cps);
7580 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7581 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7582 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7583 SendToProgram("go\n", cps);
7584 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7586 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7590 ChessProgramState *savedState;
7591 void DeferredBookMove(void)
7593 if(savedState->lastPing != savedState->lastPong)
7594 ScheduleDelayedEvent(DeferredBookMove, 10);
7596 HandleMachineMove(savedMessage, savedState);
7600 HandleMachineMove(message, cps)
7602 ChessProgramState *cps;
7604 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7605 char realname[MSG_SIZ];
7606 int fromX, fromY, toX, toY;
7615 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7617 * Kludge to ignore BEL characters
7619 while (*message == '\007') message++;
7622 * [HGM] engine debug message: ignore lines starting with '#' character
7624 if(cps->debug && *message == '#') return;
7627 * Look for book output
7629 if (cps == &first && bookRequested) {
7630 if (message[0] == '\t' || message[0] == ' ') {
7631 /* Part of the book output is here; append it */
7632 strcat(bookOutput, message);
7633 strcat(bookOutput, " \n");
7635 } else if (bookOutput[0] != NULLCHAR) {
7636 /* All of book output has arrived; display it */
7637 char *p = bookOutput;
7638 while (*p != NULLCHAR) {
7639 if (*p == '\t') *p = ' ';
7642 DisplayInformation(bookOutput);
7643 bookRequested = FALSE;
7644 /* Fall through to parse the current output */
7649 * Look for machine move.
7651 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7652 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7654 /* This method is only useful on engines that support ping */
7655 if (cps->lastPing != cps->lastPong) {
7656 if (gameMode == BeginningOfGame) {
7657 /* Extra move from before last new; ignore */
7658 if (appData.debugMode) {
7659 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7662 if (appData.debugMode) {
7663 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7664 cps->which, gameMode);
7667 SendToProgram("undo\n", cps);
7673 case BeginningOfGame:
7674 /* Extra move from before last reset; ignore */
7675 if (appData.debugMode) {
7676 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7683 /* Extra move after we tried to stop. The mode test is
7684 not a reliable way of detecting this problem, but it's
7685 the best we can do on engines that don't support ping.
7687 if (appData.debugMode) {
7688 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7689 cps->which, gameMode);
7691 SendToProgram("undo\n", cps);
7694 case MachinePlaysWhite:
7695 case IcsPlayingWhite:
7696 machineWhite = TRUE;
7699 case MachinePlaysBlack:
7700 case IcsPlayingBlack:
7701 machineWhite = FALSE;
7704 case TwoMachinesPlay:
7705 machineWhite = (cps->twoMachinesColor[0] == 'w');
7708 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7709 if (appData.debugMode) {
7711 "Ignoring move out of turn by %s, gameMode %d"
7712 ", forwardMost %d\n",
7713 cps->which, gameMode, forwardMostMove);
7718 if (appData.debugMode) { int f = forwardMostMove;
7719 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7720 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7721 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7723 if(cps->alphaRank) AlphaRank(machineMove, 4);
7724 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7725 &fromX, &fromY, &toX, &toY, &promoChar)) {
7726 /* Machine move could not be parsed; ignore it. */
7727 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7728 machineMove, _(cps->which));
7729 DisplayError(buf1, 0);
7730 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7731 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7732 if (gameMode == TwoMachinesPlay) {
7733 GameEnds(machineWhite ? BlackWins : WhiteWins,
7739 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7740 /* So we have to redo legality test with true e.p. status here, */
7741 /* to make sure an illegal e.p. capture does not slip through, */
7742 /* to cause a forfeit on a justified illegal-move complaint */
7743 /* of the opponent. */
7744 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7746 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7747 fromY, fromX, toY, toX, promoChar);
7748 if (appData.debugMode) {
7750 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7751 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7752 fprintf(debugFP, "castling rights\n");
7754 if(moveType == IllegalMove) {
7755 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7756 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7757 GameEnds(machineWhite ? BlackWins : WhiteWins,
7760 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7761 /* [HGM] Kludge to handle engines that send FRC-style castling
7762 when they shouldn't (like TSCP-Gothic) */
7764 case WhiteASideCastleFR:
7765 case BlackASideCastleFR:
7767 currentMoveString[2]++;
7769 case WhiteHSideCastleFR:
7770 case BlackHSideCastleFR:
7772 currentMoveString[2]--;
7774 default: ; // nothing to do, but suppresses warning of pedantic compilers
7777 hintRequested = FALSE;
7778 lastHint[0] = NULLCHAR;
7779 bookRequested = FALSE;
7780 /* Program may be pondering now */
7781 cps->maybeThinking = TRUE;
7782 if (cps->sendTime == 2) cps->sendTime = 1;
7783 if (cps->offeredDraw) cps->offeredDraw--;
7785 /* [AS] Save move info*/
7786 pvInfoList[ forwardMostMove ].score = programStats.score;
7787 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7788 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7790 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7792 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7793 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7796 while( count < adjudicateLossPlies ) {
7797 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7800 score = -score; /* Flip score for winning side */
7803 if( score > adjudicateLossThreshold ) {
7810 if( count >= adjudicateLossPlies ) {
7811 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7813 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7814 "Xboard adjudication",
7821 if(Adjudicate(cps)) {
7822 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7823 return; // [HGM] adjudicate: for all automatic game ends
7827 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7829 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7830 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7832 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7834 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7836 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7837 char buf[3*MSG_SIZ];
7839 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7840 programStats.score / 100.,
7842 programStats.time / 100.,
7843 (unsigned int)programStats.nodes,
7844 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7845 programStats.movelist);
7847 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7852 /* [AS] Clear stats for next move */
7853 ClearProgramStats();
7854 thinkOutput[0] = NULLCHAR;
7855 hiddenThinkOutputState = 0;
7858 if (gameMode == TwoMachinesPlay) {
7859 /* [HGM] relaying draw offers moved to after reception of move */
7860 /* and interpreting offer as claim if it brings draw condition */
7861 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7862 SendToProgram("draw\n", cps->other);
7864 if (cps->other->sendTime) {
7865 SendTimeRemaining(cps->other,
7866 cps->other->twoMachinesColor[0] == 'w');
7868 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7869 if (firstMove && !bookHit) {
7871 if (cps->other->useColors) {
7872 SendToProgram(cps->other->twoMachinesColor, cps->other);
7874 SendToProgram("go\n", cps->other);
7876 cps->other->maybeThinking = TRUE;
7879 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7881 if (!pausing && appData.ringBellAfterMoves) {
7886 * Reenable menu items that were disabled while
7887 * machine was thinking
7889 if (gameMode != TwoMachinesPlay)
7890 SetUserThinkingEnables();
7892 // [HGM] book: after book hit opponent has received move and is now in force mode
7893 // force the book reply into it, and then fake that it outputted this move by jumping
7894 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7896 static char bookMove[MSG_SIZ]; // a bit generous?
7898 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7899 strcat(bookMove, bookHit);
7902 programStats.nodes = programStats.depth = programStats.time =
7903 programStats.score = programStats.got_only_move = 0;
7904 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7906 if(cps->lastPing != cps->lastPong) {
7907 savedMessage = message; // args for deferred call
7909 ScheduleDelayedEvent(DeferredBookMove, 10);
7918 /* Set special modes for chess engines. Later something general
7919 * could be added here; for now there is just one kludge feature,
7920 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7921 * when "xboard" is given as an interactive command.
7923 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7924 cps->useSigint = FALSE;
7925 cps->useSigterm = FALSE;
7927 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7928 ParseFeatures(message+8, cps);
7929 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7932 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7933 int dummy, s=6; char buf[MSG_SIZ];
7934 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7935 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7936 ParseFEN(boards[0], &dummy, message+s);
7937 DrawPosition(TRUE, boards[0]);
7938 startedFromSetupPosition = TRUE;
7941 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7942 * want this, I was asked to put it in, and obliged.
7944 if (!strncmp(message, "setboard ", 9)) {
7945 Board initial_position;
7947 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7949 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7950 DisplayError(_("Bad FEN received from engine"), 0);
7954 CopyBoard(boards[0], initial_position);
7955 initialRulePlies = FENrulePlies;
7956 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7957 else gameMode = MachinePlaysBlack;
7958 DrawPosition(FALSE, boards[currentMove]);
7964 * Look for communication commands
7966 if (!strncmp(message, "telluser ", 9)) {
7967 if(message[9] == '\\' && message[10] == '\\')
7968 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7969 DisplayNote(message + 9);
7972 if (!strncmp(message, "tellusererror ", 14)) {
7974 if(message[14] == '\\' && message[15] == '\\')
7975 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7976 DisplayError(message + 14, 0);
7979 if (!strncmp(message, "tellopponent ", 13)) {
7980 if (appData.icsActive) {
7982 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7986 DisplayNote(message + 13);
7990 if (!strncmp(message, "tellothers ", 11)) {
7991 if (appData.icsActive) {
7993 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7999 if (!strncmp(message, "tellall ", 8)) {
8000 if (appData.icsActive) {
8002 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8006 DisplayNote(message + 8);
8010 if (strncmp(message, "warning", 7) == 0) {
8011 /* Undocumented feature, use tellusererror in new code */
8012 DisplayError(message, 0);
8015 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8016 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8017 strcat(realname, " query");
8018 AskQuestion(realname, buf2, buf1, cps->pr);
8021 /* Commands from the engine directly to ICS. We don't allow these to be
8022 * sent until we are logged on. Crafty kibitzes have been known to
8023 * interfere with the login process.
8026 if (!strncmp(message, "tellics ", 8)) {
8027 SendToICS(message + 8);
8031 if (!strncmp(message, "tellicsnoalias ", 15)) {
8032 SendToICS(ics_prefix);
8033 SendToICS(message + 15);
8037 /* The following are for backward compatibility only */
8038 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8039 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8040 SendToICS(ics_prefix);
8046 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8050 * If the move is illegal, cancel it and redraw the board.
8051 * Also deal with other error cases. Matching is rather loose
8052 * here to accommodate engines written before the spec.
8054 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8055 strncmp(message, "Error", 5) == 0) {
8056 if (StrStr(message, "name") ||
8057 StrStr(message, "rating") || StrStr(message, "?") ||
8058 StrStr(message, "result") || StrStr(message, "board") ||
8059 StrStr(message, "bk") || StrStr(message, "computer") ||
8060 StrStr(message, "variant") || StrStr(message, "hint") ||
8061 StrStr(message, "random") || StrStr(message, "depth") ||
8062 StrStr(message, "accepted")) {
8065 if (StrStr(message, "protover")) {
8066 /* Program is responding to input, so it's apparently done
8067 initializing, and this error message indicates it is
8068 protocol version 1. So we don't need to wait any longer
8069 for it to initialize and send feature commands. */
8070 FeatureDone(cps, 1);
8071 cps->protocolVersion = 1;
8074 cps->maybeThinking = FALSE;
8076 if (StrStr(message, "draw")) {
8077 /* Program doesn't have "draw" command */
8078 cps->sendDrawOffers = 0;
8081 if (cps->sendTime != 1 &&
8082 (StrStr(message, "time") || StrStr(message, "otim"))) {
8083 /* Program apparently doesn't have "time" or "otim" command */
8087 if (StrStr(message, "analyze")) {
8088 cps->analysisSupport = FALSE;
8089 cps->analyzing = FALSE;
8091 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8092 DisplayError(buf2, 0);
8095 if (StrStr(message, "(no matching move)st")) {
8096 /* Special kludge for GNU Chess 4 only */
8097 cps->stKludge = TRUE;
8098 SendTimeControl(cps, movesPerSession, timeControl,
8099 timeIncrement, appData.searchDepth,
8103 if (StrStr(message, "(no matching move)sd")) {
8104 /* Special kludge for GNU Chess 4 only */
8105 cps->sdKludge = TRUE;
8106 SendTimeControl(cps, movesPerSession, timeControl,
8107 timeIncrement, appData.searchDepth,
8111 if (!StrStr(message, "llegal")) {
8114 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8115 gameMode == IcsIdle) return;
8116 if (forwardMostMove <= backwardMostMove) return;
8117 if (pausing) PauseEvent();
8118 if(appData.forceIllegal) {
8119 // [HGM] illegal: machine refused move; force position after move into it
8120 SendToProgram("force\n", cps);
8121 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8122 // we have a real problem now, as SendBoard will use the a2a3 kludge
8123 // when black is to move, while there might be nothing on a2 or black
8124 // might already have the move. So send the board as if white has the move.
8125 // But first we must change the stm of the engine, as it refused the last move
8126 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8127 if(WhiteOnMove(forwardMostMove)) {
8128 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8129 SendBoard(cps, forwardMostMove); // kludgeless board
8131 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8132 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8133 SendBoard(cps, forwardMostMove+1); // kludgeless board
8135 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8136 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8137 gameMode == TwoMachinesPlay)
8138 SendToProgram("go\n", cps);
8141 if (gameMode == PlayFromGameFile) {
8142 /* Stop reading this game file */
8143 gameMode = EditGame;
8146 /* [HGM] illegal-move claim should forfeit game when Xboard */
8147 /* only passes fully legal moves */
8148 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8149 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8150 "False illegal-move claim", GE_XBOARD );
8151 return; // do not take back move we tested as valid
8153 currentMove = forwardMostMove-1;
8154 DisplayMove(currentMove-1); /* before DisplayMoveError */
8155 SwitchClocks(forwardMostMove-1); // [HGM] race
8156 DisplayBothClocks();
8157 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8158 parseList[currentMove], _(cps->which));
8159 DisplayMoveError(buf1);
8160 DrawPosition(FALSE, boards[currentMove]);
8163 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8164 /* Program has a broken "time" command that
8165 outputs a string not ending in newline.
8171 * If chess program startup fails, exit with an error message.
8172 * Attempts to recover here are futile.
8174 if ((StrStr(message, "unknown host") != NULL)
8175 || (StrStr(message, "No remote directory") != NULL)
8176 || (StrStr(message, "not found") != NULL)
8177 || (StrStr(message, "No such file") != NULL)
8178 || (StrStr(message, "can't alloc") != NULL)
8179 || (StrStr(message, "Permission denied") != NULL)) {
8181 cps->maybeThinking = FALSE;
8182 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8183 _(cps->which), cps->program, cps->host, message);
8184 RemoveInputSource(cps->isr);
8185 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8186 if(cps == &first) appData.noChessProgram = TRUE;
8187 DisplayError(buf1, 0);
8193 * Look for hint output
8195 if (sscanf(message, "Hint: %s", buf1) == 1) {
8196 if (cps == &first && hintRequested) {
8197 hintRequested = FALSE;
8198 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8199 &fromX, &fromY, &toX, &toY, &promoChar)) {
8200 (void) CoordsToAlgebraic(boards[forwardMostMove],
8201 PosFlags(forwardMostMove),
8202 fromY, fromX, toY, toX, promoChar, buf1);
8203 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8204 DisplayInformation(buf2);
8206 /* Hint move could not be parsed!? */
8207 snprintf(buf2, sizeof(buf2),
8208 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8209 buf1, _(cps->which));
8210 DisplayError(buf2, 0);
8213 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8219 * Ignore other messages if game is not in progress
8221 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8222 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8225 * look for win, lose, draw, or draw offer
8227 if (strncmp(message, "1-0", 3) == 0) {
8228 char *p, *q, *r = "";
8229 p = strchr(message, '{');
8237 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8239 } else if (strncmp(message, "0-1", 3) == 0) {
8240 char *p, *q, *r = "";
8241 p = strchr(message, '{');
8249 /* Kludge for Arasan 4.1 bug */
8250 if (strcmp(r, "Black resigns") == 0) {
8251 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8254 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8256 } else if (strncmp(message, "1/2", 3) == 0) {
8257 char *p, *q, *r = "";
8258 p = strchr(message, '{');
8267 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8270 } else if (strncmp(message, "White resign", 12) == 0) {
8271 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8273 } else if (strncmp(message, "Black resign", 12) == 0) {
8274 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8276 } else if (strncmp(message, "White matches", 13) == 0 ||
8277 strncmp(message, "Black matches", 13) == 0 ) {
8278 /* [HGM] ignore GNUShogi noises */
8280 } else if (strncmp(message, "White", 5) == 0 &&
8281 message[5] != '(' &&
8282 StrStr(message, "Black") == NULL) {
8283 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8285 } else if (strncmp(message, "Black", 5) == 0 &&
8286 message[5] != '(') {
8287 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8289 } else if (strcmp(message, "resign") == 0 ||
8290 strcmp(message, "computer resigns") == 0) {
8292 case MachinePlaysBlack:
8293 case IcsPlayingBlack:
8294 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8296 case MachinePlaysWhite:
8297 case IcsPlayingWhite:
8298 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8300 case TwoMachinesPlay:
8301 if (cps->twoMachinesColor[0] == 'w')
8302 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8304 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8311 } else if (strncmp(message, "opponent mates", 14) == 0) {
8313 case MachinePlaysBlack:
8314 case IcsPlayingBlack:
8315 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8317 case MachinePlaysWhite:
8318 case IcsPlayingWhite:
8319 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8321 case TwoMachinesPlay:
8322 if (cps->twoMachinesColor[0] == 'w')
8323 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8325 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8332 } else if (strncmp(message, "computer mates", 14) == 0) {
8334 case MachinePlaysBlack:
8335 case IcsPlayingBlack:
8336 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8338 case MachinePlaysWhite:
8339 case IcsPlayingWhite:
8340 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8342 case TwoMachinesPlay:
8343 if (cps->twoMachinesColor[0] == 'w')
8344 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8346 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8353 } else if (strncmp(message, "checkmate", 9) == 0) {
8354 if (WhiteOnMove(forwardMostMove)) {
8355 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8357 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8360 } else if (strstr(message, "Draw") != NULL ||
8361 strstr(message, "game is a draw") != NULL) {
8362 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8364 } else if (strstr(message, "offer") != NULL &&
8365 strstr(message, "draw") != NULL) {
8367 if (appData.zippyPlay && first.initDone) {
8368 /* Relay offer to ICS */
8369 SendToICS(ics_prefix);
8370 SendToICS("draw\n");
8373 cps->offeredDraw = 2; /* valid until this engine moves twice */
8374 if (gameMode == TwoMachinesPlay) {
8375 if (cps->other->offeredDraw) {
8376 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8377 /* [HGM] in two-machine mode we delay relaying draw offer */
8378 /* until after we also have move, to see if it is really claim */
8380 } else if (gameMode == MachinePlaysWhite ||
8381 gameMode == MachinePlaysBlack) {
8382 if (userOfferedDraw) {
8383 DisplayInformation(_("Machine accepts your draw offer"));
8384 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8386 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8393 * Look for thinking output
8395 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8396 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8398 int plylev, mvleft, mvtot, curscore, time;
8399 char mvname[MOVE_LEN];
8403 int prefixHint = FALSE;
8404 mvname[0] = NULLCHAR;
8407 case MachinePlaysBlack:
8408 case IcsPlayingBlack:
8409 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8411 case MachinePlaysWhite:
8412 case IcsPlayingWhite:
8413 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8418 case IcsObserving: /* [DM] icsEngineAnalyze */
8419 if (!appData.icsEngineAnalyze) ignore = TRUE;
8421 case TwoMachinesPlay:
8422 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8432 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8434 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8435 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8437 if (plyext != ' ' && plyext != '\t') {
8441 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8442 if( cps->scoreIsAbsolute &&
8443 ( gameMode == MachinePlaysBlack ||
8444 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8445 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8446 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8447 !WhiteOnMove(currentMove)
8450 curscore = -curscore;
8454 tempStats.depth = plylev;
8455 tempStats.nodes = nodes;
8456 tempStats.time = time;
8457 tempStats.score = curscore;
8458 tempStats.got_only_move = 0;
8460 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8463 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8464 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8465 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8466 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8467 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8468 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8469 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8470 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8473 /* Buffer overflow protection */
8474 if (buf1[0] != NULLCHAR) {
8475 if (strlen(buf1) >= sizeof(tempStats.movelist)
8476 && appData.debugMode) {
8478 "PV is too long; using the first %u bytes.\n",
8479 (unsigned) sizeof(tempStats.movelist) - 1);
8482 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8484 sprintf(tempStats.movelist, " no PV\n");
8487 if (tempStats.seen_stat) {
8488 tempStats.ok_to_send = 1;
8491 if (strchr(tempStats.movelist, '(') != NULL) {
8492 tempStats.line_is_book = 1;
8493 tempStats.nr_moves = 0;
8494 tempStats.moves_left = 0;
8496 tempStats.line_is_book = 0;
8499 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8500 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8502 SendProgramStatsToFrontend( cps, &tempStats );
8505 [AS] Protect the thinkOutput buffer from overflow... this
8506 is only useful if buf1 hasn't overflowed first!
8508 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8510 (gameMode == TwoMachinesPlay ?
8511 ToUpper(cps->twoMachinesColor[0]) : ' '),
8512 ((double) curscore) / 100.0,
8513 prefixHint ? lastHint : "",
8514 prefixHint ? " " : "" );
8516 if( buf1[0] != NULLCHAR ) {
8517 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8519 if( strlen(buf1) > max_len ) {
8520 if( appData.debugMode) {
8521 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8523 buf1[max_len+1] = '\0';
8526 strcat( thinkOutput, buf1 );
8529 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8530 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8531 DisplayMove(currentMove - 1);
8535 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8536 /* crafty (9.25+) says "(only move) <move>"
8537 * if there is only 1 legal move
8539 sscanf(p, "(only move) %s", buf1);
8540 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8541 sprintf(programStats.movelist, "%s (only move)", buf1);
8542 programStats.depth = 1;
8543 programStats.nr_moves = 1;
8544 programStats.moves_left = 1;
8545 programStats.nodes = 1;
8546 programStats.time = 1;
8547 programStats.got_only_move = 1;
8549 /* Not really, but we also use this member to
8550 mean "line isn't going to change" (Crafty
8551 isn't searching, so stats won't change) */
8552 programStats.line_is_book = 1;
8554 SendProgramStatsToFrontend( cps, &programStats );
8556 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8557 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8558 DisplayMove(currentMove - 1);
8561 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8562 &time, &nodes, &plylev, &mvleft,
8563 &mvtot, mvname) >= 5) {
8564 /* The stat01: line is from Crafty (9.29+) in response
8565 to the "." command */
8566 programStats.seen_stat = 1;
8567 cps->maybeThinking = TRUE;
8569 if (programStats.got_only_move || !appData.periodicUpdates)
8572 programStats.depth = plylev;
8573 programStats.time = time;
8574 programStats.nodes = nodes;
8575 programStats.moves_left = mvleft;
8576 programStats.nr_moves = mvtot;
8577 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8578 programStats.ok_to_send = 1;
8579 programStats.movelist[0] = '\0';
8581 SendProgramStatsToFrontend( cps, &programStats );
8585 } else if (strncmp(message,"++",2) == 0) {
8586 /* Crafty 9.29+ outputs this */
8587 programStats.got_fail = 2;
8590 } else if (strncmp(message,"--",2) == 0) {
8591 /* Crafty 9.29+ outputs this */
8592 programStats.got_fail = 1;
8595 } else if (thinkOutput[0] != NULLCHAR &&
8596 strncmp(message, " ", 4) == 0) {
8597 unsigned message_len;
8600 while (*p && *p == ' ') p++;
8602 message_len = strlen( p );
8604 /* [AS] Avoid buffer overflow */
8605 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8606 strcat(thinkOutput, " ");
8607 strcat(thinkOutput, p);
8610 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8611 strcat(programStats.movelist, " ");
8612 strcat(programStats.movelist, p);
8615 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8616 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8617 DisplayMove(currentMove - 1);
8625 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8626 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8628 ChessProgramStats cpstats;
8630 if (plyext != ' ' && plyext != '\t') {
8634 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8635 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8636 curscore = -curscore;
8639 cpstats.depth = plylev;
8640 cpstats.nodes = nodes;
8641 cpstats.time = time;
8642 cpstats.score = curscore;
8643 cpstats.got_only_move = 0;
8644 cpstats.movelist[0] = '\0';
8646 if (buf1[0] != NULLCHAR) {
8647 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8650 cpstats.ok_to_send = 0;
8651 cpstats.line_is_book = 0;
8652 cpstats.nr_moves = 0;
8653 cpstats.moves_left = 0;
8655 SendProgramStatsToFrontend( cps, &cpstats );
8662 /* Parse a game score from the character string "game", and
8663 record it as the history of the current game. The game
8664 score is NOT assumed to start from the standard position.
8665 The display is not updated in any way.
8668 ParseGameHistory(game)
8672 int fromX, fromY, toX, toY, boardIndex;
8677 if (appData.debugMode)
8678 fprintf(debugFP, "Parsing game history: %s\n", game);
8680 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8681 gameInfo.site = StrSave(appData.icsHost);
8682 gameInfo.date = PGNDate();
8683 gameInfo.round = StrSave("-");
8685 /* Parse out names of players */
8686 while (*game == ' ') game++;
8688 while (*game != ' ') *p++ = *game++;
8690 gameInfo.white = StrSave(buf);
8691 while (*game == ' ') game++;
8693 while (*game != ' ' && *game != '\n') *p++ = *game++;
8695 gameInfo.black = StrSave(buf);
8698 boardIndex = blackPlaysFirst ? 1 : 0;
8701 yyboardindex = boardIndex;
8702 moveType = (ChessMove) Myylex();
8704 case IllegalMove: /* maybe suicide chess, etc. */
8705 if (appData.debugMode) {
8706 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8707 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8708 setbuf(debugFP, NULL);
8710 case WhitePromotion:
8711 case BlackPromotion:
8712 case WhiteNonPromotion:
8713 case BlackNonPromotion:
8715 case WhiteCapturesEnPassant:
8716 case BlackCapturesEnPassant:
8717 case WhiteKingSideCastle:
8718 case WhiteQueenSideCastle:
8719 case BlackKingSideCastle:
8720 case BlackQueenSideCastle:
8721 case WhiteKingSideCastleWild:
8722 case WhiteQueenSideCastleWild:
8723 case BlackKingSideCastleWild:
8724 case BlackQueenSideCastleWild:
8726 case WhiteHSideCastleFR:
8727 case WhiteASideCastleFR:
8728 case BlackHSideCastleFR:
8729 case BlackASideCastleFR:
8731 fromX = currentMoveString[0] - AAA;
8732 fromY = currentMoveString[1] - ONE;
8733 toX = currentMoveString[2] - AAA;
8734 toY = currentMoveString[3] - ONE;
8735 promoChar = currentMoveString[4];
8739 fromX = moveType == WhiteDrop ?
8740 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8741 (int) CharToPiece(ToLower(currentMoveString[0]));
8743 toX = currentMoveString[2] - AAA;
8744 toY = currentMoveString[3] - ONE;
8745 promoChar = NULLCHAR;
8749 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8750 if (appData.debugMode) {
8751 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8752 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8753 setbuf(debugFP, NULL);
8755 DisplayError(buf, 0);
8757 case ImpossibleMove:
8759 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8760 if (appData.debugMode) {
8761 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8762 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8763 setbuf(debugFP, NULL);
8765 DisplayError(buf, 0);
8768 if (boardIndex < backwardMostMove) {
8769 /* Oops, gap. How did that happen? */
8770 DisplayError(_("Gap in move list"), 0);
8773 backwardMostMove = blackPlaysFirst ? 1 : 0;
8774 if (boardIndex > forwardMostMove) {
8775 forwardMostMove = boardIndex;
8779 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8780 strcat(parseList[boardIndex-1], " ");
8781 strcat(parseList[boardIndex-1], yy_text);
8793 case GameUnfinished:
8794 if (gameMode == IcsExamining) {
8795 if (boardIndex < backwardMostMove) {
8796 /* Oops, gap. How did that happen? */
8799 backwardMostMove = blackPlaysFirst ? 1 : 0;
8802 gameInfo.result = moveType;
8803 p = strchr(yy_text, '{');
8804 if (p == NULL) p = strchr(yy_text, '(');
8807 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8809 q = strchr(p, *p == '{' ? '}' : ')');
8810 if (q != NULL) *q = NULLCHAR;
8813 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8814 gameInfo.resultDetails = StrSave(p);
8817 if (boardIndex >= forwardMostMove &&
8818 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8819 backwardMostMove = blackPlaysFirst ? 1 : 0;
8822 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8823 fromY, fromX, toY, toX, promoChar,
8824 parseList[boardIndex]);
8825 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8826 /* currentMoveString is set as a side-effect of yylex */
8827 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8828 strcat(moveList[boardIndex], "\n");
8830 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8831 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8837 if(gameInfo.variant != VariantShogi)
8838 strcat(parseList[boardIndex - 1], "+");
8842 strcat(parseList[boardIndex - 1], "#");
8849 /* Apply a move to the given board */
8851 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8852 int fromX, fromY, toX, toY;
8856 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8857 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8859 /* [HGM] compute & store e.p. status and castling rights for new position */
8860 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8862 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8863 oldEP = (signed char)board[EP_STATUS];
8864 board[EP_STATUS] = EP_NONE;
8866 if( board[toY][toX] != EmptySquare )
8867 board[EP_STATUS] = EP_CAPTURE;
8869 if (fromY == DROP_RANK) {
8871 piece = board[toY][toX] = (ChessSquare) fromX;
8875 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8876 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8877 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8879 if( board[fromY][fromX] == WhitePawn ) {
8880 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8881 board[EP_STATUS] = EP_PAWN_MOVE;
8883 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8884 gameInfo.variant != VariantBerolina || toX < fromX)
8885 board[EP_STATUS] = toX | berolina;
8886 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8887 gameInfo.variant != VariantBerolina || toX > fromX)
8888 board[EP_STATUS] = toX;
8891 if( board[fromY][fromX] == BlackPawn ) {
8892 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8893 board[EP_STATUS] = EP_PAWN_MOVE;
8894 if( toY-fromY== -2) {
8895 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8896 gameInfo.variant != VariantBerolina || toX < fromX)
8897 board[EP_STATUS] = toX | berolina;
8898 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8899 gameInfo.variant != VariantBerolina || toX > fromX)
8900 board[EP_STATUS] = toX;
8904 for(i=0; i<nrCastlingRights; i++) {
8905 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8906 board[CASTLING][i] == toX && castlingRank[i] == toY
8907 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8910 if (fromX == toX && fromY == toY) return;
8912 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8913 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8914 if(gameInfo.variant == VariantKnightmate)
8915 king += (int) WhiteUnicorn - (int) WhiteKing;
8917 /* Code added by Tord: */
8918 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8919 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8920 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8921 board[fromY][fromX] = EmptySquare;
8922 board[toY][toX] = EmptySquare;
8923 if((toX > fromX) != (piece == WhiteRook)) {
8924 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8926 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8928 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8929 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8930 board[fromY][fromX] = EmptySquare;
8931 board[toY][toX] = EmptySquare;
8932 if((toX > fromX) != (piece == BlackRook)) {
8933 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8935 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8937 /* End of code added by Tord */
8939 } else if (board[fromY][fromX] == king
8940 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8941 && toY == fromY && toX > fromX+1) {
8942 board[fromY][fromX] = EmptySquare;
8943 board[toY][toX] = king;
8944 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8945 board[fromY][BOARD_RGHT-1] = EmptySquare;
8946 } else if (board[fromY][fromX] == king
8947 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8948 && toY == fromY && toX < fromX-1) {
8949 board[fromY][fromX] = EmptySquare;
8950 board[toY][toX] = king;
8951 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8952 board[fromY][BOARD_LEFT] = EmptySquare;
8953 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8954 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8955 && toY >= BOARD_HEIGHT-promoRank
8957 /* white pawn promotion */
8958 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8959 if (board[toY][toX] == EmptySquare) {
8960 board[toY][toX] = WhiteQueen;
8962 if(gameInfo.variant==VariantBughouse ||
8963 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8964 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8965 board[fromY][fromX] = EmptySquare;
8966 } else if ((fromY == BOARD_HEIGHT-4)
8968 && gameInfo.variant != VariantXiangqi
8969 && gameInfo.variant != VariantBerolina
8970 && (board[fromY][fromX] == WhitePawn)
8971 && (board[toY][toX] == EmptySquare)) {
8972 board[fromY][fromX] = EmptySquare;
8973 board[toY][toX] = WhitePawn;
8974 captured = board[toY - 1][toX];
8975 board[toY - 1][toX] = EmptySquare;
8976 } else if ((fromY == BOARD_HEIGHT-4)
8978 && gameInfo.variant == VariantBerolina
8979 && (board[fromY][fromX] == WhitePawn)
8980 && (board[toY][toX] == EmptySquare)) {
8981 board[fromY][fromX] = EmptySquare;
8982 board[toY][toX] = WhitePawn;
8983 if(oldEP & EP_BEROLIN_A) {
8984 captured = board[fromY][fromX-1];
8985 board[fromY][fromX-1] = EmptySquare;
8986 }else{ captured = board[fromY][fromX+1];
8987 board[fromY][fromX+1] = EmptySquare;
8989 } else if (board[fromY][fromX] == king
8990 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8991 && toY == fromY && toX > fromX+1) {
8992 board[fromY][fromX] = EmptySquare;
8993 board[toY][toX] = king;
8994 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8995 board[fromY][BOARD_RGHT-1] = EmptySquare;
8996 } else if (board[fromY][fromX] == king
8997 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8998 && toY == fromY && toX < fromX-1) {
8999 board[fromY][fromX] = EmptySquare;
9000 board[toY][toX] = king;
9001 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9002 board[fromY][BOARD_LEFT] = EmptySquare;
9003 } else if (fromY == 7 && fromX == 3
9004 && board[fromY][fromX] == BlackKing
9005 && toY == 7 && toX == 5) {
9006 board[fromY][fromX] = EmptySquare;
9007 board[toY][toX] = BlackKing;
9008 board[fromY][7] = EmptySquare;
9009 board[toY][4] = BlackRook;
9010 } else if (fromY == 7 && fromX == 3
9011 && board[fromY][fromX] == BlackKing
9012 && toY == 7 && toX == 1) {
9013 board[fromY][fromX] = EmptySquare;
9014 board[toY][toX] = BlackKing;
9015 board[fromY][0] = EmptySquare;
9016 board[toY][2] = BlackRook;
9017 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9018 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9021 /* black pawn promotion */
9022 board[toY][toX] = CharToPiece(ToLower(promoChar));
9023 if (board[toY][toX] == EmptySquare) {
9024 board[toY][toX] = BlackQueen;
9026 if(gameInfo.variant==VariantBughouse ||
9027 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9028 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9029 board[fromY][fromX] = EmptySquare;
9030 } else if ((fromY == 3)
9032 && gameInfo.variant != VariantXiangqi
9033 && gameInfo.variant != VariantBerolina
9034 && (board[fromY][fromX] == BlackPawn)
9035 && (board[toY][toX] == EmptySquare)) {
9036 board[fromY][fromX] = EmptySquare;
9037 board[toY][toX] = BlackPawn;
9038 captured = board[toY + 1][toX];
9039 board[toY + 1][toX] = EmptySquare;
9040 } else if ((fromY == 3)
9042 && gameInfo.variant == VariantBerolina
9043 && (board[fromY][fromX] == BlackPawn)
9044 && (board[toY][toX] == EmptySquare)) {
9045 board[fromY][fromX] = EmptySquare;
9046 board[toY][toX] = BlackPawn;
9047 if(oldEP & EP_BEROLIN_A) {
9048 captured = board[fromY][fromX-1];
9049 board[fromY][fromX-1] = EmptySquare;
9050 }else{ captured = board[fromY][fromX+1];
9051 board[fromY][fromX+1] = EmptySquare;
9054 board[toY][toX] = board[fromY][fromX];
9055 board[fromY][fromX] = EmptySquare;
9059 if (gameInfo.holdingsWidth != 0) {
9061 /* !!A lot more code needs to be written to support holdings */
9062 /* [HGM] OK, so I have written it. Holdings are stored in the */
9063 /* penultimate board files, so they are automaticlly stored */
9064 /* in the game history. */
9065 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9066 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9067 /* Delete from holdings, by decreasing count */
9068 /* and erasing image if necessary */
9069 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9070 if(p < (int) BlackPawn) { /* white drop */
9071 p -= (int)WhitePawn;
9072 p = PieceToNumber((ChessSquare)p);
9073 if(p >= gameInfo.holdingsSize) p = 0;
9074 if(--board[p][BOARD_WIDTH-2] <= 0)
9075 board[p][BOARD_WIDTH-1] = EmptySquare;
9076 if((int)board[p][BOARD_WIDTH-2] < 0)
9077 board[p][BOARD_WIDTH-2] = 0;
9078 } else { /* black drop */
9079 p -= (int)BlackPawn;
9080 p = PieceToNumber((ChessSquare)p);
9081 if(p >= gameInfo.holdingsSize) p = 0;
9082 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9083 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9084 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9085 board[BOARD_HEIGHT-1-p][1] = 0;
9088 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9089 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9090 /* [HGM] holdings: Add to holdings, if holdings exist */
9091 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9092 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9093 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9096 if (p >= (int) BlackPawn) {
9097 p -= (int)BlackPawn;
9098 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9099 /* in Shogi restore piece to its original first */
9100 captured = (ChessSquare) (DEMOTED captured);
9103 p = PieceToNumber((ChessSquare)p);
9104 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9105 board[p][BOARD_WIDTH-2]++;
9106 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9108 p -= (int)WhitePawn;
9109 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9110 captured = (ChessSquare) (DEMOTED captured);
9113 p = PieceToNumber((ChessSquare)p);
9114 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9115 board[BOARD_HEIGHT-1-p][1]++;
9116 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9119 } else if (gameInfo.variant == VariantAtomic) {
9120 if (captured != EmptySquare) {
9122 for (y = toY-1; y <= toY+1; y++) {
9123 for (x = toX-1; x <= toX+1; x++) {
9124 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9125 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9126 board[y][x] = EmptySquare;
9130 board[toY][toX] = EmptySquare;
9133 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9134 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9136 if(promoChar == '+') {
9137 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9138 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9139 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9140 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9142 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9143 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9144 // [HGM] superchess: take promotion piece out of holdings
9145 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9146 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9147 if(!--board[k][BOARD_WIDTH-2])
9148 board[k][BOARD_WIDTH-1] = EmptySquare;
9150 if(!--board[BOARD_HEIGHT-1-k][1])
9151 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9157 /* Updates forwardMostMove */
9159 MakeMove(fromX, fromY, toX, toY, promoChar)
9160 int fromX, fromY, toX, toY;
9163 // forwardMostMove++; // [HGM] bare: moved downstream
9165 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9166 int timeLeft; static int lastLoadFlag=0; int king, piece;
9167 piece = boards[forwardMostMove][fromY][fromX];
9168 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9169 if(gameInfo.variant == VariantKnightmate)
9170 king += (int) WhiteUnicorn - (int) WhiteKing;
9171 if(forwardMostMove == 0) {
9173 fprintf(serverMoves, "%s;", second.tidy);
9174 fprintf(serverMoves, "%s;", first.tidy);
9175 if(!blackPlaysFirst)
9176 fprintf(serverMoves, "%s;", second.tidy);
9177 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9178 lastLoadFlag = loadFlag;
9180 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9181 // print castling suffix
9182 if( toY == fromY && piece == king ) {
9184 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9186 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9189 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9190 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9191 boards[forwardMostMove][toY][toX] == EmptySquare
9192 && fromX != toX && fromY != toY)
9193 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9195 if(promoChar != NULLCHAR)
9196 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9198 fprintf(serverMoves, "/%d/%d",
9199 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9200 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9201 else timeLeft = blackTimeRemaining/1000;
9202 fprintf(serverMoves, "/%d", timeLeft);
9204 fflush(serverMoves);
9207 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9208 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9212 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9213 if (commentList[forwardMostMove+1] != NULL) {
9214 free(commentList[forwardMostMove+1]);
9215 commentList[forwardMostMove+1] = NULL;
9217 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9218 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9219 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9220 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9221 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9222 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9223 gameInfo.result = GameUnfinished;
9224 if (gameInfo.resultDetails != NULL) {
9225 free(gameInfo.resultDetails);
9226 gameInfo.resultDetails = NULL;
9228 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9229 moveList[forwardMostMove - 1]);
9230 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9231 PosFlags(forwardMostMove - 1),
9232 fromY, fromX, toY, toX, promoChar,
9233 parseList[forwardMostMove - 1]);
9234 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9240 if(gameInfo.variant != VariantShogi)
9241 strcat(parseList[forwardMostMove - 1], "+");
9245 strcat(parseList[forwardMostMove - 1], "#");
9248 if (appData.debugMode) {
9249 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9254 /* Updates currentMove if not pausing */
9256 ShowMove(fromX, fromY, toX, toY)
9258 int instant = (gameMode == PlayFromGameFile) ?
9259 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9260 if(appData.noGUI) return;
9261 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9263 if (forwardMostMove == currentMove + 1) {
9264 AnimateMove(boards[forwardMostMove - 1],
9265 fromX, fromY, toX, toY);
9267 if (appData.highlightLastMove) {
9268 SetHighlights(fromX, fromY, toX, toY);
9271 currentMove = forwardMostMove;
9274 if (instant) return;
9276 DisplayMove(currentMove - 1);
9277 DrawPosition(FALSE, boards[currentMove]);
9278 DisplayBothClocks();
9279 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9280 DisplayBook(currentMove);
9283 void SendEgtPath(ChessProgramState *cps)
9284 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9285 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9287 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9290 char c, *q = name+1, *r, *s;
9292 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9293 while(*p && *p != ',') *q++ = *p++;
9295 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9296 strcmp(name, ",nalimov:") == 0 ) {
9297 // take nalimov path from the menu-changeable option first, if it is defined
9298 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9299 SendToProgram(buf,cps); // send egtbpath command for nalimov
9301 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9302 (s = StrStr(appData.egtFormats, name)) != NULL) {
9303 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9304 s = r = StrStr(s, ":") + 1; // beginning of path info
9305 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9306 c = *r; *r = 0; // temporarily null-terminate path info
9307 *--q = 0; // strip of trailig ':' from name
9308 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9310 SendToProgram(buf,cps); // send egtbpath command for this format
9312 if(*p == ',') p++; // read away comma to position for next format name
9317 InitChessProgram(cps, setup)
9318 ChessProgramState *cps;
9319 int setup; /* [HGM] needed to setup FRC opening position */
9321 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9322 if (appData.noChessProgram) return;
9323 hintRequested = FALSE;
9324 bookRequested = FALSE;
9326 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9327 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9328 if(cps->memSize) { /* [HGM] memory */
9329 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9330 SendToProgram(buf, cps);
9332 SendEgtPath(cps); /* [HGM] EGT */
9333 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9334 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9335 SendToProgram(buf, cps);
9338 SendToProgram(cps->initString, cps);
9339 if (gameInfo.variant != VariantNormal &&
9340 gameInfo.variant != VariantLoadable
9341 /* [HGM] also send variant if board size non-standard */
9342 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9344 char *v = VariantName(gameInfo.variant);
9345 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9346 /* [HGM] in protocol 1 we have to assume all variants valid */
9347 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9348 DisplayFatalError(buf, 0, 1);
9352 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9353 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9354 if( gameInfo.variant == VariantXiangqi )
9355 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9356 if( gameInfo.variant == VariantShogi )
9357 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9358 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9359 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9360 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9361 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9362 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9363 if( gameInfo.variant == VariantCourier )
9364 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9365 if( gameInfo.variant == VariantSuper )
9366 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9367 if( gameInfo.variant == VariantGreat )
9368 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9369 if( gameInfo.variant == VariantSChess )
9370 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9373 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9374 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9375 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9376 if(StrStr(cps->variants, b) == NULL) {
9377 // specific sized variant not known, check if general sizing allowed
9378 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9379 if(StrStr(cps->variants, "boardsize") == NULL) {
9380 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9381 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9382 DisplayFatalError(buf, 0, 1);
9385 /* [HGM] here we really should compare with the maximum supported board size */
9388 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9389 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9390 SendToProgram(buf, cps);
9392 currentlyInitializedVariant = gameInfo.variant;
9394 /* [HGM] send opening position in FRC to first engine */
9396 SendToProgram("force\n", cps);
9398 /* engine is now in force mode! Set flag to wake it up after first move. */
9399 setboardSpoiledMachineBlack = 1;
9403 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9404 SendToProgram(buf, cps);
9406 cps->maybeThinking = FALSE;
9407 cps->offeredDraw = 0;
9408 if (!appData.icsActive) {
9409 SendTimeControl(cps, movesPerSession, timeControl,
9410 timeIncrement, appData.searchDepth,
9413 if (appData.showThinking
9414 // [HGM] thinking: four options require thinking output to be sent
9415 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9417 SendToProgram("post\n", cps);
9419 SendToProgram("hard\n", cps);
9420 if (!appData.ponderNextMove) {
9421 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9422 it without being sure what state we are in first. "hard"
9423 is not a toggle, so that one is OK.
9425 SendToProgram("easy\n", cps);
9428 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9429 SendToProgram(buf, cps);
9431 cps->initDone = TRUE;
9436 StartChessProgram(cps)
9437 ChessProgramState *cps;
9442 if (appData.noChessProgram) return;
9443 cps->initDone = FALSE;
9445 if (strcmp(cps->host, "localhost") == 0) {
9446 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9447 } else if (*appData.remoteShell == NULLCHAR) {
9448 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9450 if (*appData.remoteUser == NULLCHAR) {
9451 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9454 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9455 cps->host, appData.remoteUser, cps->program);
9457 err = StartChildProcess(buf, "", &cps->pr);
9461 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9462 DisplayFatalError(buf, err, 1);
9468 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9469 if (cps->protocolVersion > 1) {
9470 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9471 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9472 cps->comboCnt = 0; // and values of combo boxes
9473 SendToProgram(buf, cps);
9475 SendToProgram("xboard\n", cps);
9480 TwoMachinesEventIfReady P((void))
9482 static int curMess = 0;
9483 if (first.lastPing != first.lastPong) {
9484 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9485 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9488 if (second.lastPing != second.lastPong) {
9489 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9490 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9493 DisplayMessage("", ""); curMess = 0;
9499 CreateTourney(char *name)
9502 if(name[0] == NULLCHAR) return 0;
9503 f = fopen(appData.tourneyFile, "r");
9504 if(f) { // file exists
9505 ParseArgsFromFile(f); // parse it
9507 f = fopen(appData.tourneyFile, "w");
9508 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9509 // create a file with tournament description
9510 fprintf(f, "-participants {%s}\n", appData.participants);
9511 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9512 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9513 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9514 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9515 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9516 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9517 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9518 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9519 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9520 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9521 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9523 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9525 fprintf(f, "-mps %d\n", appData.movesPerSession);
9526 fprintf(f, "-tc %s\n", appData.timeControl);
9527 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9529 fprintf(f, "-results \"\"\n");
9533 appData.noChessProgram = FALSE;
9534 appData.clockMode = TRUE;
9539 #define MAXENGINES 1000
9540 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9542 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9544 char buf[MSG_SIZ], *p, *q;
9548 while(*p && *p != '\n') *q++ = *p++;
9550 if(engineList[i]) free(engineList[i]);
9551 engineList[i] = strdup(buf);
9553 TidyProgramName(engineList[i], "localhost", buf);
9554 if(engineMnemonic[i]) free(engineMnemonic[i]);
9555 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9557 sscanf(q + 8, "%s", buf + strlen(buf));
9560 engineMnemonic[i] = strdup(buf);
9562 if(i > MAXENGINES - 2) break;
9564 engineList[i] = NULL;
9567 // following implemented as macro to avoid type limitations
9568 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9570 void SwapEngines(int n)
9571 { // swap settings for first engine and other engine (so far only some selected options)
9576 SWAP(chessProgram, p)
9578 SWAP(hasOwnBookUCI, h)
9579 SWAP(protocolVersion, h)
9581 SWAP(scoreIsAbsolute, h)
9588 SetPlayer(int player)
9589 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9591 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9592 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9593 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9594 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9596 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9597 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9598 ParseArgsFromString(buf);
9604 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9605 { // determine players from game number
9606 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9608 if(appData.tourneyType == 0) {
9609 roundsPerCycle = (nPlayers - 1) | 1;
9610 pairingsPerRound = nPlayers / 2;
9611 } else if(appData.tourneyType > 0) {
9612 roundsPerCycle = nPlayers - appData.tourneyType;
9613 pairingsPerRound = appData.tourneyType;
9615 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9616 gamesPerCycle = gamesPerRound * roundsPerCycle;
9617 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9618 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9619 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9620 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9621 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9622 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9624 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9625 if(appData.roundSync) *syncInterval = gamesPerRound;
9627 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9629 if(appData.tourneyType == 0) {
9630 if(curPairing == (nPlayers-1)/2 ) {
9631 *whitePlayer = curRound;
9632 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9634 *whitePlayer = curRound - pairingsPerRound + curPairing;
9635 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9636 *blackPlayer = curRound + pairingsPerRound - curPairing;
9637 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9639 } else if(appData.tourneyType > 0) {
9640 *whitePlayer = curPairing;
9641 *blackPlayer = curRound + appData.tourneyType;
9644 // take care of white/black alternation per round.
9645 // For cycles and games this is already taken care of by default, derived from matchGame!
9646 return curRound & 1;
9650 NextTourneyGame(int nr, int *swapColors)
9651 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9653 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9655 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9656 tf = fopen(appData.tourneyFile, "r");
9657 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9658 ParseArgsFromFile(tf); fclose(tf);
9659 InitTimeControls(); // TC might be altered from tourney file
9661 p = appData.participants;
9662 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9663 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9666 p = q = appData.results;
9667 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9668 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9669 DisplayMessage(_("Waiting for other game(s)"),"");
9670 waitingForGame = TRUE;
9671 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9674 waitingForGame = FALSE;
9677 if(first.pr != NoProc) return 1; // engines already loaded
9679 // redefine engines, engine dir, etc.
9680 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9681 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9683 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9684 SwapEngines(1); // and make that valid for second engine by swapping
9685 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9686 InitEngine(&second, 1);
9687 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9693 { // performs game initialization that does not invoke engines, and then tries to start the game
9694 int firstWhite, swapColors = 0;
9695 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9696 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9697 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9698 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9699 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9700 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9701 Reset(FALSE, first.pr != NoProc);
9702 appData.noChessProgram = FALSE;
9703 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9707 void UserAdjudicationEvent( int result )
9709 ChessMove gameResult = GameIsDrawn;
9712 gameResult = WhiteWins;
9714 else if( result < 0 ) {
9715 gameResult = BlackWins;
9718 if( gameMode == TwoMachinesPlay ) {
9719 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9724 // [HGM] save: calculate checksum of game to make games easily identifiable
9725 int StringCheckSum(char *s)
9728 if(s==NULL) return 0;
9729 while(*s) i = i*259 + *s++;
9736 for(i=backwardMostMove; i<forwardMostMove; i++) {
9737 sum += pvInfoList[i].depth;
9738 sum += StringCheckSum(parseList[i]);
9739 sum += StringCheckSum(commentList[i]);
9742 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9743 return sum + StringCheckSum(commentList[i]);
9744 } // end of save patch
9747 GameEnds(result, resultDetails, whosays)
9749 char *resultDetails;
9752 GameMode nextGameMode;
9754 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9756 if(endingGame) return; /* [HGM] crash: forbid recursion */
9758 if(twoBoards) { // [HGM] dual: switch back to one board
9759 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9760 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9762 if (appData.debugMode) {
9763 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9764 result, resultDetails ? resultDetails : "(null)", whosays);
9767 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9769 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9770 /* If we are playing on ICS, the server decides when the
9771 game is over, but the engine can offer to draw, claim
9775 if (appData.zippyPlay && first.initDone) {
9776 if (result == GameIsDrawn) {
9777 /* In case draw still needs to be claimed */
9778 SendToICS(ics_prefix);
9779 SendToICS("draw\n");
9780 } else if (StrCaseStr(resultDetails, "resign")) {
9781 SendToICS(ics_prefix);
9782 SendToICS("resign\n");
9786 endingGame = 0; /* [HGM] crash */
9790 /* If we're loading the game from a file, stop */
9791 if (whosays == GE_FILE) {
9792 (void) StopLoadGameTimer();
9796 /* Cancel draw offers */
9797 first.offeredDraw = second.offeredDraw = 0;
9799 /* If this is an ICS game, only ICS can really say it's done;
9800 if not, anyone can. */
9801 isIcsGame = (gameMode == IcsPlayingWhite ||
9802 gameMode == IcsPlayingBlack ||
9803 gameMode == IcsObserving ||
9804 gameMode == IcsExamining);
9806 if (!isIcsGame || whosays == GE_ICS) {
9807 /* OK -- not an ICS game, or ICS said it was done */
9809 if (!isIcsGame && !appData.noChessProgram)
9810 SetUserThinkingEnables();
9812 /* [HGM] if a machine claims the game end we verify this claim */
9813 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9814 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9816 ChessMove trueResult = (ChessMove) -1;
9818 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9819 first.twoMachinesColor[0] :
9820 second.twoMachinesColor[0] ;
9822 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9823 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9824 /* [HGM] verify: engine mate claims accepted if they were flagged */
9825 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9827 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9828 /* [HGM] verify: engine mate claims accepted if they were flagged */
9829 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9831 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9832 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9835 // now verify win claims, but not in drop games, as we don't understand those yet
9836 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9837 || gameInfo.variant == VariantGreat) &&
9838 (result == WhiteWins && claimer == 'w' ||
9839 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9840 if (appData.debugMode) {
9841 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9842 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9844 if(result != trueResult) {
9845 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9846 result = claimer == 'w' ? BlackWins : WhiteWins;
9847 resultDetails = buf;
9850 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9851 && (forwardMostMove <= backwardMostMove ||
9852 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9853 (claimer=='b')==(forwardMostMove&1))
9855 /* [HGM] verify: draws that were not flagged are false claims */
9856 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9857 result = claimer == 'w' ? BlackWins : WhiteWins;
9858 resultDetails = buf;
9860 /* (Claiming a loss is accepted no questions asked!) */
9862 /* [HGM] bare: don't allow bare King to win */
9863 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9864 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9865 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9866 && result != GameIsDrawn)
9867 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9868 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9869 int p = (signed char)boards[forwardMostMove][i][j] - color;
9870 if(p >= 0 && p <= (int)WhiteKing) k++;
9872 if (appData.debugMode) {
9873 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9874 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9877 result = GameIsDrawn;
9878 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9879 resultDetails = buf;
9885 if(serverMoves != NULL && !loadFlag) { char c = '=';
9886 if(result==WhiteWins) c = '+';
9887 if(result==BlackWins) c = '-';
9888 if(resultDetails != NULL)
9889 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9891 if (resultDetails != NULL) {
9892 gameInfo.result = result;
9893 gameInfo.resultDetails = StrSave(resultDetails);
9895 /* display last move only if game was not loaded from file */
9896 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9897 DisplayMove(currentMove - 1);
9899 if (forwardMostMove != 0) {
9900 if (gameMode != PlayFromGameFile && gameMode != EditGame
9901 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9903 if (*appData.saveGameFile != NULLCHAR) {
9904 SaveGameToFile(appData.saveGameFile, TRUE);
9905 } else if (appData.autoSaveGames) {
9908 if (*appData.savePositionFile != NULLCHAR) {
9909 SavePositionToFile(appData.savePositionFile);
9914 /* Tell program how game ended in case it is learning */
9915 /* [HGM] Moved this to after saving the PGN, just in case */
9916 /* engine died and we got here through time loss. In that */
9917 /* case we will get a fatal error writing the pipe, which */
9918 /* would otherwise lose us the PGN. */
9919 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9920 /* output during GameEnds should never be fatal anymore */
9921 if (gameMode == MachinePlaysWhite ||
9922 gameMode == MachinePlaysBlack ||
9923 gameMode == TwoMachinesPlay ||
9924 gameMode == IcsPlayingWhite ||
9925 gameMode == IcsPlayingBlack ||
9926 gameMode == BeginningOfGame) {
9928 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9930 if (first.pr != NoProc) {
9931 SendToProgram(buf, &first);
9933 if (second.pr != NoProc &&
9934 gameMode == TwoMachinesPlay) {
9935 SendToProgram(buf, &second);
9940 if (appData.icsActive) {
9941 if (appData.quietPlay &&
9942 (gameMode == IcsPlayingWhite ||
9943 gameMode == IcsPlayingBlack)) {
9944 SendToICS(ics_prefix);
9945 SendToICS("set shout 1\n");
9947 nextGameMode = IcsIdle;
9948 ics_user_moved = FALSE;
9949 /* clean up premove. It's ugly when the game has ended and the
9950 * premove highlights are still on the board.
9954 ClearPremoveHighlights();
9955 DrawPosition(FALSE, boards[currentMove]);
9957 if (whosays == GE_ICS) {
9960 if (gameMode == IcsPlayingWhite)
9962 else if(gameMode == IcsPlayingBlack)
9966 if (gameMode == IcsPlayingBlack)
9968 else if(gameMode == IcsPlayingWhite)
9975 PlayIcsUnfinishedSound();
9978 } else if (gameMode == EditGame ||
9979 gameMode == PlayFromGameFile ||
9980 gameMode == AnalyzeMode ||
9981 gameMode == AnalyzeFile) {
9982 nextGameMode = gameMode;
9984 nextGameMode = EndOfGame;
9989 nextGameMode = gameMode;
9992 if (appData.noChessProgram) {
9993 gameMode = nextGameMode;
9995 endingGame = 0; /* [HGM] crash */
10000 /* Put first chess program into idle state */
10001 if (first.pr != NoProc &&
10002 (gameMode == MachinePlaysWhite ||
10003 gameMode == MachinePlaysBlack ||
10004 gameMode == TwoMachinesPlay ||
10005 gameMode == IcsPlayingWhite ||
10006 gameMode == IcsPlayingBlack ||
10007 gameMode == BeginningOfGame)) {
10008 SendToProgram("force\n", &first);
10009 if (first.usePing) {
10011 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10012 SendToProgram(buf, &first);
10015 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10016 /* Kill off first chess program */
10017 if (first.isr != NULL)
10018 RemoveInputSource(first.isr);
10021 if (first.pr != NoProc) {
10023 DoSleep( appData.delayBeforeQuit );
10024 SendToProgram("quit\n", &first);
10025 DoSleep( appData.delayAfterQuit );
10026 DestroyChildProcess(first.pr, first.useSigterm);
10030 if (second.reuse) {
10031 /* Put second chess program into idle state */
10032 if (second.pr != NoProc &&
10033 gameMode == TwoMachinesPlay) {
10034 SendToProgram("force\n", &second);
10035 if (second.usePing) {
10037 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10038 SendToProgram(buf, &second);
10041 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10042 /* Kill off second chess program */
10043 if (second.isr != NULL)
10044 RemoveInputSource(second.isr);
10047 if (second.pr != NoProc) {
10048 DoSleep( appData.delayBeforeQuit );
10049 SendToProgram("quit\n", &second);
10050 DoSleep( appData.delayAfterQuit );
10051 DestroyChildProcess(second.pr, second.useSigterm);
10053 second.pr = NoProc;
10056 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10057 char resChar = '=';
10061 if (first.twoMachinesColor[0] == 'w') {
10064 second.matchWins++;
10069 if (first.twoMachinesColor[0] == 'b') {
10072 second.matchWins++;
10075 case GameUnfinished:
10081 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10082 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10083 ReserveGame(nextGame, resChar); // sets nextGame
10084 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10085 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10087 if (nextGame <= appData.matchGames && !abortMatch) {
10088 gameMode = nextGameMode;
10089 matchGame = nextGame; // this will be overruled in tourney mode!
10090 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10091 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10092 endingGame = 0; /* [HGM] crash */
10095 gameMode = nextGameMode;
10096 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10097 first.tidy, second.tidy,
10098 first.matchWins, second.matchWins,
10099 appData.matchGames - (first.matchWins + second.matchWins));
10100 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10101 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10102 first.twoMachinesColor = "black\n";
10103 second.twoMachinesColor = "white\n";
10105 first.twoMachinesColor = "white\n";
10106 second.twoMachinesColor = "black\n";
10110 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10111 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10113 gameMode = nextGameMode;
10115 endingGame = 0; /* [HGM] crash */
10116 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10117 if(matchMode == TRUE) { // match through command line: exit with or without popup
10119 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10121 } else DisplayFatalError(buf, 0, 0);
10122 } else { // match through menu; just stop, with or without popup
10123 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10125 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10126 } else DisplayNote(buf);
10128 if(ranking) free(ranking);
10132 /* Assumes program was just initialized (initString sent).
10133 Leaves program in force mode. */
10135 FeedMovesToProgram(cps, upto)
10136 ChessProgramState *cps;
10141 if (appData.debugMode)
10142 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10143 startedFromSetupPosition ? "position and " : "",
10144 backwardMostMove, upto, cps->which);
10145 if(currentlyInitializedVariant != gameInfo.variant) {
10147 // [HGM] variantswitch: make engine aware of new variant
10148 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10149 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10150 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10151 SendToProgram(buf, cps);
10152 currentlyInitializedVariant = gameInfo.variant;
10154 SendToProgram("force\n", cps);
10155 if (startedFromSetupPosition) {
10156 SendBoard(cps, backwardMostMove);
10157 if (appData.debugMode) {
10158 fprintf(debugFP, "feedMoves\n");
10161 for (i = backwardMostMove; i < upto; i++) {
10162 SendMoveToProgram(i, cps);
10168 ResurrectChessProgram()
10170 /* The chess program may have exited.
10171 If so, restart it and feed it all the moves made so far. */
10172 static int doInit = 0;
10174 if (appData.noChessProgram) return 1;
10176 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10177 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10178 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10179 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10181 if (first.pr != NoProc) return 1;
10182 StartChessProgram(&first);
10184 InitChessProgram(&first, FALSE);
10185 FeedMovesToProgram(&first, currentMove);
10187 if (!first.sendTime) {
10188 /* can't tell gnuchess what its clock should read,
10189 so we bow to its notion. */
10191 timeRemaining[0][currentMove] = whiteTimeRemaining;
10192 timeRemaining[1][currentMove] = blackTimeRemaining;
10195 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10196 appData.icsEngineAnalyze) && first.analysisSupport) {
10197 SendToProgram("analyze\n", &first);
10198 first.analyzing = TRUE;
10204 * Button procedures
10207 Reset(redraw, init)
10212 if (appData.debugMode) {
10213 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10214 redraw, init, gameMode);
10216 CleanupTail(); // [HGM] vari: delete any stored variations
10217 pausing = pauseExamInvalid = FALSE;
10218 startedFromSetupPosition = blackPlaysFirst = FALSE;
10220 whiteFlag = blackFlag = FALSE;
10221 userOfferedDraw = FALSE;
10222 hintRequested = bookRequested = FALSE;
10223 first.maybeThinking = FALSE;
10224 second.maybeThinking = FALSE;
10225 first.bookSuspend = FALSE; // [HGM] book
10226 second.bookSuspend = FALSE;
10227 thinkOutput[0] = NULLCHAR;
10228 lastHint[0] = NULLCHAR;
10229 ClearGameInfo(&gameInfo);
10230 gameInfo.variant = StringToVariant(appData.variant);
10231 ics_user_moved = ics_clock_paused = FALSE;
10232 ics_getting_history = H_FALSE;
10234 white_holding[0] = black_holding[0] = NULLCHAR;
10235 ClearProgramStats();
10236 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10240 flipView = appData.flipView;
10241 ClearPremoveHighlights();
10242 gotPremove = FALSE;
10243 alarmSounded = FALSE;
10245 GameEnds(EndOfFile, NULL, GE_PLAYER);
10246 if(appData.serverMovesName != NULL) {
10247 /* [HGM] prepare to make moves file for broadcasting */
10248 clock_t t = clock();
10249 if(serverMoves != NULL) fclose(serverMoves);
10250 serverMoves = fopen(appData.serverMovesName, "r");
10251 if(serverMoves != NULL) {
10252 fclose(serverMoves);
10253 /* delay 15 sec before overwriting, so all clients can see end */
10254 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10256 serverMoves = fopen(appData.serverMovesName, "w");
10260 gameMode = BeginningOfGame;
10262 if(appData.icsActive) gameInfo.variant = VariantNormal;
10263 currentMove = forwardMostMove = backwardMostMove = 0;
10264 InitPosition(redraw);
10265 for (i = 0; i < MAX_MOVES; i++) {
10266 if (commentList[i] != NULL) {
10267 free(commentList[i]);
10268 commentList[i] = NULL;
10272 timeRemaining[0][0] = whiteTimeRemaining;
10273 timeRemaining[1][0] = blackTimeRemaining;
10275 if (first.pr == NULL) {
10276 StartChessProgram(&first);
10279 InitChessProgram(&first, startedFromSetupPosition);
10282 DisplayMessage("", "");
10283 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10284 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10291 if (!AutoPlayOneMove())
10293 if (matchMode || appData.timeDelay == 0)
10295 if (appData.timeDelay < 0)
10297 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10306 int fromX, fromY, toX, toY;
10308 if (appData.debugMode) {
10309 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10312 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10315 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10316 pvInfoList[currentMove].depth = programStats.depth;
10317 pvInfoList[currentMove].score = programStats.score;
10318 pvInfoList[currentMove].time = 0;
10319 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10322 if (currentMove >= forwardMostMove) {
10323 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10324 gameMode = EditGame;
10327 /* [AS] Clear current move marker at the end of a game */
10328 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10333 toX = moveList[currentMove][2] - AAA;
10334 toY = moveList[currentMove][3] - ONE;
10336 if (moveList[currentMove][1] == '@') {
10337 if (appData.highlightLastMove) {
10338 SetHighlights(-1, -1, toX, toY);
10341 fromX = moveList[currentMove][0] - AAA;
10342 fromY = moveList[currentMove][1] - ONE;
10344 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10346 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10348 if (appData.highlightLastMove) {
10349 SetHighlights(fromX, fromY, toX, toY);
10352 DisplayMove(currentMove);
10353 SendMoveToProgram(currentMove++, &first);
10354 DisplayBothClocks();
10355 DrawPosition(FALSE, boards[currentMove]);
10356 // [HGM] PV info: always display, routine tests if empty
10357 DisplayComment(currentMove - 1, commentList[currentMove]);
10363 LoadGameOneMove(readAhead)
10364 ChessMove readAhead;
10366 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10367 char promoChar = NULLCHAR;
10368 ChessMove moveType;
10369 char move[MSG_SIZ];
10372 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10373 gameMode != AnalyzeMode && gameMode != Training) {
10378 yyboardindex = forwardMostMove;
10379 if (readAhead != EndOfFile) {
10380 moveType = readAhead;
10382 if (gameFileFP == NULL)
10384 moveType = (ChessMove) Myylex();
10388 switch (moveType) {
10390 if (appData.debugMode)
10391 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10394 /* append the comment but don't display it */
10395 AppendComment(currentMove, p, FALSE);
10398 case WhiteCapturesEnPassant:
10399 case BlackCapturesEnPassant:
10400 case WhitePromotion:
10401 case BlackPromotion:
10402 case WhiteNonPromotion:
10403 case BlackNonPromotion:
10405 case WhiteKingSideCastle:
10406 case WhiteQueenSideCastle:
10407 case BlackKingSideCastle:
10408 case BlackQueenSideCastle:
10409 case WhiteKingSideCastleWild:
10410 case WhiteQueenSideCastleWild:
10411 case BlackKingSideCastleWild:
10412 case BlackQueenSideCastleWild:
10414 case WhiteHSideCastleFR:
10415 case WhiteASideCastleFR:
10416 case BlackHSideCastleFR:
10417 case BlackASideCastleFR:
10419 if (appData.debugMode)
10420 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10421 fromX = currentMoveString[0] - AAA;
10422 fromY = currentMoveString[1] - ONE;
10423 toX = currentMoveString[2] - AAA;
10424 toY = currentMoveString[3] - ONE;
10425 promoChar = currentMoveString[4];
10430 if (appData.debugMode)
10431 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10432 fromX = moveType == WhiteDrop ?
10433 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10434 (int) CharToPiece(ToLower(currentMoveString[0]));
10436 toX = currentMoveString[2] - AAA;
10437 toY = currentMoveString[3] - ONE;
10443 case GameUnfinished:
10444 if (appData.debugMode)
10445 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10446 p = strchr(yy_text, '{');
10447 if (p == NULL) p = strchr(yy_text, '(');
10450 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10452 q = strchr(p, *p == '{' ? '}' : ')');
10453 if (q != NULL) *q = NULLCHAR;
10456 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10457 GameEnds(moveType, p, GE_FILE);
10459 if (cmailMsgLoaded) {
10461 flipView = WhiteOnMove(currentMove);
10462 if (moveType == GameUnfinished) flipView = !flipView;
10463 if (appData.debugMode)
10464 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10469 if (appData.debugMode)
10470 fprintf(debugFP, "Parser hit end of file\n");
10471 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10477 if (WhiteOnMove(currentMove)) {
10478 GameEnds(BlackWins, "Black mates", GE_FILE);
10480 GameEnds(WhiteWins, "White mates", GE_FILE);
10484 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10490 case MoveNumberOne:
10491 if (lastLoadGameStart == GNUChessGame) {
10492 /* GNUChessGames have numbers, but they aren't move numbers */
10493 if (appData.debugMode)
10494 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10495 yy_text, (int) moveType);
10496 return LoadGameOneMove(EndOfFile); /* tail recursion */
10498 /* else fall thru */
10503 /* Reached start of next game in file */
10504 if (appData.debugMode)
10505 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10506 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10512 if (WhiteOnMove(currentMove)) {
10513 GameEnds(BlackWins, "Black mates", GE_FILE);
10515 GameEnds(WhiteWins, "White mates", GE_FILE);
10519 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10525 case PositionDiagram: /* should not happen; ignore */
10526 case ElapsedTime: /* ignore */
10527 case NAG: /* ignore */
10528 if (appData.debugMode)
10529 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10530 yy_text, (int) moveType);
10531 return LoadGameOneMove(EndOfFile); /* tail recursion */
10534 if (appData.testLegality) {
10535 if (appData.debugMode)
10536 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10537 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10538 (forwardMostMove / 2) + 1,
10539 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10540 DisplayError(move, 0);
10543 if (appData.debugMode)
10544 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10545 yy_text, currentMoveString);
10546 fromX = currentMoveString[0] - AAA;
10547 fromY = currentMoveString[1] - ONE;
10548 toX = currentMoveString[2] - AAA;
10549 toY = currentMoveString[3] - ONE;
10550 promoChar = currentMoveString[4];
10554 case AmbiguousMove:
10555 if (appData.debugMode)
10556 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10557 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10558 (forwardMostMove / 2) + 1,
10559 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10560 DisplayError(move, 0);
10565 case ImpossibleMove:
10566 if (appData.debugMode)
10567 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10568 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10569 (forwardMostMove / 2) + 1,
10570 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10571 DisplayError(move, 0);
10577 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10578 DrawPosition(FALSE, boards[currentMove]);
10579 DisplayBothClocks();
10580 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10581 DisplayComment(currentMove - 1, commentList[currentMove]);
10583 (void) StopLoadGameTimer();
10585 cmailOldMove = forwardMostMove;
10588 /* currentMoveString is set as a side-effect of yylex */
10590 thinkOutput[0] = NULLCHAR;
10591 MakeMove(fromX, fromY, toX, toY, promoChar);
10592 currentMove = forwardMostMove;
10597 /* Load the nth game from the given file */
10599 LoadGameFromFile(filename, n, title, useList)
10603 /*Boolean*/ int useList;
10608 if (strcmp(filename, "-") == 0) {
10612 f = fopen(filename, "rb");
10614 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10615 DisplayError(buf, errno);
10619 if (fseek(f, 0, 0) == -1) {
10620 /* f is not seekable; probably a pipe */
10623 if (useList && n == 0) {
10624 int error = GameListBuild(f);
10626 DisplayError(_("Cannot build game list"), error);
10627 } else if (!ListEmpty(&gameList) &&
10628 ((ListGame *) gameList.tailPred)->number > 1) {
10629 GameListPopUp(f, title);
10636 return LoadGame(f, n, title, FALSE);
10641 MakeRegisteredMove()
10643 int fromX, fromY, toX, toY;
10645 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10646 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10649 if (appData.debugMode)
10650 fprintf(debugFP, "Restoring %s for game %d\n",
10651 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10653 thinkOutput[0] = NULLCHAR;
10654 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10655 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10656 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10657 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10658 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10659 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10660 MakeMove(fromX, fromY, toX, toY, promoChar);
10661 ShowMove(fromX, fromY, toX, toY);
10663 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10670 if (WhiteOnMove(currentMove)) {
10671 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10673 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10678 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10685 if (WhiteOnMove(currentMove)) {
10686 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10688 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10693 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10704 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10706 CmailLoadGame(f, gameNumber, title, useList)
10714 if (gameNumber > nCmailGames) {
10715 DisplayError(_("No more games in this message"), 0);
10718 if (f == lastLoadGameFP) {
10719 int offset = gameNumber - lastLoadGameNumber;
10721 cmailMsg[0] = NULLCHAR;
10722 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10723 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10724 nCmailMovesRegistered--;
10726 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10727 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10728 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10731 if (! RegisterMove()) return FALSE;
10735 retVal = LoadGame(f, gameNumber, title, useList);
10737 /* Make move registered during previous look at this game, if any */
10738 MakeRegisteredMove();
10740 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10741 commentList[currentMove]
10742 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10743 DisplayComment(currentMove - 1, commentList[currentMove]);
10749 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10754 int gameNumber = lastLoadGameNumber + offset;
10755 if (lastLoadGameFP == NULL) {
10756 DisplayError(_("No game has been loaded yet"), 0);
10759 if (gameNumber <= 0) {
10760 DisplayError(_("Can't back up any further"), 0);
10763 if (cmailMsgLoaded) {
10764 return CmailLoadGame(lastLoadGameFP, gameNumber,
10765 lastLoadGameTitle, lastLoadGameUseList);
10767 return LoadGame(lastLoadGameFP, gameNumber,
10768 lastLoadGameTitle, lastLoadGameUseList);
10774 /* Load the nth game from open file f */
10776 LoadGame(f, gameNumber, title, useList)
10784 int gn = gameNumber;
10785 ListGame *lg = NULL;
10786 int numPGNTags = 0;
10788 GameMode oldGameMode;
10789 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10791 if (appData.debugMode)
10792 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10794 if (gameMode == Training )
10795 SetTrainingModeOff();
10797 oldGameMode = gameMode;
10798 if (gameMode != BeginningOfGame) {
10799 Reset(FALSE, TRUE);
10803 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10804 fclose(lastLoadGameFP);
10808 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10811 fseek(f, lg->offset, 0);
10812 GameListHighlight(gameNumber);
10816 DisplayError(_("Game number out of range"), 0);
10821 if (fseek(f, 0, 0) == -1) {
10822 if (f == lastLoadGameFP ?
10823 gameNumber == lastLoadGameNumber + 1 :
10827 DisplayError(_("Can't seek on game file"), 0);
10832 lastLoadGameFP = f;
10833 lastLoadGameNumber = gameNumber;
10834 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10835 lastLoadGameUseList = useList;
10839 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10840 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10841 lg->gameInfo.black);
10843 } else if (*title != NULLCHAR) {
10844 if (gameNumber > 1) {
10845 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10848 DisplayTitle(title);
10852 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10853 gameMode = PlayFromGameFile;
10857 currentMove = forwardMostMove = backwardMostMove = 0;
10858 CopyBoard(boards[0], initialPosition);
10862 * Skip the first gn-1 games in the file.
10863 * Also skip over anything that precedes an identifiable
10864 * start of game marker, to avoid being confused by
10865 * garbage at the start of the file. Currently
10866 * recognized start of game markers are the move number "1",
10867 * the pattern "gnuchess .* game", the pattern
10868 * "^[#;%] [^ ]* game file", and a PGN tag block.
10869 * A game that starts with one of the latter two patterns
10870 * will also have a move number 1, possibly
10871 * following a position diagram.
10872 * 5-4-02: Let's try being more lenient and allowing a game to
10873 * start with an unnumbered move. Does that break anything?
10875 cm = lastLoadGameStart = EndOfFile;
10877 yyboardindex = forwardMostMove;
10878 cm = (ChessMove) Myylex();
10881 if (cmailMsgLoaded) {
10882 nCmailGames = CMAIL_MAX_GAMES - gn;
10885 DisplayError(_("Game not found in file"), 0);
10892 lastLoadGameStart = cm;
10895 case MoveNumberOne:
10896 switch (lastLoadGameStart) {
10901 case MoveNumberOne:
10903 gn--; /* count this game */
10904 lastLoadGameStart = cm;
10913 switch (lastLoadGameStart) {
10916 case MoveNumberOne:
10918 gn--; /* count this game */
10919 lastLoadGameStart = cm;
10922 lastLoadGameStart = cm; /* game counted already */
10930 yyboardindex = forwardMostMove;
10931 cm = (ChessMove) Myylex();
10932 } while (cm == PGNTag || cm == Comment);
10939 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10940 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10941 != CMAIL_OLD_RESULT) {
10943 cmailResult[ CMAIL_MAX_GAMES
10944 - gn - 1] = CMAIL_OLD_RESULT;
10950 /* Only a NormalMove can be at the start of a game
10951 * without a position diagram. */
10952 if (lastLoadGameStart == EndOfFile ) {
10954 lastLoadGameStart = MoveNumberOne;
10963 if (appData.debugMode)
10964 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10966 if (cm == XBoardGame) {
10967 /* Skip any header junk before position diagram and/or move 1 */
10969 yyboardindex = forwardMostMove;
10970 cm = (ChessMove) Myylex();
10972 if (cm == EndOfFile ||
10973 cm == GNUChessGame || cm == XBoardGame) {
10974 /* Empty game; pretend end-of-file and handle later */
10979 if (cm == MoveNumberOne || cm == PositionDiagram ||
10980 cm == PGNTag || cm == Comment)
10983 } else if (cm == GNUChessGame) {
10984 if (gameInfo.event != NULL) {
10985 free(gameInfo.event);
10987 gameInfo.event = StrSave(yy_text);
10990 startedFromSetupPosition = FALSE;
10991 while (cm == PGNTag) {
10992 if (appData.debugMode)
10993 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10994 err = ParsePGNTag(yy_text, &gameInfo);
10995 if (!err) numPGNTags++;
10997 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10998 if(gameInfo.variant != oldVariant) {
10999 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11000 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11001 InitPosition(TRUE);
11002 oldVariant = gameInfo.variant;
11003 if (appData.debugMode)
11004 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11008 if (gameInfo.fen != NULL) {
11009 Board initial_position;
11010 startedFromSetupPosition = TRUE;
11011 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11013 DisplayError(_("Bad FEN position in file"), 0);
11016 CopyBoard(boards[0], initial_position);
11017 if (blackPlaysFirst) {
11018 currentMove = forwardMostMove = backwardMostMove = 1;
11019 CopyBoard(boards[1], initial_position);
11020 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11021 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11022 timeRemaining[0][1] = whiteTimeRemaining;
11023 timeRemaining[1][1] = blackTimeRemaining;
11024 if (commentList[0] != NULL) {
11025 commentList[1] = commentList[0];
11026 commentList[0] = NULL;
11029 currentMove = forwardMostMove = backwardMostMove = 0;
11031 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11033 initialRulePlies = FENrulePlies;
11034 for( i=0; i< nrCastlingRights; i++ )
11035 initialRights[i] = initial_position[CASTLING][i];
11037 yyboardindex = forwardMostMove;
11038 free(gameInfo.fen);
11039 gameInfo.fen = NULL;
11042 yyboardindex = forwardMostMove;
11043 cm = (ChessMove) Myylex();
11045 /* Handle comments interspersed among the tags */
11046 while (cm == Comment) {
11048 if (appData.debugMode)
11049 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11051 AppendComment(currentMove, p, FALSE);
11052 yyboardindex = forwardMostMove;
11053 cm = (ChessMove) Myylex();
11057 /* don't rely on existence of Event tag since if game was
11058 * pasted from clipboard the Event tag may not exist
11060 if (numPGNTags > 0){
11062 if (gameInfo.variant == VariantNormal) {
11063 VariantClass v = StringToVariant(gameInfo.event);
11064 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11065 if(v < VariantShogi) gameInfo.variant = v;
11068 if( appData.autoDisplayTags ) {
11069 tags = PGNTags(&gameInfo);
11070 TagsPopUp(tags, CmailMsg());
11075 /* Make something up, but don't display it now */
11080 if (cm == PositionDiagram) {
11083 Board initial_position;
11085 if (appData.debugMode)
11086 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11088 if (!startedFromSetupPosition) {
11090 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11091 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11102 initial_position[i][j++] = CharToPiece(*p);
11105 while (*p == ' ' || *p == '\t' ||
11106 *p == '\n' || *p == '\r') p++;
11108 if (strncmp(p, "black", strlen("black"))==0)
11109 blackPlaysFirst = TRUE;
11111 blackPlaysFirst = FALSE;
11112 startedFromSetupPosition = TRUE;
11114 CopyBoard(boards[0], initial_position);
11115 if (blackPlaysFirst) {
11116 currentMove = forwardMostMove = backwardMostMove = 1;
11117 CopyBoard(boards[1], initial_position);
11118 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11119 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11120 timeRemaining[0][1] = whiteTimeRemaining;
11121 timeRemaining[1][1] = blackTimeRemaining;
11122 if (commentList[0] != NULL) {
11123 commentList[1] = commentList[0];
11124 commentList[0] = NULL;
11127 currentMove = forwardMostMove = backwardMostMove = 0;
11130 yyboardindex = forwardMostMove;
11131 cm = (ChessMove) Myylex();
11134 if (first.pr == NoProc) {
11135 StartChessProgram(&first);
11137 InitChessProgram(&first, FALSE);
11138 SendToProgram("force\n", &first);
11139 if (startedFromSetupPosition) {
11140 SendBoard(&first, forwardMostMove);
11141 if (appData.debugMode) {
11142 fprintf(debugFP, "Load Game\n");
11144 DisplayBothClocks();
11147 /* [HGM] server: flag to write setup moves in broadcast file as one */
11148 loadFlag = appData.suppressLoadMoves;
11150 while (cm == Comment) {
11152 if (appData.debugMode)
11153 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11155 AppendComment(currentMove, p, FALSE);
11156 yyboardindex = forwardMostMove;
11157 cm = (ChessMove) Myylex();
11160 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11161 cm == WhiteWins || cm == BlackWins ||
11162 cm == GameIsDrawn || cm == GameUnfinished) {
11163 DisplayMessage("", _("No moves in game"));
11164 if (cmailMsgLoaded) {
11165 if (appData.debugMode)
11166 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11170 DrawPosition(FALSE, boards[currentMove]);
11171 DisplayBothClocks();
11172 gameMode = EditGame;
11179 // [HGM] PV info: routine tests if comment empty
11180 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11181 DisplayComment(currentMove - 1, commentList[currentMove]);
11183 if (!matchMode && appData.timeDelay != 0)
11184 DrawPosition(FALSE, boards[currentMove]);
11186 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11187 programStats.ok_to_send = 1;
11190 /* if the first token after the PGN tags is a move
11191 * and not move number 1, retrieve it from the parser
11193 if (cm != MoveNumberOne)
11194 LoadGameOneMove(cm);
11196 /* load the remaining moves from the file */
11197 while (LoadGameOneMove(EndOfFile)) {
11198 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11199 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11202 /* rewind to the start of the game */
11203 currentMove = backwardMostMove;
11205 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11207 if (oldGameMode == AnalyzeFile ||
11208 oldGameMode == AnalyzeMode) {
11209 AnalyzeFileEvent();
11212 if (matchMode || appData.timeDelay == 0) {
11214 gameMode = EditGame;
11216 } else if (appData.timeDelay > 0) {
11217 AutoPlayGameLoop();
11220 if (appData.debugMode)
11221 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11223 loadFlag = 0; /* [HGM] true game starts */
11227 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11229 ReloadPosition(offset)
11232 int positionNumber = lastLoadPositionNumber + offset;
11233 if (lastLoadPositionFP == NULL) {
11234 DisplayError(_("No position has been loaded yet"), 0);
11237 if (positionNumber <= 0) {
11238 DisplayError(_("Can't back up any further"), 0);
11241 return LoadPosition(lastLoadPositionFP, positionNumber,
11242 lastLoadPositionTitle);
11245 /* Load the nth position from the given file */
11247 LoadPositionFromFile(filename, n, title)
11255 if (strcmp(filename, "-") == 0) {
11256 return LoadPosition(stdin, n, "stdin");
11258 f = fopen(filename, "rb");
11260 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11261 DisplayError(buf, errno);
11264 return LoadPosition(f, n, title);
11269 /* Load the nth position from the given open file, and close it */
11271 LoadPosition(f, positionNumber, title)
11273 int positionNumber;
11276 char *p, line[MSG_SIZ];
11277 Board initial_position;
11278 int i, j, fenMode, pn;
11280 if (gameMode == Training )
11281 SetTrainingModeOff();
11283 if (gameMode != BeginningOfGame) {
11284 Reset(FALSE, TRUE);
11286 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11287 fclose(lastLoadPositionFP);
11289 if (positionNumber == 0) positionNumber = 1;
11290 lastLoadPositionFP = f;
11291 lastLoadPositionNumber = positionNumber;
11292 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11293 if (first.pr == NoProc) {
11294 StartChessProgram(&first);
11295 InitChessProgram(&first, FALSE);
11297 pn = positionNumber;
11298 if (positionNumber < 0) {
11299 /* Negative position number means to seek to that byte offset */
11300 if (fseek(f, -positionNumber, 0) == -1) {
11301 DisplayError(_("Can't seek on position file"), 0);
11306 if (fseek(f, 0, 0) == -1) {
11307 if (f == lastLoadPositionFP ?
11308 positionNumber == lastLoadPositionNumber + 1 :
11309 positionNumber == 1) {
11312 DisplayError(_("Can't seek on position file"), 0);
11317 /* See if this file is FEN or old-style xboard */
11318 if (fgets(line, MSG_SIZ, f) == NULL) {
11319 DisplayError(_("Position not found in file"), 0);
11322 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11323 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11326 if (fenMode || line[0] == '#') pn--;
11328 /* skip positions before number pn */
11329 if (fgets(line, MSG_SIZ, f) == NULL) {
11331 DisplayError(_("Position not found in file"), 0);
11334 if (fenMode || line[0] == '#') pn--;
11339 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11340 DisplayError(_("Bad FEN position in file"), 0);
11344 (void) fgets(line, MSG_SIZ, f);
11345 (void) fgets(line, MSG_SIZ, f);
11347 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11348 (void) fgets(line, MSG_SIZ, f);
11349 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11352 initial_position[i][j++] = CharToPiece(*p);
11356 blackPlaysFirst = FALSE;
11358 (void) fgets(line, MSG_SIZ, f);
11359 if (strncmp(line, "black", strlen("black"))==0)
11360 blackPlaysFirst = TRUE;
11363 startedFromSetupPosition = TRUE;
11365 SendToProgram("force\n", &first);
11366 CopyBoard(boards[0], initial_position);
11367 if (blackPlaysFirst) {
11368 currentMove = forwardMostMove = backwardMostMove = 1;
11369 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11370 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11371 CopyBoard(boards[1], initial_position);
11372 DisplayMessage("", _("Black to play"));
11374 currentMove = forwardMostMove = backwardMostMove = 0;
11375 DisplayMessage("", _("White to play"));
11377 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11378 SendBoard(&first, forwardMostMove);
11379 if (appData.debugMode) {
11381 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11382 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11383 fprintf(debugFP, "Load Position\n");
11386 if (positionNumber > 1) {
11387 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11388 DisplayTitle(line);
11390 DisplayTitle(title);
11392 gameMode = EditGame;
11395 timeRemaining[0][1] = whiteTimeRemaining;
11396 timeRemaining[1][1] = blackTimeRemaining;
11397 DrawPosition(FALSE, boards[currentMove]);
11404 CopyPlayerNameIntoFileName(dest, src)
11407 while (*src != NULLCHAR && *src != ',') {
11412 *(*dest)++ = *src++;
11417 char *DefaultFileName(ext)
11420 static char def[MSG_SIZ];
11423 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11425 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11427 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11429 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11436 /* Save the current game to the given file */
11438 SaveGameToFile(filename, append)
11446 if (strcmp(filename, "-") == 0) {
11447 return SaveGame(stdout, 0, NULL);
11449 f = fopen(filename, append ? "a" : "w");
11451 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11452 DisplayError(buf, errno);
11455 safeStrCpy(buf, lastMsg, MSG_SIZ);
11456 DisplayMessage(_("Waiting for access to save file"), "");
11457 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11458 DisplayMessage(_("Saving game"), "");
11459 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11460 result = SaveGame(f, 0, NULL);
11461 DisplayMessage(buf, "");
11471 static char buf[MSG_SIZ];
11474 p = strchr(str, ' ');
11475 if (p == NULL) return str;
11476 strncpy(buf, str, p - str);
11477 buf[p - str] = NULLCHAR;
11481 #define PGN_MAX_LINE 75
11483 #define PGN_SIDE_WHITE 0
11484 #define PGN_SIDE_BLACK 1
11487 static int FindFirstMoveOutOfBook( int side )
11491 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11492 int index = backwardMostMove;
11493 int has_book_hit = 0;
11495 if( (index % 2) != side ) {
11499 while( index < forwardMostMove ) {
11500 /* Check to see if engine is in book */
11501 int depth = pvInfoList[index].depth;
11502 int score = pvInfoList[index].score;
11508 else if( score == 0 && depth == 63 ) {
11509 in_book = 1; /* Zappa */
11511 else if( score == 2 && depth == 99 ) {
11512 in_book = 1; /* Abrok */
11515 has_book_hit += in_book;
11531 void GetOutOfBookInfo( char * buf )
11535 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11537 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11538 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11542 if( oob[0] >= 0 || oob[1] >= 0 ) {
11543 for( i=0; i<2; i++ ) {
11547 if( i > 0 && oob[0] >= 0 ) {
11548 strcat( buf, " " );
11551 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11552 sprintf( buf+strlen(buf), "%s%.2f",
11553 pvInfoList[idx].score >= 0 ? "+" : "",
11554 pvInfoList[idx].score / 100.0 );
11560 /* Save game in PGN style and close the file */
11565 int i, offset, linelen, newblock;
11569 int movelen, numlen, blank;
11570 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11572 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11574 tm = time((time_t *) NULL);
11576 PrintPGNTags(f, &gameInfo);
11578 if (backwardMostMove > 0 || startedFromSetupPosition) {
11579 char *fen = PositionToFEN(backwardMostMove, NULL);
11580 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11581 fprintf(f, "\n{--------------\n");
11582 PrintPosition(f, backwardMostMove);
11583 fprintf(f, "--------------}\n");
11587 /* [AS] Out of book annotation */
11588 if( appData.saveOutOfBookInfo ) {
11591 GetOutOfBookInfo( buf );
11593 if( buf[0] != '\0' ) {
11594 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11601 i = backwardMostMove;
11605 while (i < forwardMostMove) {
11606 /* Print comments preceding this move */
11607 if (commentList[i] != NULL) {
11608 if (linelen > 0) fprintf(f, "\n");
11609 fprintf(f, "%s", commentList[i]);
11614 /* Format move number */
11616 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11619 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11621 numtext[0] = NULLCHAR;
11623 numlen = strlen(numtext);
11626 /* Print move number */
11627 blank = linelen > 0 && numlen > 0;
11628 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11637 fprintf(f, "%s", numtext);
11641 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11642 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11645 blank = linelen > 0 && movelen > 0;
11646 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11655 fprintf(f, "%s", move_buffer);
11656 linelen += movelen;
11658 /* [AS] Add PV info if present */
11659 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11660 /* [HGM] add time */
11661 char buf[MSG_SIZ]; int seconds;
11663 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11669 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11672 seconds = (seconds + 4)/10; // round to full seconds
11674 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11676 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11679 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11680 pvInfoList[i].score >= 0 ? "+" : "",
11681 pvInfoList[i].score / 100.0,
11682 pvInfoList[i].depth,
11685 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11687 /* Print score/depth */
11688 blank = linelen > 0 && movelen > 0;
11689 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11698 fprintf(f, "%s", move_buffer);
11699 linelen += movelen;
11705 /* Start a new line */
11706 if (linelen > 0) fprintf(f, "\n");
11708 /* Print comments after last move */
11709 if (commentList[i] != NULL) {
11710 fprintf(f, "%s\n", commentList[i]);
11714 if (gameInfo.resultDetails != NULL &&
11715 gameInfo.resultDetails[0] != NULLCHAR) {
11716 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11717 PGNResult(gameInfo.result));
11719 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11723 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11727 /* Save game in old style and close the file */
11729 SaveGameOldStyle(f)
11735 tm = time((time_t *) NULL);
11737 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11740 if (backwardMostMove > 0 || startedFromSetupPosition) {
11741 fprintf(f, "\n[--------------\n");
11742 PrintPosition(f, backwardMostMove);
11743 fprintf(f, "--------------]\n");
11748 i = backwardMostMove;
11749 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11751 while (i < forwardMostMove) {
11752 if (commentList[i] != NULL) {
11753 fprintf(f, "[%s]\n", commentList[i]);
11756 if ((i % 2) == 1) {
11757 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11760 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11762 if (commentList[i] != NULL) {
11766 if (i >= forwardMostMove) {
11770 fprintf(f, "%s\n", parseList[i]);
11775 if (commentList[i] != NULL) {
11776 fprintf(f, "[%s]\n", commentList[i]);
11779 /* This isn't really the old style, but it's close enough */
11780 if (gameInfo.resultDetails != NULL &&
11781 gameInfo.resultDetails[0] != NULLCHAR) {
11782 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11783 gameInfo.resultDetails);
11785 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11792 /* Save the current game to open file f and close the file */
11794 SaveGame(f, dummy, dummy2)
11799 if (gameMode == EditPosition) EditPositionDone(TRUE);
11800 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11801 if (appData.oldSaveStyle)
11802 return SaveGameOldStyle(f);
11804 return SaveGamePGN(f);
11807 /* Save the current position to the given file */
11809 SavePositionToFile(filename)
11815 if (strcmp(filename, "-") == 0) {
11816 return SavePosition(stdout, 0, NULL);
11818 f = fopen(filename, "a");
11820 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11821 DisplayError(buf, errno);
11824 safeStrCpy(buf, lastMsg, MSG_SIZ);
11825 DisplayMessage(_("Waiting for access to save file"), "");
11826 flock(fileno(f), LOCK_EX); // [HGM] lock
11827 DisplayMessage(_("Saving position"), "");
11828 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11829 SavePosition(f, 0, NULL);
11830 DisplayMessage(buf, "");
11836 /* Save the current position to the given open file and close the file */
11838 SavePosition(f, dummy, dummy2)
11846 if (gameMode == EditPosition) EditPositionDone(TRUE);
11847 if (appData.oldSaveStyle) {
11848 tm = time((time_t *) NULL);
11850 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11852 fprintf(f, "[--------------\n");
11853 PrintPosition(f, currentMove);
11854 fprintf(f, "--------------]\n");
11856 fen = PositionToFEN(currentMove, NULL);
11857 fprintf(f, "%s\n", fen);
11865 ReloadCmailMsgEvent(unregister)
11869 static char *inFilename = NULL;
11870 static char *outFilename;
11872 struct stat inbuf, outbuf;
11875 /* Any registered moves are unregistered if unregister is set, */
11876 /* i.e. invoked by the signal handler */
11878 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11879 cmailMoveRegistered[i] = FALSE;
11880 if (cmailCommentList[i] != NULL) {
11881 free(cmailCommentList[i]);
11882 cmailCommentList[i] = NULL;
11885 nCmailMovesRegistered = 0;
11888 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11889 cmailResult[i] = CMAIL_NOT_RESULT;
11893 if (inFilename == NULL) {
11894 /* Because the filenames are static they only get malloced once */
11895 /* and they never get freed */
11896 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11897 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11899 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11900 sprintf(outFilename, "%s.out", appData.cmailGameName);
11903 status = stat(outFilename, &outbuf);
11905 cmailMailedMove = FALSE;
11907 status = stat(inFilename, &inbuf);
11908 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11911 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11912 counts the games, notes how each one terminated, etc.
11914 It would be nice to remove this kludge and instead gather all
11915 the information while building the game list. (And to keep it
11916 in the game list nodes instead of having a bunch of fixed-size
11917 parallel arrays.) Note this will require getting each game's
11918 termination from the PGN tags, as the game list builder does
11919 not process the game moves. --mann
11921 cmailMsgLoaded = TRUE;
11922 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11924 /* Load first game in the file or popup game menu */
11925 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11927 #endif /* !WIN32 */
11935 char string[MSG_SIZ];
11937 if ( cmailMailedMove
11938 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11939 return TRUE; /* Allow free viewing */
11942 /* Unregister move to ensure that we don't leave RegisterMove */
11943 /* with the move registered when the conditions for registering no */
11945 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11946 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11947 nCmailMovesRegistered --;
11949 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11951 free(cmailCommentList[lastLoadGameNumber - 1]);
11952 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11956 if (cmailOldMove == -1) {
11957 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11961 if (currentMove > cmailOldMove + 1) {
11962 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11966 if (currentMove < cmailOldMove) {
11967 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11971 if (forwardMostMove > currentMove) {
11972 /* Silently truncate extra moves */
11976 if ( (currentMove == cmailOldMove + 1)
11977 || ( (currentMove == cmailOldMove)
11978 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11979 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11980 if (gameInfo.result != GameUnfinished) {
11981 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11984 if (commentList[currentMove] != NULL) {
11985 cmailCommentList[lastLoadGameNumber - 1]
11986 = StrSave(commentList[currentMove]);
11988 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11990 if (appData.debugMode)
11991 fprintf(debugFP, "Saving %s for game %d\n",
11992 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11994 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11996 f = fopen(string, "w");
11997 if (appData.oldSaveStyle) {
11998 SaveGameOldStyle(f); /* also closes the file */
12000 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12001 f = fopen(string, "w");
12002 SavePosition(f, 0, NULL); /* also closes the file */
12004 fprintf(f, "{--------------\n");
12005 PrintPosition(f, currentMove);
12006 fprintf(f, "--------------}\n\n");
12008 SaveGame(f, 0, NULL); /* also closes the file*/
12011 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12012 nCmailMovesRegistered ++;
12013 } else if (nCmailGames == 1) {
12014 DisplayError(_("You have not made a move yet"), 0);
12025 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12026 FILE *commandOutput;
12027 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12028 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12034 if (! cmailMsgLoaded) {
12035 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12039 if (nCmailGames == nCmailResults) {
12040 DisplayError(_("No unfinished games"), 0);
12044 #if CMAIL_PROHIBIT_REMAIL
12045 if (cmailMailedMove) {
12046 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);
12047 DisplayError(msg, 0);
12052 if (! (cmailMailedMove || RegisterMove())) return;
12054 if ( cmailMailedMove
12055 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12056 snprintf(string, MSG_SIZ, partCommandString,
12057 appData.debugMode ? " -v" : "", appData.cmailGameName);
12058 commandOutput = popen(string, "r");
12060 if (commandOutput == NULL) {
12061 DisplayError(_("Failed to invoke cmail"), 0);
12063 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12064 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12066 if (nBuffers > 1) {
12067 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12068 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12069 nBytes = MSG_SIZ - 1;
12071 (void) memcpy(msg, buffer, nBytes);
12073 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12075 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12076 cmailMailedMove = TRUE; /* Prevent >1 moves */
12079 for (i = 0; i < nCmailGames; i ++) {
12080 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12085 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12087 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12089 appData.cmailGameName,
12091 LoadGameFromFile(buffer, 1, buffer, FALSE);
12092 cmailMsgLoaded = FALSE;
12096 DisplayInformation(msg);
12097 pclose(commandOutput);
12100 if ((*cmailMsg) != '\0') {
12101 DisplayInformation(cmailMsg);
12106 #endif /* !WIN32 */
12115 int prependComma = 0;
12117 char string[MSG_SIZ]; /* Space for game-list */
12120 if (!cmailMsgLoaded) return "";
12122 if (cmailMailedMove) {
12123 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12125 /* Create a list of games left */
12126 snprintf(string, MSG_SIZ, "[");
12127 for (i = 0; i < nCmailGames; i ++) {
12128 if (! ( cmailMoveRegistered[i]
12129 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12130 if (prependComma) {
12131 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12133 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12137 strcat(string, number);
12140 strcat(string, "]");
12142 if (nCmailMovesRegistered + nCmailResults == 0) {
12143 switch (nCmailGames) {
12145 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12149 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12153 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12158 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12160 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12165 if (nCmailResults == nCmailGames) {
12166 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12168 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12173 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12185 if (gameMode == Training)
12186 SetTrainingModeOff();
12189 cmailMsgLoaded = FALSE;
12190 if (appData.icsActive) {
12191 SendToICS(ics_prefix);
12192 SendToICS("refresh\n");
12202 /* Give up on clean exit */
12206 /* Keep trying for clean exit */
12210 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12212 if (telnetISR != NULL) {
12213 RemoveInputSource(telnetISR);
12215 if (icsPR != NoProc) {
12216 DestroyChildProcess(icsPR, TRUE);
12219 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12220 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12222 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12223 /* make sure this other one finishes before killing it! */
12224 if(endingGame) { int count = 0;
12225 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12226 while(endingGame && count++ < 10) DoSleep(1);
12227 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12230 /* Kill off chess programs */
12231 if (first.pr != NoProc) {
12234 DoSleep( appData.delayBeforeQuit );
12235 SendToProgram("quit\n", &first);
12236 DoSleep( appData.delayAfterQuit );
12237 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12239 if (second.pr != NoProc) {
12240 DoSleep( appData.delayBeforeQuit );
12241 SendToProgram("quit\n", &second);
12242 DoSleep( appData.delayAfterQuit );
12243 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12245 if (first.isr != NULL) {
12246 RemoveInputSource(first.isr);
12248 if (second.isr != NULL) {
12249 RemoveInputSource(second.isr);
12252 ShutDownFrontEnd();
12259 if (appData.debugMode)
12260 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12264 if (gameMode == MachinePlaysWhite ||
12265 gameMode == MachinePlaysBlack) {
12268 DisplayBothClocks();
12270 if (gameMode == PlayFromGameFile) {
12271 if (appData.timeDelay >= 0)
12272 AutoPlayGameLoop();
12273 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12274 Reset(FALSE, TRUE);
12275 SendToICS(ics_prefix);
12276 SendToICS("refresh\n");
12277 } else if (currentMove < forwardMostMove) {
12278 ForwardInner(forwardMostMove);
12280 pauseExamInvalid = FALSE;
12282 switch (gameMode) {
12286 pauseExamForwardMostMove = forwardMostMove;
12287 pauseExamInvalid = FALSE;
12290 case IcsPlayingWhite:
12291 case IcsPlayingBlack:
12295 case PlayFromGameFile:
12296 (void) StopLoadGameTimer();
12300 case BeginningOfGame:
12301 if (appData.icsActive) return;
12302 /* else fall through */
12303 case MachinePlaysWhite:
12304 case MachinePlaysBlack:
12305 case TwoMachinesPlay:
12306 if (forwardMostMove == 0)
12307 return; /* don't pause if no one has moved */
12308 if ((gameMode == MachinePlaysWhite &&
12309 !WhiteOnMove(forwardMostMove)) ||
12310 (gameMode == MachinePlaysBlack &&
12311 WhiteOnMove(forwardMostMove))) {
12324 char title[MSG_SIZ];
12326 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12327 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12329 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12330 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12331 parseList[currentMove - 1]);
12334 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12341 char *tags = PGNTags(&gameInfo);
12343 EditTagsPopUp(tags, NULL);
12350 if (appData.noChessProgram || gameMode == AnalyzeMode)
12353 if (gameMode != AnalyzeFile) {
12354 if (!appData.icsEngineAnalyze) {
12356 if (gameMode != EditGame) return;
12358 ResurrectChessProgram();
12359 SendToProgram("analyze\n", &first);
12360 first.analyzing = TRUE;
12361 /*first.maybeThinking = TRUE;*/
12362 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12363 EngineOutputPopUp();
12365 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12370 StartAnalysisClock();
12371 GetTimeMark(&lastNodeCountTime);
12378 if (appData.noChessProgram || gameMode == AnalyzeFile)
12381 if (gameMode != AnalyzeMode) {
12383 if (gameMode != EditGame) return;
12384 ResurrectChessProgram();
12385 SendToProgram("analyze\n", &first);
12386 first.analyzing = TRUE;
12387 /*first.maybeThinking = TRUE;*/
12388 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12389 EngineOutputPopUp();
12391 gameMode = AnalyzeFile;
12396 StartAnalysisClock();
12397 GetTimeMark(&lastNodeCountTime);
12402 MachineWhiteEvent()
12405 char *bookHit = NULL;
12407 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12411 if (gameMode == PlayFromGameFile ||
12412 gameMode == TwoMachinesPlay ||
12413 gameMode == Training ||
12414 gameMode == AnalyzeMode ||
12415 gameMode == EndOfGame)
12418 if (gameMode == EditPosition)
12419 EditPositionDone(TRUE);
12421 if (!WhiteOnMove(currentMove)) {
12422 DisplayError(_("It is not White's turn"), 0);
12426 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12429 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12430 gameMode == AnalyzeFile)
12433 ResurrectChessProgram(); /* in case it isn't running */
12434 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12435 gameMode = MachinePlaysWhite;
12438 gameMode = MachinePlaysWhite;
12442 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12444 if (first.sendName) {
12445 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12446 SendToProgram(buf, &first);
12448 if (first.sendTime) {
12449 if (first.useColors) {
12450 SendToProgram("black\n", &first); /*gnu kludge*/
12452 SendTimeRemaining(&first, TRUE);
12454 if (first.useColors) {
12455 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12457 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12458 SetMachineThinkingEnables();
12459 first.maybeThinking = TRUE;
12463 if (appData.autoFlipView && !flipView) {
12464 flipView = !flipView;
12465 DrawPosition(FALSE, NULL);
12466 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12469 if(bookHit) { // [HGM] book: simulate book reply
12470 static char bookMove[MSG_SIZ]; // a bit generous?
12472 programStats.nodes = programStats.depth = programStats.time =
12473 programStats.score = programStats.got_only_move = 0;
12474 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12476 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12477 strcat(bookMove, bookHit);
12478 HandleMachineMove(bookMove, &first);
12483 MachineBlackEvent()
12486 char *bookHit = NULL;
12488 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12492 if (gameMode == PlayFromGameFile ||
12493 gameMode == TwoMachinesPlay ||
12494 gameMode == Training ||
12495 gameMode == AnalyzeMode ||
12496 gameMode == EndOfGame)
12499 if (gameMode == EditPosition)
12500 EditPositionDone(TRUE);
12502 if (WhiteOnMove(currentMove)) {
12503 DisplayError(_("It is not Black's turn"), 0);
12507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12510 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12511 gameMode == AnalyzeFile)
12514 ResurrectChessProgram(); /* in case it isn't running */
12515 gameMode = MachinePlaysBlack;
12519 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12521 if (first.sendName) {
12522 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12523 SendToProgram(buf, &first);
12525 if (first.sendTime) {
12526 if (first.useColors) {
12527 SendToProgram("white\n", &first); /*gnu kludge*/
12529 SendTimeRemaining(&first, FALSE);
12531 if (first.useColors) {
12532 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12534 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12535 SetMachineThinkingEnables();
12536 first.maybeThinking = TRUE;
12539 if (appData.autoFlipView && flipView) {
12540 flipView = !flipView;
12541 DrawPosition(FALSE, NULL);
12542 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12544 if(bookHit) { // [HGM] book: simulate book reply
12545 static char bookMove[MSG_SIZ]; // a bit generous?
12547 programStats.nodes = programStats.depth = programStats.time =
12548 programStats.score = programStats.got_only_move = 0;
12549 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12551 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12552 strcat(bookMove, bookHit);
12553 HandleMachineMove(bookMove, &first);
12559 DisplayTwoMachinesTitle()
12562 if (appData.matchGames > 0) {
12563 if (first.twoMachinesColor[0] == 'w') {
12564 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12565 gameInfo.white, gameInfo.black,
12566 first.matchWins, second.matchWins,
12567 matchGame - 1 - (first.matchWins + second.matchWins));
12569 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12570 gameInfo.white, gameInfo.black,
12571 second.matchWins, first.matchWins,
12572 matchGame - 1 - (first.matchWins + second.matchWins));
12575 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12581 SettingsMenuIfReady()
12583 if (second.lastPing != second.lastPong) {
12584 DisplayMessage("", _("Waiting for second chess program"));
12585 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12589 DisplayMessage("", "");
12590 SettingsPopUp(&second);
12594 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12597 if (cps->pr == NULL) {
12598 StartChessProgram(cps);
12599 if (cps->protocolVersion == 1) {
12602 /* kludge: allow timeout for initial "feature" command */
12604 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12605 DisplayMessage("", buf);
12606 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12614 TwoMachinesEvent P((void))
12618 ChessProgramState *onmove;
12619 char *bookHit = NULL;
12620 static int stalling = 0;
12624 if (appData.noChessProgram) return;
12626 switch (gameMode) {
12627 case TwoMachinesPlay:
12629 case MachinePlaysWhite:
12630 case MachinePlaysBlack:
12631 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12632 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12636 case BeginningOfGame:
12637 case PlayFromGameFile:
12640 if (gameMode != EditGame) return;
12643 EditPositionDone(TRUE);
12654 // forwardMostMove = currentMove;
12655 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12657 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12659 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12660 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12661 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12665 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12666 SendToProgram("force\n", &second);
12668 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12671 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12672 if(appData.matchPause>10000 || appData.matchPause<10)
12673 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12674 wait = SubtractTimeMarks(&now, &pauseStart);
12675 if(wait < appData.matchPause) {
12676 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12680 DisplayMessage("", "");
12681 if (startedFromSetupPosition) {
12682 SendBoard(&second, backwardMostMove);
12683 if (appData.debugMode) {
12684 fprintf(debugFP, "Two Machines\n");
12687 for (i = backwardMostMove; i < forwardMostMove; i++) {
12688 SendMoveToProgram(i, &second);
12691 gameMode = TwoMachinesPlay;
12695 DisplayTwoMachinesTitle();
12697 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12702 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12703 SendToProgram(first.computerString, &first);
12704 if (first.sendName) {
12705 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12706 SendToProgram(buf, &first);
12708 SendToProgram(second.computerString, &second);
12709 if (second.sendName) {
12710 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12711 SendToProgram(buf, &second);
12715 if (!first.sendTime || !second.sendTime) {
12716 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12717 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12719 if (onmove->sendTime) {
12720 if (onmove->useColors) {
12721 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12723 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12725 if (onmove->useColors) {
12726 SendToProgram(onmove->twoMachinesColor, onmove);
12728 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12729 // SendToProgram("go\n", onmove);
12730 onmove->maybeThinking = TRUE;
12731 SetMachineThinkingEnables();
12735 if(bookHit) { // [HGM] book: simulate book reply
12736 static char bookMove[MSG_SIZ]; // a bit generous?
12738 programStats.nodes = programStats.depth = programStats.time =
12739 programStats.score = programStats.got_only_move = 0;
12740 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12742 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12743 strcat(bookMove, bookHit);
12744 savedMessage = bookMove; // args for deferred call
12745 savedState = onmove;
12746 ScheduleDelayedEvent(DeferredBookMove, 1);
12753 if (gameMode == Training) {
12754 SetTrainingModeOff();
12755 gameMode = PlayFromGameFile;
12756 DisplayMessage("", _("Training mode off"));
12758 gameMode = Training;
12759 animateTraining = appData.animate;
12761 /* make sure we are not already at the end of the game */
12762 if (currentMove < forwardMostMove) {
12763 SetTrainingModeOn();
12764 DisplayMessage("", _("Training mode on"));
12766 gameMode = PlayFromGameFile;
12767 DisplayError(_("Already at end of game"), 0);
12776 if (!appData.icsActive) return;
12777 switch (gameMode) {
12778 case IcsPlayingWhite:
12779 case IcsPlayingBlack:
12782 case BeginningOfGame:
12790 EditPositionDone(TRUE);
12803 gameMode = IcsIdle;
12814 switch (gameMode) {
12816 SetTrainingModeOff();
12818 case MachinePlaysWhite:
12819 case MachinePlaysBlack:
12820 case BeginningOfGame:
12821 SendToProgram("force\n", &first);
12822 SetUserThinkingEnables();
12824 case PlayFromGameFile:
12825 (void) StopLoadGameTimer();
12826 if (gameFileFP != NULL) {
12831 EditPositionDone(TRUE);
12836 SendToProgram("force\n", &first);
12838 case TwoMachinesPlay:
12839 GameEnds(EndOfFile, NULL, GE_PLAYER);
12840 ResurrectChessProgram();
12841 SetUserThinkingEnables();
12844 ResurrectChessProgram();
12846 case IcsPlayingBlack:
12847 case IcsPlayingWhite:
12848 DisplayError(_("Warning: You are still playing a game"), 0);
12851 DisplayError(_("Warning: You are still observing a game"), 0);
12854 DisplayError(_("Warning: You are still examining a game"), 0);
12865 first.offeredDraw = second.offeredDraw = 0;
12867 if (gameMode == PlayFromGameFile) {
12868 whiteTimeRemaining = timeRemaining[0][currentMove];
12869 blackTimeRemaining = timeRemaining[1][currentMove];
12873 if (gameMode == MachinePlaysWhite ||
12874 gameMode == MachinePlaysBlack ||
12875 gameMode == TwoMachinesPlay ||
12876 gameMode == EndOfGame) {
12877 i = forwardMostMove;
12878 while (i > currentMove) {
12879 SendToProgram("undo\n", &first);
12882 whiteTimeRemaining = timeRemaining[0][currentMove];
12883 blackTimeRemaining = timeRemaining[1][currentMove];
12884 DisplayBothClocks();
12885 if (whiteFlag || blackFlag) {
12886 whiteFlag = blackFlag = 0;
12891 gameMode = EditGame;
12898 EditPositionEvent()
12900 if (gameMode == EditPosition) {
12906 if (gameMode != EditGame) return;
12908 gameMode = EditPosition;
12911 if (currentMove > 0)
12912 CopyBoard(boards[0], boards[currentMove]);
12914 blackPlaysFirst = !WhiteOnMove(currentMove);
12916 currentMove = forwardMostMove = backwardMostMove = 0;
12917 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12924 /* [DM] icsEngineAnalyze - possible call from other functions */
12925 if (appData.icsEngineAnalyze) {
12926 appData.icsEngineAnalyze = FALSE;
12928 DisplayMessage("",_("Close ICS engine analyze..."));
12930 if (first.analysisSupport && first.analyzing) {
12931 SendToProgram("exit\n", &first);
12932 first.analyzing = FALSE;
12934 thinkOutput[0] = NULLCHAR;
12938 EditPositionDone(Boolean fakeRights)
12940 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12942 startedFromSetupPosition = TRUE;
12943 InitChessProgram(&first, FALSE);
12944 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12945 boards[0][EP_STATUS] = EP_NONE;
12946 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12947 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12948 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12949 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12950 } else boards[0][CASTLING][2] = NoRights;
12951 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12952 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12953 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12954 } else boards[0][CASTLING][5] = NoRights;
12956 SendToProgram("force\n", &first);
12957 if (blackPlaysFirst) {
12958 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12959 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12960 currentMove = forwardMostMove = backwardMostMove = 1;
12961 CopyBoard(boards[1], boards[0]);
12963 currentMove = forwardMostMove = backwardMostMove = 0;
12965 SendBoard(&first, forwardMostMove);
12966 if (appData.debugMode) {
12967 fprintf(debugFP, "EditPosDone\n");
12970 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12971 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12972 gameMode = EditGame;
12974 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12975 ClearHighlights(); /* [AS] */
12978 /* Pause for `ms' milliseconds */
12979 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12989 } while (SubtractTimeMarks(&m2, &m1) < ms);
12992 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12994 SendMultiLineToICS(buf)
12997 char temp[MSG_SIZ+1], *p;
13004 strncpy(temp, buf, len);
13009 if (*p == '\n' || *p == '\r')
13014 strcat(temp, "\n");
13016 SendToPlayer(temp, strlen(temp));
13020 SetWhiteToPlayEvent()
13022 if (gameMode == EditPosition) {
13023 blackPlaysFirst = FALSE;
13024 DisplayBothClocks(); /* works because currentMove is 0 */
13025 } else if (gameMode == IcsExamining) {
13026 SendToICS(ics_prefix);
13027 SendToICS("tomove white\n");
13032 SetBlackToPlayEvent()
13034 if (gameMode == EditPosition) {
13035 blackPlaysFirst = TRUE;
13036 currentMove = 1; /* kludge */
13037 DisplayBothClocks();
13039 } else if (gameMode == IcsExamining) {
13040 SendToICS(ics_prefix);
13041 SendToICS("tomove black\n");
13046 EditPositionMenuEvent(selection, x, y)
13047 ChessSquare selection;
13051 ChessSquare piece = boards[0][y][x];
13053 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13055 switch (selection) {
13057 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13058 SendToICS(ics_prefix);
13059 SendToICS("bsetup clear\n");
13060 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13061 SendToICS(ics_prefix);
13062 SendToICS("clearboard\n");
13064 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13065 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13066 for (y = 0; y < BOARD_HEIGHT; y++) {
13067 if (gameMode == IcsExamining) {
13068 if (boards[currentMove][y][x] != EmptySquare) {
13069 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13074 boards[0][y][x] = p;
13079 if (gameMode == EditPosition) {
13080 DrawPosition(FALSE, boards[0]);
13085 SetWhiteToPlayEvent();
13089 SetBlackToPlayEvent();
13093 if (gameMode == IcsExamining) {
13094 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13095 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13098 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13099 if(x == BOARD_LEFT-2) {
13100 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13101 boards[0][y][1] = 0;
13103 if(x == BOARD_RGHT+1) {
13104 if(y >= gameInfo.holdingsSize) break;
13105 boards[0][y][BOARD_WIDTH-2] = 0;
13108 boards[0][y][x] = EmptySquare;
13109 DrawPosition(FALSE, boards[0]);
13114 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13115 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13116 selection = (ChessSquare) (PROMOTED piece);
13117 } else if(piece == EmptySquare) selection = WhiteSilver;
13118 else selection = (ChessSquare)((int)piece - 1);
13122 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13123 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13124 selection = (ChessSquare) (DEMOTED piece);
13125 } else if(piece == EmptySquare) selection = BlackSilver;
13126 else selection = (ChessSquare)((int)piece + 1);
13131 if(gameInfo.variant == VariantShatranj ||
13132 gameInfo.variant == VariantXiangqi ||
13133 gameInfo.variant == VariantCourier ||
13134 gameInfo.variant == VariantMakruk )
13135 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13140 if(gameInfo.variant == VariantXiangqi)
13141 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13142 if(gameInfo.variant == VariantKnightmate)
13143 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13146 if (gameMode == IcsExamining) {
13147 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13148 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13149 PieceToChar(selection), AAA + x, ONE + y);
13152 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13154 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13155 n = PieceToNumber(selection - BlackPawn);
13156 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13157 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13158 boards[0][BOARD_HEIGHT-1-n][1]++;
13160 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13161 n = PieceToNumber(selection);
13162 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13163 boards[0][n][BOARD_WIDTH-1] = selection;
13164 boards[0][n][BOARD_WIDTH-2]++;
13167 boards[0][y][x] = selection;
13168 DrawPosition(TRUE, boards[0]);
13176 DropMenuEvent(selection, x, y)
13177 ChessSquare selection;
13180 ChessMove moveType;
13182 switch (gameMode) {
13183 case IcsPlayingWhite:
13184 case MachinePlaysBlack:
13185 if (!WhiteOnMove(currentMove)) {
13186 DisplayMoveError(_("It is Black's turn"));
13189 moveType = WhiteDrop;
13191 case IcsPlayingBlack:
13192 case MachinePlaysWhite:
13193 if (WhiteOnMove(currentMove)) {
13194 DisplayMoveError(_("It is White's turn"));
13197 moveType = BlackDrop;
13200 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13206 if (moveType == BlackDrop && selection < BlackPawn) {
13207 selection = (ChessSquare) ((int) selection
13208 + (int) BlackPawn - (int) WhitePawn);
13210 if (boards[currentMove][y][x] != EmptySquare) {
13211 DisplayMoveError(_("That square is occupied"));
13215 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13221 /* Accept a pending offer of any kind from opponent */
13223 if (appData.icsActive) {
13224 SendToICS(ics_prefix);
13225 SendToICS("accept\n");
13226 } else if (cmailMsgLoaded) {
13227 if (currentMove == cmailOldMove &&
13228 commentList[cmailOldMove] != NULL &&
13229 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13230 "Black offers a draw" : "White offers a draw")) {
13232 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13233 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13235 DisplayError(_("There is no pending offer on this move"), 0);
13236 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13239 /* Not used for offers from chess program */
13246 /* Decline a pending offer of any kind from opponent */
13248 if (appData.icsActive) {
13249 SendToICS(ics_prefix);
13250 SendToICS("decline\n");
13251 } else if (cmailMsgLoaded) {
13252 if (currentMove == cmailOldMove &&
13253 commentList[cmailOldMove] != NULL &&
13254 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13255 "Black offers a draw" : "White offers a draw")) {
13257 AppendComment(cmailOldMove, "Draw declined", TRUE);
13258 DisplayComment(cmailOldMove - 1, "Draw declined");
13261 DisplayError(_("There is no pending offer on this move"), 0);
13264 /* Not used for offers from chess program */
13271 /* Issue ICS rematch command */
13272 if (appData.icsActive) {
13273 SendToICS(ics_prefix);
13274 SendToICS("rematch\n");
13281 /* Call your opponent's flag (claim a win on time) */
13282 if (appData.icsActive) {
13283 SendToICS(ics_prefix);
13284 SendToICS("flag\n");
13286 switch (gameMode) {
13289 case MachinePlaysWhite:
13292 GameEnds(GameIsDrawn, "Both players ran out of time",
13295 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13297 DisplayError(_("Your opponent is not out of time"), 0);
13300 case MachinePlaysBlack:
13303 GameEnds(GameIsDrawn, "Both players ran out of time",
13306 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13308 DisplayError(_("Your opponent is not out of time"), 0);
13316 ClockClick(int which)
13317 { // [HGM] code moved to back-end from winboard.c
13318 if(which) { // black clock
13319 if (gameMode == EditPosition || gameMode == IcsExamining) {
13320 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13321 SetBlackToPlayEvent();
13322 } else if (gameMode == EditGame || shiftKey) {
13323 AdjustClock(which, -1);
13324 } else if (gameMode == IcsPlayingWhite ||
13325 gameMode == MachinePlaysBlack) {
13328 } else { // white clock
13329 if (gameMode == EditPosition || gameMode == IcsExamining) {
13330 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13331 SetWhiteToPlayEvent();
13332 } else if (gameMode == EditGame || shiftKey) {
13333 AdjustClock(which, -1);
13334 } else if (gameMode == IcsPlayingBlack ||
13335 gameMode == MachinePlaysWhite) {
13344 /* Offer draw or accept pending draw offer from opponent */
13346 if (appData.icsActive) {
13347 /* Note: tournament rules require draw offers to be
13348 made after you make your move but before you punch
13349 your clock. Currently ICS doesn't let you do that;
13350 instead, you immediately punch your clock after making
13351 a move, but you can offer a draw at any time. */
13353 SendToICS(ics_prefix);
13354 SendToICS("draw\n");
13355 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13356 } else if (cmailMsgLoaded) {
13357 if (currentMove == cmailOldMove &&
13358 commentList[cmailOldMove] != NULL &&
13359 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13360 "Black offers a draw" : "White offers a draw")) {
13361 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13362 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13363 } else if (currentMove == cmailOldMove + 1) {
13364 char *offer = WhiteOnMove(cmailOldMove) ?
13365 "White offers a draw" : "Black offers a draw";
13366 AppendComment(currentMove, offer, TRUE);
13367 DisplayComment(currentMove - 1, offer);
13368 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13370 DisplayError(_("You must make your move before offering a draw"), 0);
13371 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13373 } else if (first.offeredDraw) {
13374 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13376 if (first.sendDrawOffers) {
13377 SendToProgram("draw\n", &first);
13378 userOfferedDraw = TRUE;
13386 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13388 if (appData.icsActive) {
13389 SendToICS(ics_prefix);
13390 SendToICS("adjourn\n");
13392 /* Currently GNU Chess doesn't offer or accept Adjourns */
13400 /* Offer Abort or accept pending Abort offer from opponent */
13402 if (appData.icsActive) {
13403 SendToICS(ics_prefix);
13404 SendToICS("abort\n");
13406 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13413 /* Resign. You can do this even if it's not your turn. */
13415 if (appData.icsActive) {
13416 SendToICS(ics_prefix);
13417 SendToICS("resign\n");
13419 switch (gameMode) {
13420 case MachinePlaysWhite:
13421 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13423 case MachinePlaysBlack:
13424 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13427 if (cmailMsgLoaded) {
13429 if (WhiteOnMove(cmailOldMove)) {
13430 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13432 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13434 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13445 StopObservingEvent()
13447 /* Stop observing current games */
13448 SendToICS(ics_prefix);
13449 SendToICS("unobserve\n");
13453 StopExaminingEvent()
13455 /* Stop observing current game */
13456 SendToICS(ics_prefix);
13457 SendToICS("unexamine\n");
13461 ForwardInner(target)
13466 if (appData.debugMode)
13467 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13468 target, currentMove, forwardMostMove);
13470 if (gameMode == EditPosition)
13473 if (gameMode == PlayFromGameFile && !pausing)
13476 if (gameMode == IcsExamining && pausing)
13477 limit = pauseExamForwardMostMove;
13479 limit = forwardMostMove;
13481 if (target > limit) target = limit;
13483 if (target > 0 && moveList[target - 1][0]) {
13484 int fromX, fromY, toX, toY;
13485 toX = moveList[target - 1][2] - AAA;
13486 toY = moveList[target - 1][3] - ONE;
13487 if (moveList[target - 1][1] == '@') {
13488 if (appData.highlightLastMove) {
13489 SetHighlights(-1, -1, toX, toY);
13492 fromX = moveList[target - 1][0] - AAA;
13493 fromY = moveList[target - 1][1] - ONE;
13494 if (target == currentMove + 1) {
13495 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13497 if (appData.highlightLastMove) {
13498 SetHighlights(fromX, fromY, toX, toY);
13502 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13503 gameMode == Training || gameMode == PlayFromGameFile ||
13504 gameMode == AnalyzeFile) {
13505 while (currentMove < target) {
13506 SendMoveToProgram(currentMove++, &first);
13509 currentMove = target;
13512 if (gameMode == EditGame || gameMode == EndOfGame) {
13513 whiteTimeRemaining = timeRemaining[0][currentMove];
13514 blackTimeRemaining = timeRemaining[1][currentMove];
13516 DisplayBothClocks();
13517 DisplayMove(currentMove - 1);
13518 DrawPosition(FALSE, boards[currentMove]);
13519 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13520 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13521 DisplayComment(currentMove - 1, commentList[currentMove]);
13523 DisplayBook(currentMove);
13530 if (gameMode == IcsExamining && !pausing) {
13531 SendToICS(ics_prefix);
13532 SendToICS("forward\n");
13534 ForwardInner(currentMove + 1);
13541 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13542 /* to optimze, we temporarily turn off analysis mode while we feed
13543 * the remaining moves to the engine. Otherwise we get analysis output
13546 if (first.analysisSupport) {
13547 SendToProgram("exit\nforce\n", &first);
13548 first.analyzing = FALSE;
13552 if (gameMode == IcsExamining && !pausing) {
13553 SendToICS(ics_prefix);
13554 SendToICS("forward 999999\n");
13556 ForwardInner(forwardMostMove);
13559 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13560 /* we have fed all the moves, so reactivate analysis mode */
13561 SendToProgram("analyze\n", &first);
13562 first.analyzing = TRUE;
13563 /*first.maybeThinking = TRUE;*/
13564 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13569 BackwardInner(target)
13572 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13574 if (appData.debugMode)
13575 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13576 target, currentMove, forwardMostMove);
13578 if (gameMode == EditPosition) return;
13579 if (currentMove <= backwardMostMove) {
13581 DrawPosition(full_redraw, boards[currentMove]);
13584 if (gameMode == PlayFromGameFile && !pausing)
13587 if (moveList[target][0]) {
13588 int fromX, fromY, toX, toY;
13589 toX = moveList[target][2] - AAA;
13590 toY = moveList[target][3] - ONE;
13591 if (moveList[target][1] == '@') {
13592 if (appData.highlightLastMove) {
13593 SetHighlights(-1, -1, toX, toY);
13596 fromX = moveList[target][0] - AAA;
13597 fromY = moveList[target][1] - ONE;
13598 if (target == currentMove - 1) {
13599 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13601 if (appData.highlightLastMove) {
13602 SetHighlights(fromX, fromY, toX, toY);
13606 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13607 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13608 while (currentMove > target) {
13609 SendToProgram("undo\n", &first);
13613 currentMove = target;
13616 if (gameMode == EditGame || gameMode == EndOfGame) {
13617 whiteTimeRemaining = timeRemaining[0][currentMove];
13618 blackTimeRemaining = timeRemaining[1][currentMove];
13620 DisplayBothClocks();
13621 DisplayMove(currentMove - 1);
13622 DrawPosition(full_redraw, boards[currentMove]);
13623 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13624 // [HGM] PV info: routine tests if comment empty
13625 DisplayComment(currentMove - 1, commentList[currentMove]);
13626 DisplayBook(currentMove);
13632 if (gameMode == IcsExamining && !pausing) {
13633 SendToICS(ics_prefix);
13634 SendToICS("backward\n");
13636 BackwardInner(currentMove - 1);
13643 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13644 /* to optimize, we temporarily turn off analysis mode while we undo
13645 * all the moves. Otherwise we get analysis output after each undo.
13647 if (first.analysisSupport) {
13648 SendToProgram("exit\nforce\n", &first);
13649 first.analyzing = FALSE;
13653 if (gameMode == IcsExamining && !pausing) {
13654 SendToICS(ics_prefix);
13655 SendToICS("backward 999999\n");
13657 BackwardInner(backwardMostMove);
13660 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13661 /* we have fed all the moves, so reactivate analysis mode */
13662 SendToProgram("analyze\n", &first);
13663 first.analyzing = TRUE;
13664 /*first.maybeThinking = TRUE;*/
13665 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13672 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13673 if (to >= forwardMostMove) to = forwardMostMove;
13674 if (to <= backwardMostMove) to = backwardMostMove;
13675 if (to < currentMove) {
13683 RevertEvent(Boolean annotate)
13685 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13688 if (gameMode != IcsExamining) {
13689 DisplayError(_("You are not examining a game"), 0);
13693 DisplayError(_("You can't revert while pausing"), 0);
13696 SendToICS(ics_prefix);
13697 SendToICS("revert\n");
13703 switch (gameMode) {
13704 case MachinePlaysWhite:
13705 case MachinePlaysBlack:
13706 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13707 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13710 if (forwardMostMove < 2) return;
13711 currentMove = forwardMostMove = forwardMostMove - 2;
13712 whiteTimeRemaining = timeRemaining[0][currentMove];
13713 blackTimeRemaining = timeRemaining[1][currentMove];
13714 DisplayBothClocks();
13715 DisplayMove(currentMove - 1);
13716 ClearHighlights();/*!! could figure this out*/
13717 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13718 SendToProgram("remove\n", &first);
13719 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13722 case BeginningOfGame:
13726 case IcsPlayingWhite:
13727 case IcsPlayingBlack:
13728 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13729 SendToICS(ics_prefix);
13730 SendToICS("takeback 2\n");
13732 SendToICS(ics_prefix);
13733 SendToICS("takeback 1\n");
13742 ChessProgramState *cps;
13744 switch (gameMode) {
13745 case MachinePlaysWhite:
13746 if (!WhiteOnMove(forwardMostMove)) {
13747 DisplayError(_("It is your turn"), 0);
13752 case MachinePlaysBlack:
13753 if (WhiteOnMove(forwardMostMove)) {
13754 DisplayError(_("It is your turn"), 0);
13759 case TwoMachinesPlay:
13760 if (WhiteOnMove(forwardMostMove) ==
13761 (first.twoMachinesColor[0] == 'w')) {
13767 case BeginningOfGame:
13771 SendToProgram("?\n", cps);
13775 TruncateGameEvent()
13778 if (gameMode != EditGame) return;
13785 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13786 if (forwardMostMove > currentMove) {
13787 if (gameInfo.resultDetails != NULL) {
13788 free(gameInfo.resultDetails);
13789 gameInfo.resultDetails = NULL;
13790 gameInfo.result = GameUnfinished;
13792 forwardMostMove = currentMove;
13793 HistorySet(parseList, backwardMostMove, forwardMostMove,
13801 if (appData.noChessProgram) return;
13802 switch (gameMode) {
13803 case MachinePlaysWhite:
13804 if (WhiteOnMove(forwardMostMove)) {
13805 DisplayError(_("Wait until your turn"), 0);
13809 case BeginningOfGame:
13810 case MachinePlaysBlack:
13811 if (!WhiteOnMove(forwardMostMove)) {
13812 DisplayError(_("Wait until your turn"), 0);
13817 DisplayError(_("No hint available"), 0);
13820 SendToProgram("hint\n", &first);
13821 hintRequested = TRUE;
13827 if (appData.noChessProgram) return;
13828 switch (gameMode) {
13829 case MachinePlaysWhite:
13830 if (WhiteOnMove(forwardMostMove)) {
13831 DisplayError(_("Wait until your turn"), 0);
13835 case BeginningOfGame:
13836 case MachinePlaysBlack:
13837 if (!WhiteOnMove(forwardMostMove)) {
13838 DisplayError(_("Wait until your turn"), 0);
13843 EditPositionDone(TRUE);
13845 case TwoMachinesPlay:
13850 SendToProgram("bk\n", &first);
13851 bookOutput[0] = NULLCHAR;
13852 bookRequested = TRUE;
13858 char *tags = PGNTags(&gameInfo);
13859 TagsPopUp(tags, CmailMsg());
13863 /* end button procedures */
13866 PrintPosition(fp, move)
13872 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13873 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13874 char c = PieceToChar(boards[move][i][j]);
13875 fputc(c == 'x' ? '.' : c, fp);
13876 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13879 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13880 fprintf(fp, "white to play\n");
13882 fprintf(fp, "black to play\n");
13889 if (gameInfo.white != NULL) {
13890 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13896 /* Find last component of program's own name, using some heuristics */
13898 TidyProgramName(prog, host, buf)
13899 char *prog, *host, buf[MSG_SIZ];
13902 int local = (strcmp(host, "localhost") == 0);
13903 while (!local && (p = strchr(prog, ';')) != NULL) {
13905 while (*p == ' ') p++;
13908 if (*prog == '"' || *prog == '\'') {
13909 q = strchr(prog + 1, *prog);
13911 q = strchr(prog, ' ');
13913 if (q == NULL) q = prog + strlen(prog);
13915 while (p >= prog && *p != '/' && *p != '\\') p--;
13917 if(p == prog && *p == '"') p++;
13918 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13919 memcpy(buf, p, q - p);
13920 buf[q - p] = NULLCHAR;
13928 TimeControlTagValue()
13931 if (!appData.clockMode) {
13932 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13933 } else if (movesPerSession > 0) {
13934 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13935 } else if (timeIncrement == 0) {
13936 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13938 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13940 return StrSave(buf);
13946 /* This routine is used only for certain modes */
13947 VariantClass v = gameInfo.variant;
13948 ChessMove r = GameUnfinished;
13951 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13952 r = gameInfo.result;
13953 p = gameInfo.resultDetails;
13954 gameInfo.resultDetails = NULL;
13956 ClearGameInfo(&gameInfo);
13957 gameInfo.variant = v;
13959 switch (gameMode) {
13960 case MachinePlaysWhite:
13961 gameInfo.event = StrSave( appData.pgnEventHeader );
13962 gameInfo.site = StrSave(HostName());
13963 gameInfo.date = PGNDate();
13964 gameInfo.round = StrSave("-");
13965 gameInfo.white = StrSave(first.tidy);
13966 gameInfo.black = StrSave(UserName());
13967 gameInfo.timeControl = TimeControlTagValue();
13970 case MachinePlaysBlack:
13971 gameInfo.event = StrSave( appData.pgnEventHeader );
13972 gameInfo.site = StrSave(HostName());
13973 gameInfo.date = PGNDate();
13974 gameInfo.round = StrSave("-");
13975 gameInfo.white = StrSave(UserName());
13976 gameInfo.black = StrSave(first.tidy);
13977 gameInfo.timeControl = TimeControlTagValue();
13980 case TwoMachinesPlay:
13981 gameInfo.event = StrSave( appData.pgnEventHeader );
13982 gameInfo.site = StrSave(HostName());
13983 gameInfo.date = PGNDate();
13986 snprintf(buf, MSG_SIZ, "%d", roundNr);
13987 gameInfo.round = StrSave(buf);
13989 gameInfo.round = StrSave("-");
13991 if (first.twoMachinesColor[0] == 'w') {
13992 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13993 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13995 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13996 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13998 gameInfo.timeControl = TimeControlTagValue();
14002 gameInfo.event = StrSave("Edited game");
14003 gameInfo.site = StrSave(HostName());
14004 gameInfo.date = PGNDate();
14005 gameInfo.round = StrSave("-");
14006 gameInfo.white = StrSave("-");
14007 gameInfo.black = StrSave("-");
14008 gameInfo.result = r;
14009 gameInfo.resultDetails = p;
14013 gameInfo.event = StrSave("Edited position");
14014 gameInfo.site = StrSave(HostName());
14015 gameInfo.date = PGNDate();
14016 gameInfo.round = StrSave("-");
14017 gameInfo.white = StrSave("-");
14018 gameInfo.black = StrSave("-");
14021 case IcsPlayingWhite:
14022 case IcsPlayingBlack:
14027 case PlayFromGameFile:
14028 gameInfo.event = StrSave("Game from non-PGN file");
14029 gameInfo.site = StrSave(HostName());
14030 gameInfo.date = PGNDate();
14031 gameInfo.round = StrSave("-");
14032 gameInfo.white = StrSave("?");
14033 gameInfo.black = StrSave("?");
14042 ReplaceComment(index, text)
14050 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14051 pvInfoList[index-1].depth == len &&
14052 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14053 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14054 while (*text == '\n') text++;
14055 len = strlen(text);
14056 while (len > 0 && text[len - 1] == '\n') len--;
14058 if (commentList[index] != NULL)
14059 free(commentList[index]);
14062 commentList[index] = NULL;
14065 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14066 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14067 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14068 commentList[index] = (char *) malloc(len + 2);
14069 strncpy(commentList[index], text, len);
14070 commentList[index][len] = '\n';
14071 commentList[index][len + 1] = NULLCHAR;
14073 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14075 commentList[index] = (char *) malloc(len + 7);
14076 safeStrCpy(commentList[index], "{\n", 3);
14077 safeStrCpy(commentList[index]+2, text, len+1);
14078 commentList[index][len+2] = NULLCHAR;
14079 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14080 strcat(commentList[index], "\n}\n");
14094 if (ch == '\r') continue;
14096 } while (ch != '\0');
14100 AppendComment(index, text, addBraces)
14103 Boolean addBraces; // [HGM] braces: tells if we should add {}
14108 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14109 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14112 while (*text == '\n') text++;
14113 len = strlen(text);
14114 while (len > 0 && text[len - 1] == '\n') len--;
14116 if (len == 0) return;
14118 if (commentList[index] != NULL) {
14119 old = commentList[index];
14120 oldlen = strlen(old);
14121 while(commentList[index][oldlen-1] == '\n')
14122 commentList[index][--oldlen] = NULLCHAR;
14123 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14124 safeStrCpy(commentList[index], old, oldlen + len + 6);
14126 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14127 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14128 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14129 while (*text == '\n') { text++; len--; }
14130 commentList[index][--oldlen] = NULLCHAR;
14132 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14133 else strcat(commentList[index], "\n");
14134 strcat(commentList[index], text);
14135 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14136 else strcat(commentList[index], "\n");
14138 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14140 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14141 else commentList[index][0] = NULLCHAR;
14142 strcat(commentList[index], text);
14143 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14144 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14148 static char * FindStr( char * text, char * sub_text )
14150 char * result = strstr( text, sub_text );
14152 if( result != NULL ) {
14153 result += strlen( sub_text );
14159 /* [AS] Try to extract PV info from PGN comment */
14160 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14161 char *GetInfoFromComment( int index, char * text )
14163 char * sep = text, *p;
14165 if( text != NULL && index > 0 ) {
14168 int time = -1, sec = 0, deci;
14169 char * s_eval = FindStr( text, "[%eval " );
14170 char * s_emt = FindStr( text, "[%emt " );
14172 if( s_eval != NULL || s_emt != NULL ) {
14176 if( s_eval != NULL ) {
14177 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14181 if( delim != ']' ) {
14186 if( s_emt != NULL ) {
14191 /* We expect something like: [+|-]nnn.nn/dd */
14194 if(*text != '{') return text; // [HGM] braces: must be normal comment
14196 sep = strchr( text, '/' );
14197 if( sep == NULL || sep < (text+4) ) {
14202 if(p[1] == '(') { // comment starts with PV
14203 p = strchr(p, ')'); // locate end of PV
14204 if(p == NULL || sep < p+5) return text;
14205 // at this point we have something like "{(.*) +0.23/6 ..."
14206 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14207 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14208 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14210 time = -1; sec = -1; deci = -1;
14211 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14212 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14213 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14214 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14218 if( score_lo < 0 || score_lo >= 100 ) {
14222 if(sec >= 0) time = 600*time + 10*sec; else
14223 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14225 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14227 /* [HGM] PV time: now locate end of PV info */
14228 while( *++sep >= '0' && *sep <= '9'); // strip depth
14230 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14232 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14234 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14235 while(*sep == ' ') sep++;
14246 pvInfoList[index-1].depth = depth;
14247 pvInfoList[index-1].score = score;
14248 pvInfoList[index-1].time = 10*time; // centi-sec
14249 if(*sep == '}') *sep = 0; else *--sep = '{';
14250 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14256 SendToProgram(message, cps)
14258 ChessProgramState *cps;
14260 int count, outCount, error;
14263 if (cps->pr == NULL) return;
14266 if (appData.debugMode) {
14269 fprintf(debugFP, "%ld >%-6s: %s",
14270 SubtractTimeMarks(&now, &programStartTime),
14271 cps->which, message);
14274 count = strlen(message);
14275 outCount = OutputToProcess(cps->pr, message, count, &error);
14276 if (outCount < count && !exiting
14277 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14278 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14279 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14280 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14281 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14282 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14283 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14284 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14286 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14287 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14288 gameInfo.result = res;
14290 gameInfo.resultDetails = StrSave(buf);
14292 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14293 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14298 ReceiveFromProgram(isr, closure, message, count, error)
14299 InputSourceRef isr;
14307 ChessProgramState *cps = (ChessProgramState *)closure;
14309 if (isr != cps->isr) return; /* Killed intentionally */
14312 RemoveInputSource(cps->isr);
14313 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14314 _(cps->which), cps->program);
14315 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14316 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14317 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14318 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14319 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14321 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14322 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14323 gameInfo.result = res;
14325 gameInfo.resultDetails = StrSave(buf);
14327 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14328 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14330 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14331 _(cps->which), cps->program);
14332 RemoveInputSource(cps->isr);
14334 /* [AS] Program is misbehaving badly... kill it */
14335 if( count == -2 ) {
14336 DestroyChildProcess( cps->pr, 9 );
14340 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14345 if ((end_str = strchr(message, '\r')) != NULL)
14346 *end_str = NULLCHAR;
14347 if ((end_str = strchr(message, '\n')) != NULL)
14348 *end_str = NULLCHAR;
14350 if (appData.debugMode) {
14351 TimeMark now; int print = 1;
14352 char *quote = ""; char c; int i;
14354 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14355 char start = message[0];
14356 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14357 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14358 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14359 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14360 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14361 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14362 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14363 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14364 sscanf(message, "hint: %c", &c)!=1 &&
14365 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14366 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14367 print = (appData.engineComments >= 2);
14369 message[0] = start; // restore original message
14373 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14374 SubtractTimeMarks(&now, &programStartTime), cps->which,
14380 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14381 if (appData.icsEngineAnalyze) {
14382 if (strstr(message, "whisper") != NULL ||
14383 strstr(message, "kibitz") != NULL ||
14384 strstr(message, "tellics") != NULL) return;
14387 HandleMachineMove(message, cps);
14392 SendTimeControl(cps, mps, tc, inc, sd, st)
14393 ChessProgramState *cps;
14394 int mps, inc, sd, st;
14400 if( timeControl_2 > 0 ) {
14401 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14402 tc = timeControl_2;
14405 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14406 inc /= cps->timeOdds;
14407 st /= cps->timeOdds;
14409 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14412 /* Set exact time per move, normally using st command */
14413 if (cps->stKludge) {
14414 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14416 if (seconds == 0) {
14417 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14419 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14422 snprintf(buf, MSG_SIZ, "st %d\n", st);
14425 /* Set conventional or incremental time control, using level command */
14426 if (seconds == 0) {
14427 /* Note old gnuchess bug -- minutes:seconds used to not work.
14428 Fixed in later versions, but still avoid :seconds
14429 when seconds is 0. */
14430 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14432 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14433 seconds, inc/1000.);
14436 SendToProgram(buf, cps);
14438 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14439 /* Orthogonally, limit search to given depth */
14441 if (cps->sdKludge) {
14442 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14444 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14446 SendToProgram(buf, cps);
14449 if(cps->nps >= 0) { /* [HGM] nps */
14450 if(cps->supportsNPS == FALSE)
14451 cps->nps = -1; // don't use if engine explicitly says not supported!
14453 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14454 SendToProgram(buf, cps);
14459 ChessProgramState *WhitePlayer()
14460 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14462 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14463 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14469 SendTimeRemaining(cps, machineWhite)
14470 ChessProgramState *cps;
14471 int /*boolean*/ machineWhite;
14473 char message[MSG_SIZ];
14476 /* Note: this routine must be called when the clocks are stopped
14477 or when they have *just* been set or switched; otherwise
14478 it will be off by the time since the current tick started.
14480 if (machineWhite) {
14481 time = whiteTimeRemaining / 10;
14482 otime = blackTimeRemaining / 10;
14484 time = blackTimeRemaining / 10;
14485 otime = whiteTimeRemaining / 10;
14487 /* [HGM] translate opponent's time by time-odds factor */
14488 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14489 if (appData.debugMode) {
14490 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14493 if (time <= 0) time = 1;
14494 if (otime <= 0) otime = 1;
14496 snprintf(message, MSG_SIZ, "time %ld\n", time);
14497 SendToProgram(message, cps);
14499 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14500 SendToProgram(message, cps);
14504 BoolFeature(p, name, loc, cps)
14508 ChessProgramState *cps;
14511 int len = strlen(name);
14514 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14516 sscanf(*p, "%d", &val);
14518 while (**p && **p != ' ')
14520 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14521 SendToProgram(buf, cps);
14528 IntFeature(p, name, loc, cps)
14532 ChessProgramState *cps;
14535 int len = strlen(name);
14536 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14538 sscanf(*p, "%d", loc);
14539 while (**p && **p != ' ') (*p)++;
14540 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14541 SendToProgram(buf, cps);
14548 StringFeature(p, name, loc, cps)
14552 ChessProgramState *cps;
14555 int len = strlen(name);
14556 if (strncmp((*p), name, len) == 0
14557 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14559 sscanf(*p, "%[^\"]", loc);
14560 while (**p && **p != '\"') (*p)++;
14561 if (**p == '\"') (*p)++;
14562 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14563 SendToProgram(buf, cps);
14570 ParseOption(Option *opt, ChessProgramState *cps)
14571 // [HGM] options: process the string that defines an engine option, and determine
14572 // name, type, default value, and allowed value range
14574 char *p, *q, buf[MSG_SIZ];
14575 int n, min = (-1)<<31, max = 1<<31, def;
14577 if(p = strstr(opt->name, " -spin ")) {
14578 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14579 if(max < min) max = min; // enforce consistency
14580 if(def < min) def = min;
14581 if(def > max) def = max;
14586 } else if((p = strstr(opt->name, " -slider "))) {
14587 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14588 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14589 if(max < min) max = min; // enforce consistency
14590 if(def < min) def = min;
14591 if(def > max) def = max;
14595 opt->type = Spin; // Slider;
14596 } else if((p = strstr(opt->name, " -string "))) {
14597 opt->textValue = p+9;
14598 opt->type = TextBox;
14599 } else if((p = strstr(opt->name, " -file "))) {
14600 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14601 opt->textValue = p+7;
14602 opt->type = FileName; // FileName;
14603 } else if((p = strstr(opt->name, " -path "))) {
14604 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14605 opt->textValue = p+7;
14606 opt->type = PathName; // PathName;
14607 } else if(p = strstr(opt->name, " -check ")) {
14608 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14609 opt->value = (def != 0);
14610 opt->type = CheckBox;
14611 } else if(p = strstr(opt->name, " -combo ")) {
14612 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14613 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14614 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14615 opt->value = n = 0;
14616 while(q = StrStr(q, " /// ")) {
14617 n++; *q = 0; // count choices, and null-terminate each of them
14619 if(*q == '*') { // remember default, which is marked with * prefix
14623 cps->comboList[cps->comboCnt++] = q;
14625 cps->comboList[cps->comboCnt++] = NULL;
14627 opt->type = ComboBox;
14628 } else if(p = strstr(opt->name, " -button")) {
14629 opt->type = Button;
14630 } else if(p = strstr(opt->name, " -save")) {
14631 opt->type = SaveButton;
14632 } else return FALSE;
14633 *p = 0; // terminate option name
14634 // now look if the command-line options define a setting for this engine option.
14635 if(cps->optionSettings && cps->optionSettings[0])
14636 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14637 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14638 snprintf(buf, MSG_SIZ, "option %s", p);
14639 if(p = strstr(buf, ",")) *p = 0;
14640 if(q = strchr(buf, '=')) switch(opt->type) {
14642 for(n=0; n<opt->max; n++)
14643 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14646 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14650 opt->value = atoi(q+1);
14655 SendToProgram(buf, cps);
14661 FeatureDone(cps, val)
14662 ChessProgramState* cps;
14665 DelayedEventCallback cb = GetDelayedEvent();
14666 if ((cb == InitBackEnd3 && cps == &first) ||
14667 (cb == SettingsMenuIfReady && cps == &second) ||
14668 (cb == LoadEngine) ||
14669 (cb == TwoMachinesEventIfReady)) {
14670 CancelDelayedEvent();
14671 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14673 cps->initDone = val;
14676 /* Parse feature command from engine */
14678 ParseFeatures(args, cps)
14680 ChessProgramState *cps;
14688 while (*p == ' ') p++;
14689 if (*p == NULLCHAR) return;
14691 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14692 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14693 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14694 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14695 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14696 if (BoolFeature(&p, "reuse", &val, cps)) {
14697 /* Engine can disable reuse, but can't enable it if user said no */
14698 if (!val) cps->reuse = FALSE;
14701 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14702 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14703 if (gameMode == TwoMachinesPlay) {
14704 DisplayTwoMachinesTitle();
14710 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14711 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14712 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14713 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14714 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14715 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14716 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14717 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14718 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14719 if (IntFeature(&p, "done", &val, cps)) {
14720 FeatureDone(cps, val);
14723 /* Added by Tord: */
14724 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14725 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14726 /* End of additions by Tord */
14728 /* [HGM] added features: */
14729 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14730 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14731 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14732 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14733 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14734 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14735 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14736 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14737 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14738 SendToProgram(buf, cps);
14741 if(cps->nrOptions >= MAX_OPTIONS) {
14743 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14744 DisplayError(buf, 0);
14748 /* End of additions by HGM */
14750 /* unknown feature: complain and skip */
14752 while (*q && *q != '=') q++;
14753 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14754 SendToProgram(buf, cps);
14760 while (*p && *p != '\"') p++;
14761 if (*p == '\"') p++;
14763 while (*p && *p != ' ') p++;
14771 PeriodicUpdatesEvent(newState)
14774 if (newState == appData.periodicUpdates)
14777 appData.periodicUpdates=newState;
14779 /* Display type changes, so update it now */
14780 // DisplayAnalysis();
14782 /* Get the ball rolling again... */
14784 AnalysisPeriodicEvent(1);
14785 StartAnalysisClock();
14790 PonderNextMoveEvent(newState)
14793 if (newState == appData.ponderNextMove) return;
14794 if (gameMode == EditPosition) EditPositionDone(TRUE);
14796 SendToProgram("hard\n", &first);
14797 if (gameMode == TwoMachinesPlay) {
14798 SendToProgram("hard\n", &second);
14801 SendToProgram("easy\n", &first);
14802 thinkOutput[0] = NULLCHAR;
14803 if (gameMode == TwoMachinesPlay) {
14804 SendToProgram("easy\n", &second);
14807 appData.ponderNextMove = newState;
14811 NewSettingEvent(option, feature, command, value)
14813 int option, value, *feature;
14817 if (gameMode == EditPosition) EditPositionDone(TRUE);
14818 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14819 if(feature == NULL || *feature) SendToProgram(buf, &first);
14820 if (gameMode == TwoMachinesPlay) {
14821 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14826 ShowThinkingEvent()
14827 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14829 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14830 int newState = appData.showThinking
14831 // [HGM] thinking: other features now need thinking output as well
14832 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14834 if (oldState == newState) return;
14835 oldState = newState;
14836 if (gameMode == EditPosition) EditPositionDone(TRUE);
14838 SendToProgram("post\n", &first);
14839 if (gameMode == TwoMachinesPlay) {
14840 SendToProgram("post\n", &second);
14843 SendToProgram("nopost\n", &first);
14844 thinkOutput[0] = NULLCHAR;
14845 if (gameMode == TwoMachinesPlay) {
14846 SendToProgram("nopost\n", &second);
14849 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14853 AskQuestionEvent(title, question, replyPrefix, which)
14854 char *title; char *question; char *replyPrefix; char *which;
14856 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14857 if (pr == NoProc) return;
14858 AskQuestion(title, question, replyPrefix, pr);
14862 TypeInEvent(char firstChar)
14864 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14865 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14866 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14867 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14868 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14869 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14870 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14871 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14872 gameMode == Training) PopUpMoveDialog(firstChar);
14876 TypeInDoneEvent(char *move)
14879 int n, fromX, fromY, toX, toY;
14881 ChessMove moveType;
\r
14884 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14885 EditPositionPasteFEN(move);
\r
14888 // [HGM] movenum: allow move number to be typed in any mode
\r
14889 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14890 ToNrEvent(2*n-1);
\r
14894 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14895 gameMode != Training) {
\r
14896 DisplayMoveError(_("Displayed move is not current"));
\r
14898 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14899 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14900 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14901 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14902 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14903 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14905 DisplayMoveError(_("Could not parse move"));
\r
14911 DisplayMove(moveNumber)
14914 char message[MSG_SIZ];
14916 char cpThinkOutput[MSG_SIZ];
14918 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14920 if (moveNumber == forwardMostMove - 1 ||
14921 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14923 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14925 if (strchr(cpThinkOutput, '\n')) {
14926 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14929 *cpThinkOutput = NULLCHAR;
14932 /* [AS] Hide thinking from human user */
14933 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14934 *cpThinkOutput = NULLCHAR;
14935 if( thinkOutput[0] != NULLCHAR ) {
14938 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14939 cpThinkOutput[i] = '.';
14941 cpThinkOutput[i] = NULLCHAR;
14942 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14946 if (moveNumber == forwardMostMove - 1 &&
14947 gameInfo.resultDetails != NULL) {
14948 if (gameInfo.resultDetails[0] == NULLCHAR) {
14949 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14951 snprintf(res, MSG_SIZ, " {%s} %s",
14952 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14958 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14959 DisplayMessage(res, cpThinkOutput);
14961 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14962 WhiteOnMove(moveNumber) ? " " : ".. ",
14963 parseList[moveNumber], res);
14964 DisplayMessage(message, cpThinkOutput);
14969 DisplayComment(moveNumber, text)
14973 char title[MSG_SIZ];
14974 char buf[8000]; // comment can be long!
14977 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14978 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14980 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14981 WhiteOnMove(moveNumber) ? " " : ".. ",
14982 parseList[moveNumber]);
14984 // [HGM] PV info: display PV info together with (or as) comment
14985 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14986 if(text == NULL) text = "";
14987 score = pvInfoList[moveNumber].score;
14988 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14989 depth, (pvInfoList[moveNumber].time+50)/100, text);
14992 if (text != NULL && (appData.autoDisplayComment || commentUp))
14993 CommentPopUp(title, text);
14996 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14997 * might be busy thinking or pondering. It can be omitted if your
14998 * gnuchess is configured to stop thinking immediately on any user
14999 * input. However, that gnuchess feature depends on the FIONREAD
15000 * ioctl, which does not work properly on some flavors of Unix.
15004 ChessProgramState *cps;
15007 if (!cps->useSigint) return;
15008 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15009 switch (gameMode) {
15010 case MachinePlaysWhite:
15011 case MachinePlaysBlack:
15012 case TwoMachinesPlay:
15013 case IcsPlayingWhite:
15014 case IcsPlayingBlack:
15017 /* Skip if we know it isn't thinking */
15018 if (!cps->maybeThinking) return;
15019 if (appData.debugMode)
15020 fprintf(debugFP, "Interrupting %s\n", cps->which);
15021 InterruptChildProcess(cps->pr);
15022 cps->maybeThinking = FALSE;
15027 #endif /*ATTENTION*/
15033 if (whiteTimeRemaining <= 0) {
15036 if (appData.icsActive) {
15037 if (appData.autoCallFlag &&
15038 gameMode == IcsPlayingBlack && !blackFlag) {
15039 SendToICS(ics_prefix);
15040 SendToICS("flag\n");
15044 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15046 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15047 if (appData.autoCallFlag) {
15048 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15055 if (blackTimeRemaining <= 0) {
15058 if (appData.icsActive) {
15059 if (appData.autoCallFlag &&
15060 gameMode == IcsPlayingWhite && !whiteFlag) {
15061 SendToICS(ics_prefix);
15062 SendToICS("flag\n");
15066 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15068 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15069 if (appData.autoCallFlag) {
15070 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15083 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15084 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15087 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15089 if ( !WhiteOnMove(forwardMostMove) ) {
15090 /* White made time control */
15091 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15092 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15093 /* [HGM] time odds: correct new time quota for time odds! */
15094 / WhitePlayer()->timeOdds;
15095 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15097 lastBlack -= blackTimeRemaining;
15098 /* Black made time control */
15099 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15100 / WhitePlayer()->other->timeOdds;
15101 lastWhite = whiteTimeRemaining;
15106 DisplayBothClocks()
15108 int wom = gameMode == EditPosition ?
15109 !blackPlaysFirst : WhiteOnMove(currentMove);
15110 DisplayWhiteClock(whiteTimeRemaining, wom);
15111 DisplayBlackClock(blackTimeRemaining, !wom);
15115 /* Timekeeping seems to be a portability nightmare. I think everyone
15116 has ftime(), but I'm really not sure, so I'm including some ifdefs
15117 to use other calls if you don't. Clocks will be less accurate if
15118 you have neither ftime nor gettimeofday.
15121 /* VS 2008 requires the #include outside of the function */
15122 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15123 #include <sys/timeb.h>
15126 /* Get the current time as a TimeMark */
15131 #if HAVE_GETTIMEOFDAY
15133 struct timeval timeVal;
15134 struct timezone timeZone;
15136 gettimeofday(&timeVal, &timeZone);
15137 tm->sec = (long) timeVal.tv_sec;
15138 tm->ms = (int) (timeVal.tv_usec / 1000L);
15140 #else /*!HAVE_GETTIMEOFDAY*/
15143 // include <sys/timeb.h> / moved to just above start of function
15144 struct timeb timeB;
15147 tm->sec = (long) timeB.time;
15148 tm->ms = (int) timeB.millitm;
15150 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15151 tm->sec = (long) time(NULL);
15157 /* Return the difference in milliseconds between two
15158 time marks. We assume the difference will fit in a long!
15161 SubtractTimeMarks(tm2, tm1)
15162 TimeMark *tm2, *tm1;
15164 return 1000L*(tm2->sec - tm1->sec) +
15165 (long) (tm2->ms - tm1->ms);
15170 * Code to manage the game clocks.
15172 * In tournament play, black starts the clock and then white makes a move.
15173 * We give the human user a slight advantage if he is playing white---the
15174 * clocks don't run until he makes his first move, so it takes zero time.
15175 * Also, we don't account for network lag, so we could get out of sync
15176 * with GNU Chess's clock -- but then, referees are always right.
15179 static TimeMark tickStartTM;
15180 static long intendedTickLength;
15183 NextTickLength(timeRemaining)
15184 long timeRemaining;
15186 long nominalTickLength, nextTickLength;
15188 if (timeRemaining > 0L && timeRemaining <= 10000L)
15189 nominalTickLength = 100L;
15191 nominalTickLength = 1000L;
15192 nextTickLength = timeRemaining % nominalTickLength;
15193 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15195 return nextTickLength;
15198 /* Adjust clock one minute up or down */
15200 AdjustClock(Boolean which, int dir)
15202 if(which) blackTimeRemaining += 60000*dir;
15203 else whiteTimeRemaining += 60000*dir;
15204 DisplayBothClocks();
15207 /* Stop clocks and reset to a fresh time control */
15211 (void) StopClockTimer();
15212 if (appData.icsActive) {
15213 whiteTimeRemaining = blackTimeRemaining = 0;
15214 } else if (searchTime) {
15215 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15216 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15217 } else { /* [HGM] correct new time quote for time odds */
15218 whiteTC = blackTC = fullTimeControlString;
15219 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15220 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15222 if (whiteFlag || blackFlag) {
15224 whiteFlag = blackFlag = FALSE;
15226 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15227 DisplayBothClocks();
15230 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15232 /* Decrement running clock by amount of time that has passed */
15236 long timeRemaining;
15237 long lastTickLength, fudge;
15240 if (!appData.clockMode) return;
15241 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15245 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15247 /* Fudge if we woke up a little too soon */
15248 fudge = intendedTickLength - lastTickLength;
15249 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15251 if (WhiteOnMove(forwardMostMove)) {
15252 if(whiteNPS >= 0) lastTickLength = 0;
15253 timeRemaining = whiteTimeRemaining -= lastTickLength;
15254 if(timeRemaining < 0 && !appData.icsActive) {
15255 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15256 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15257 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15258 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15261 DisplayWhiteClock(whiteTimeRemaining - fudge,
15262 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15264 if(blackNPS >= 0) lastTickLength = 0;
15265 timeRemaining = blackTimeRemaining -= lastTickLength;
15266 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15267 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15269 blackStartMove = forwardMostMove;
15270 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15273 DisplayBlackClock(blackTimeRemaining - fudge,
15274 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15276 if (CheckFlags()) return;
15279 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15280 StartClockTimer(intendedTickLength);
15282 /* if the time remaining has fallen below the alarm threshold, sound the
15283 * alarm. if the alarm has sounded and (due to a takeback or time control
15284 * with increment) the time remaining has increased to a level above the
15285 * threshold, reset the alarm so it can sound again.
15288 if (appData.icsActive && appData.icsAlarm) {
15290 /* make sure we are dealing with the user's clock */
15291 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15292 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15295 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15296 alarmSounded = FALSE;
15297 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15299 alarmSounded = TRUE;
15305 /* A player has just moved, so stop the previously running
15306 clock and (if in clock mode) start the other one.
15307 We redisplay both clocks in case we're in ICS mode, because
15308 ICS gives us an update to both clocks after every move.
15309 Note that this routine is called *after* forwardMostMove
15310 is updated, so the last fractional tick must be subtracted
15311 from the color that is *not* on move now.
15314 SwitchClocks(int newMoveNr)
15316 long lastTickLength;
15318 int flagged = FALSE;
15322 if (StopClockTimer() && appData.clockMode) {
15323 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15324 if (!WhiteOnMove(forwardMostMove)) {
15325 if(blackNPS >= 0) lastTickLength = 0;
15326 blackTimeRemaining -= lastTickLength;
15327 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15328 // if(pvInfoList[forwardMostMove].time == -1)
15329 pvInfoList[forwardMostMove].time = // use GUI time
15330 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15332 if(whiteNPS >= 0) lastTickLength = 0;
15333 whiteTimeRemaining -= lastTickLength;
15334 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15335 // if(pvInfoList[forwardMostMove].time == -1)
15336 pvInfoList[forwardMostMove].time =
15337 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15339 flagged = CheckFlags();
15341 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15342 CheckTimeControl();
15344 if (flagged || !appData.clockMode) return;
15346 switch (gameMode) {
15347 case MachinePlaysBlack:
15348 case MachinePlaysWhite:
15349 case BeginningOfGame:
15350 if (pausing) return;
15354 case PlayFromGameFile:
15362 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15363 if(WhiteOnMove(forwardMostMove))
15364 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15365 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15369 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15370 whiteTimeRemaining : blackTimeRemaining);
15371 StartClockTimer(intendedTickLength);
15375 /* Stop both clocks */
15379 long lastTickLength;
15382 if (!StopClockTimer()) return;
15383 if (!appData.clockMode) return;
15387 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15388 if (WhiteOnMove(forwardMostMove)) {
15389 if(whiteNPS >= 0) lastTickLength = 0;
15390 whiteTimeRemaining -= lastTickLength;
15391 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15393 if(blackNPS >= 0) lastTickLength = 0;
15394 blackTimeRemaining -= lastTickLength;
15395 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15400 /* Start clock of player on move. Time may have been reset, so
15401 if clock is already running, stop and restart it. */
15405 (void) StopClockTimer(); /* in case it was running already */
15406 DisplayBothClocks();
15407 if (CheckFlags()) return;
15409 if (!appData.clockMode) return;
15410 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15412 GetTimeMark(&tickStartTM);
15413 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15414 whiteTimeRemaining : blackTimeRemaining);
15416 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15417 whiteNPS = blackNPS = -1;
15418 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15419 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15420 whiteNPS = first.nps;
15421 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15422 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15423 blackNPS = first.nps;
15424 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15425 whiteNPS = second.nps;
15426 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15427 blackNPS = second.nps;
15428 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15430 StartClockTimer(intendedTickLength);
15437 long second, minute, hour, day;
15439 static char buf[32];
15441 if (ms > 0 && ms <= 9900) {
15442 /* convert milliseconds to tenths, rounding up */
15443 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15445 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15449 /* convert milliseconds to seconds, rounding up */
15450 /* use floating point to avoid strangeness of integer division
15451 with negative dividends on many machines */
15452 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15459 day = second / (60 * 60 * 24);
15460 second = second % (60 * 60 * 24);
15461 hour = second / (60 * 60);
15462 second = second % (60 * 60);
15463 minute = second / 60;
15464 second = second % 60;
15467 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15468 sign, day, hour, minute, second);
15470 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15472 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15479 * This is necessary because some C libraries aren't ANSI C compliant yet.
15482 StrStr(string, match)
15483 char *string, *match;
15487 length = strlen(match);
15489 for (i = strlen(string) - length; i >= 0; i--, string++)
15490 if (!strncmp(match, string, length))
15497 StrCaseStr(string, match)
15498 char *string, *match;
15502 length = strlen(match);
15504 for (i = strlen(string) - length; i >= 0; i--, string++) {
15505 for (j = 0; j < length; j++) {
15506 if (ToLower(match[j]) != ToLower(string[j]))
15509 if (j == length) return string;
15523 c1 = ToLower(*s1++);
15524 c2 = ToLower(*s2++);
15525 if (c1 > c2) return 1;
15526 if (c1 < c2) return -1;
15527 if (c1 == NULLCHAR) return 0;
15536 return isupper(c) ? tolower(c) : c;
15544 return islower(c) ? toupper(c) : c;
15546 #endif /* !_amigados */
15554 if ((ret = (char *) malloc(strlen(s) + 1)))
15556 safeStrCpy(ret, s, strlen(s)+1);
15562 StrSavePtr(s, savePtr)
15563 char *s, **savePtr;
15568 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15569 safeStrCpy(*savePtr, s, strlen(s)+1);
15581 clock = time((time_t *)NULL);
15582 tm = localtime(&clock);
15583 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15584 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15585 return StrSave(buf);
15590 PositionToFEN(move, overrideCastling)
15592 char *overrideCastling;
15594 int i, j, fromX, fromY, toX, toY;
15601 whiteToPlay = (gameMode == EditPosition) ?
15602 !blackPlaysFirst : (move % 2 == 0);
15605 /* Piece placement data */
15606 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15608 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15609 if (boards[move][i][j] == EmptySquare) {
15611 } else { ChessSquare piece = boards[move][i][j];
15612 if (emptycount > 0) {
15613 if(emptycount<10) /* [HGM] can be >= 10 */
15614 *p++ = '0' + emptycount;
15615 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15618 if(PieceToChar(piece) == '+') {
15619 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15621 piece = (ChessSquare)(DEMOTED piece);
15623 *p++ = PieceToChar(piece);
15625 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15626 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15631 if (emptycount > 0) {
15632 if(emptycount<10) /* [HGM] can be >= 10 */
15633 *p++ = '0' + emptycount;
15634 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15641 /* [HGM] print Crazyhouse or Shogi holdings */
15642 if( gameInfo.holdingsWidth ) {
15643 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15645 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15646 piece = boards[move][i][BOARD_WIDTH-1];
15647 if( piece != EmptySquare )
15648 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15649 *p++ = PieceToChar(piece);
15651 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15652 piece = boards[move][BOARD_HEIGHT-i-1][0];
15653 if( piece != EmptySquare )
15654 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15655 *p++ = PieceToChar(piece);
15658 if( q == p ) *p++ = '-';
15664 *p++ = whiteToPlay ? 'w' : 'b';
15667 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15668 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15670 if(nrCastlingRights) {
15672 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15673 /* [HGM] write directly from rights */
15674 if(boards[move][CASTLING][2] != NoRights &&
15675 boards[move][CASTLING][0] != NoRights )
15676 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15677 if(boards[move][CASTLING][2] != NoRights &&
15678 boards[move][CASTLING][1] != NoRights )
15679 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15680 if(boards[move][CASTLING][5] != NoRights &&
15681 boards[move][CASTLING][3] != NoRights )
15682 *p++ = boards[move][CASTLING][3] + AAA;
15683 if(boards[move][CASTLING][5] != NoRights &&
15684 boards[move][CASTLING][4] != NoRights )
15685 *p++ = boards[move][CASTLING][4] + AAA;
15688 /* [HGM] write true castling rights */
15689 if( nrCastlingRights == 6 ) {
15690 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15691 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15692 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15693 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15694 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15695 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15696 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15697 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15700 if (q == p) *p++ = '-'; /* No castling rights */
15704 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15705 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15706 /* En passant target square */
15707 if (move > backwardMostMove) {
15708 fromX = moveList[move - 1][0] - AAA;
15709 fromY = moveList[move - 1][1] - ONE;
15710 toX = moveList[move - 1][2] - AAA;
15711 toY = moveList[move - 1][3] - ONE;
15712 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15713 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15714 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15716 /* 2-square pawn move just happened */
15718 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15722 } else if(move == backwardMostMove) {
15723 // [HGM] perhaps we should always do it like this, and forget the above?
15724 if((signed char)boards[move][EP_STATUS] >= 0) {
15725 *p++ = boards[move][EP_STATUS] + AAA;
15726 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15737 /* [HGM] find reversible plies */
15738 { int i = 0, j=move;
15740 if (appData.debugMode) { int k;
15741 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15742 for(k=backwardMostMove; k<=forwardMostMove; k++)
15743 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15747 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15748 if( j == backwardMostMove ) i += initialRulePlies;
15749 sprintf(p, "%d ", i);
15750 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15752 /* Fullmove number */
15753 sprintf(p, "%d", (move / 2) + 1);
15755 return StrSave(buf);
15759 ParseFEN(board, blackPlaysFirst, fen)
15761 int *blackPlaysFirst;
15771 /* [HGM] by default clear Crazyhouse holdings, if present */
15772 if(gameInfo.holdingsWidth) {
15773 for(i=0; i<BOARD_HEIGHT; i++) {
15774 board[i][0] = EmptySquare; /* black holdings */
15775 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15776 board[i][1] = (ChessSquare) 0; /* black counts */
15777 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15781 /* Piece placement data */
15782 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15785 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15786 if (*p == '/') p++;
15787 emptycount = gameInfo.boardWidth - j;
15788 while (emptycount--)
15789 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15791 #if(BOARD_FILES >= 10)
15792 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15793 p++; emptycount=10;
15794 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15795 while (emptycount--)
15796 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15798 } else if (isdigit(*p)) {
15799 emptycount = *p++ - '0';
15800 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15801 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15802 while (emptycount--)
15803 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15804 } else if (*p == '+' || isalpha(*p)) {
15805 if (j >= gameInfo.boardWidth) return FALSE;
15807 piece = CharToPiece(*++p);
15808 if(piece == EmptySquare) return FALSE; /* unknown piece */
15809 piece = (ChessSquare) (PROMOTED piece ); p++;
15810 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15811 } else piece = CharToPiece(*p++);
15813 if(piece==EmptySquare) return FALSE; /* unknown piece */
15814 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15815 piece = (ChessSquare) (PROMOTED piece);
15816 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15819 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15825 while (*p == '/' || *p == ' ') p++;
15827 /* [HGM] look for Crazyhouse holdings here */
15828 while(*p==' ') p++;
15829 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15831 if(*p == '-' ) p++; /* empty holdings */ else {
15832 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15833 /* if we would allow FEN reading to set board size, we would */
15834 /* have to add holdings and shift the board read so far here */
15835 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15837 if((int) piece >= (int) BlackPawn ) {
15838 i = (int)piece - (int)BlackPawn;
15839 i = PieceToNumber((ChessSquare)i);
15840 if( i >= gameInfo.holdingsSize ) return FALSE;
15841 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15842 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15844 i = (int)piece - (int)WhitePawn;
15845 i = PieceToNumber((ChessSquare)i);
15846 if( i >= gameInfo.holdingsSize ) return FALSE;
15847 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15848 board[i][BOARD_WIDTH-2]++; /* black holdings */
15855 while(*p == ' ') p++;
15859 if(appData.colorNickNames) {
15860 if( c == appData.colorNickNames[0] ) c = 'w'; else
15861 if( c == appData.colorNickNames[1] ) c = 'b';
15865 *blackPlaysFirst = FALSE;
15868 *blackPlaysFirst = TRUE;
15874 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15875 /* return the extra info in global variiables */
15877 /* set defaults in case FEN is incomplete */
15878 board[EP_STATUS] = EP_UNKNOWN;
15879 for(i=0; i<nrCastlingRights; i++ ) {
15880 board[CASTLING][i] =
15881 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15882 } /* assume possible unless obviously impossible */
15883 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15884 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15885 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15886 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15887 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15888 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15889 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15890 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15893 while(*p==' ') p++;
15894 if(nrCastlingRights) {
15895 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15896 /* castling indicator present, so default becomes no castlings */
15897 for(i=0; i<nrCastlingRights; i++ ) {
15898 board[CASTLING][i] = NoRights;
15901 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15902 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15903 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15904 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15905 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15907 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15908 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15909 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15911 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15912 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15913 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15914 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15915 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15916 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15919 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15920 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15921 board[CASTLING][2] = whiteKingFile;
15924 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15925 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15926 board[CASTLING][2] = whiteKingFile;
15929 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15930 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15931 board[CASTLING][5] = blackKingFile;
15934 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15935 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15936 board[CASTLING][5] = blackKingFile;
15939 default: /* FRC castlings */
15940 if(c >= 'a') { /* black rights */
15941 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15942 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15943 if(i == BOARD_RGHT) break;
15944 board[CASTLING][5] = i;
15946 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15947 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15949 board[CASTLING][3] = c;
15951 board[CASTLING][4] = c;
15952 } else { /* white rights */
15953 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15954 if(board[0][i] == WhiteKing) break;
15955 if(i == BOARD_RGHT) break;
15956 board[CASTLING][2] = i;
15957 c -= AAA - 'a' + 'A';
15958 if(board[0][c] >= WhiteKing) break;
15960 board[CASTLING][0] = c;
15962 board[CASTLING][1] = c;
15966 for(i=0; i<nrCastlingRights; i++)
15967 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15968 if (appData.debugMode) {
15969 fprintf(debugFP, "FEN castling rights:");
15970 for(i=0; i<nrCastlingRights; i++)
15971 fprintf(debugFP, " %d", board[CASTLING][i]);
15972 fprintf(debugFP, "\n");
15975 while(*p==' ') p++;
15978 /* read e.p. field in games that know e.p. capture */
15979 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15980 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15982 p++; board[EP_STATUS] = EP_NONE;
15984 char c = *p++ - AAA;
15986 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15987 if(*p >= '0' && *p <='9') p++;
15988 board[EP_STATUS] = c;
15993 if(sscanf(p, "%d", &i) == 1) {
15994 FENrulePlies = i; /* 50-move ply counter */
15995 /* (The move number is still ignored) */
16002 EditPositionPasteFEN(char *fen)
16005 Board initial_position;
16007 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16008 DisplayError(_("Bad FEN position in clipboard"), 0);
16011 int savedBlackPlaysFirst = blackPlaysFirst;
16012 EditPositionEvent();
16013 blackPlaysFirst = savedBlackPlaysFirst;
16014 CopyBoard(boards[0], initial_position);
16015 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16016 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16017 DisplayBothClocks();
16018 DrawPosition(FALSE, boards[currentMove]);
16023 static char cseq[12] = "\\ ";
16025 Boolean set_cont_sequence(char *new_seq)
16030 // handle bad attempts to set the sequence
16032 return 0; // acceptable error - no debug
16034 len = strlen(new_seq);
16035 ret = (len > 0) && (len < sizeof(cseq));
16037 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16038 else if (appData.debugMode)
16039 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16044 reformat a source message so words don't cross the width boundary. internal
16045 newlines are not removed. returns the wrapped size (no null character unless
16046 included in source message). If dest is NULL, only calculate the size required
16047 for the dest buffer. lp argument indicats line position upon entry, and it's
16048 passed back upon exit.
16050 int wrap(char *dest, char *src, int count, int width, int *lp)
16052 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16054 cseq_len = strlen(cseq);
16055 old_line = line = *lp;
16056 ansi = len = clen = 0;
16058 for (i=0; i < count; i++)
16060 if (src[i] == '\033')
16063 // if we hit the width, back up
16064 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16066 // store i & len in case the word is too long
16067 old_i = i, old_len = len;
16069 // find the end of the last word
16070 while (i && src[i] != ' ' && src[i] != '\n')
16076 // word too long? restore i & len before splitting it
16077 if ((old_i-i+clen) >= width)
16084 if (i && src[i-1] == ' ')
16087 if (src[i] != ' ' && src[i] != '\n')
16094 // now append the newline and continuation sequence
16099 strncpy(dest+len, cseq, cseq_len);
16107 dest[len] = src[i];
16111 if (src[i] == '\n')
16116 if (dest && appData.debugMode)
16118 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16119 count, width, line, len, *lp);
16120 show_bytes(debugFP, src, count);
16121 fprintf(debugFP, "\ndest: ");
16122 show_bytes(debugFP, dest, len);
16123 fprintf(debugFP, "\n");
16125 *lp = dest ? line : old_line;
16130 // [HGM] vari: routines for shelving variations
16133 PushInner(int firstMove, int lastMove)
16135 int i, j, nrMoves = lastMove - firstMove;
16137 // push current tail of game on stack
16138 savedResult[storedGames] = gameInfo.result;
16139 savedDetails[storedGames] = gameInfo.resultDetails;
16140 gameInfo.resultDetails = NULL;
16141 savedFirst[storedGames] = firstMove;
16142 savedLast [storedGames] = lastMove;
16143 savedFramePtr[storedGames] = framePtr;
16144 framePtr -= nrMoves; // reserve space for the boards
16145 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16146 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16147 for(j=0; j<MOVE_LEN; j++)
16148 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16149 for(j=0; j<2*MOVE_LEN; j++)
16150 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16151 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16152 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16153 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16154 pvInfoList[firstMove+i-1].depth = 0;
16155 commentList[framePtr+i] = commentList[firstMove+i];
16156 commentList[firstMove+i] = NULL;
16160 forwardMostMove = firstMove; // truncate game so we can start variation
16164 PushTail(int firstMove, int lastMove)
16166 if(appData.icsActive) { // only in local mode
16167 forwardMostMove = currentMove; // mimic old ICS behavior
16170 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16172 PushInner(firstMove, lastMove);
16173 if(storedGames == 1) GreyRevert(FALSE);
16177 PopInner(Boolean annotate)
16180 char buf[8000], moveBuf[20];
16183 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16184 nrMoves = savedLast[storedGames] - currentMove;
16187 if(!WhiteOnMove(currentMove))
16188 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16189 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16190 for(i=currentMove; i<forwardMostMove; i++) {
16192 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16193 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16194 strcat(buf, moveBuf);
16195 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16196 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16200 for(i=1; i<=nrMoves; i++) { // copy last variation back
16201 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16202 for(j=0; j<MOVE_LEN; j++)
16203 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16204 for(j=0; j<2*MOVE_LEN; j++)
16205 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16206 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16207 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16208 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16209 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16210 commentList[currentMove+i] = commentList[framePtr+i];
16211 commentList[framePtr+i] = NULL;
16213 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16214 framePtr = savedFramePtr[storedGames];
16215 gameInfo.result = savedResult[storedGames];
16216 if(gameInfo.resultDetails != NULL) {
16217 free(gameInfo.resultDetails);
16219 gameInfo.resultDetails = savedDetails[storedGames];
16220 forwardMostMove = currentMove + nrMoves;
16224 PopTail(Boolean annotate)
16226 if(appData.icsActive) return FALSE; // only in local mode
16227 if(!storedGames) return FALSE; // sanity
16228 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16230 PopInner(annotate);
16232 if(storedGames == 0) GreyRevert(TRUE);
16238 { // remove all shelved variations
16240 for(i=0; i<storedGames; i++) {
16241 if(savedDetails[i])
16242 free(savedDetails[i]);
16243 savedDetails[i] = NULL;
16245 for(i=framePtr; i<MAX_MOVES; i++) {
16246 if(commentList[i]) free(commentList[i]);
16247 commentList[i] = NULL;
16249 framePtr = MAX_MOVES-1;
16254 LoadVariation(int index, char *text)
16255 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16256 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16257 int level = 0, move;
16259 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16260 // first find outermost bracketing variation
16261 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16262 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16263 if(*p == '{') wait = '}'; else
16264 if(*p == '[') wait = ']'; else
16265 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16266 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16268 if(*p == wait) wait = NULLCHAR; // closing ]} found
16271 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16272 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16273 end[1] = NULLCHAR; // clip off comment beyond variation
16274 ToNrEvent(currentMove-1);
16275 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16276 // kludge: use ParsePV() to append variation to game
16277 move = currentMove;
16278 ParsePV(start, TRUE, TRUE);
16279 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16280 ClearPremoveHighlights();
16282 ToNrEvent(currentMove+1);