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;
868 Load(ChessProgramState *cps, int i)
870 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871 if(engineLine[0]) { // an engine was selected from the combo box
872 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -fn \"\"");
875 ParseArgsFromString(buf);
877 ReplaceEngine(cps, i);
881 while(q = strchr(p, SLASH)) p = q+1;
882 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883 if(engineDir[0] != NULLCHAR)
884 appData.directory[i] = engineDir;
885 else if(p != engineName) { // derive directory from engine path, when not given
887 appData.directory[i] = strdup(engineName);
889 } else appData.directory[i] = ".";
891 snprintf(command, MSG_SIZ, "%s %s", p, params);
894 appData.chessProgram[i] = strdup(p);
895 appData.isUCI[i] = isUCI;
896 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897 appData.hasOwnBookUCI[i] = hasBook;
898 if(!nickName[0]) useNick = FALSE;
899 if(useNick) ASSIGN(appData.pgnName[i], nickName);
902 q = firstChessProgramNames;
903 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
904 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
905 useNick ? " -fn \"" : "",
906 useNick ? nickName : "",
908 v1 ? " -firstProtocolVersion 1" : "",
909 hasBook ? "" : " -fNoOwnBookUCI",
910 isUCI ? " -fUCI" : "",
911 storeVariant ? " -variant " : "",
912 storeVariant ? VariantName(gameInfo.variant) : "");
913 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
914 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
917 ReplaceEngine(cps, i);
923 int matched, min, sec;
925 * Parse timeControl resource
927 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
928 appData.movesPerSession)) {
930 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
931 DisplayFatalError(buf, 0, 2);
935 * Parse searchTime resource
937 if (*appData.searchTime != NULLCHAR) {
938 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
940 searchTime = min * 60;
941 } else if (matched == 2) {
942 searchTime = min * 60 + sec;
945 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
946 DisplayFatalError(buf, 0, 2);
955 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
956 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
958 GetTimeMark(&programStartTime);
959 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
960 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
963 programStats.ok_to_send = 1;
964 programStats.seen_stat = 0;
967 * Initialize game list
973 * Internet chess server status
975 if (appData.icsActive) {
976 appData.matchMode = FALSE;
977 appData.matchGames = 0;
979 appData.noChessProgram = !appData.zippyPlay;
981 appData.zippyPlay = FALSE;
982 appData.zippyTalk = FALSE;
983 appData.noChessProgram = TRUE;
985 if (*appData.icsHelper != NULLCHAR) {
986 appData.useTelnet = TRUE;
987 appData.telnetProgram = appData.icsHelper;
990 appData.zippyTalk = appData.zippyPlay = FALSE;
993 /* [AS] Initialize pv info list [HGM] and game state */
997 for( i=0; i<=framePtr; i++ ) {
998 pvInfoList[i].depth = -1;
999 boards[i][EP_STATUS] = EP_NONE;
1000 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1006 /* [AS] Adjudication threshold */
1007 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1009 InitEngine(&first, 0);
1010 InitEngine(&second, 1);
1013 if (appData.icsActive) {
1014 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1015 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1016 appData.clockMode = FALSE;
1017 first.sendTime = second.sendTime = 0;
1021 /* Override some settings from environment variables, for backward
1022 compatibility. Unfortunately it's not feasible to have the env
1023 vars just set defaults, at least in xboard. Ugh.
1025 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030 if (!appData.icsActive) {
1034 /* Check for variants that are supported only in ICS mode,
1035 or not at all. Some that are accepted here nevertheless
1036 have bugs; see comments below.
1038 VariantClass variant = StringToVariant(appData.variant);
1040 case VariantBughouse: /* need four players and two boards */
1041 case VariantKriegspiel: /* need to hide pieces and move details */
1042 /* case VariantFischeRandom: (Fabien: moved below) */
1043 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1044 if( (len > MSG_SIZ) && appData.debugMode )
1045 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1047 DisplayFatalError(buf, 0, 2);
1050 case VariantUnknown:
1051 case VariantLoadable:
1061 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1062 if( (len > MSG_SIZ) && appData.debugMode )
1063 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1065 DisplayFatalError(buf, 0, 2);
1068 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1069 case VariantFairy: /* [HGM] TestLegality definitely off! */
1070 case VariantGothic: /* [HGM] should work */
1071 case VariantCapablanca: /* [HGM] should work */
1072 case VariantCourier: /* [HGM] initial forced moves not implemented */
1073 case VariantShogi: /* [HGM] could still mate with pawn drop */
1074 case VariantKnightmate: /* [HGM] should work */
1075 case VariantCylinder: /* [HGM] untested */
1076 case VariantFalcon: /* [HGM] untested */
1077 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1078 offboard interposition not understood */
1079 case VariantNormal: /* definitely works! */
1080 case VariantWildCastle: /* pieces not automatically shuffled */
1081 case VariantNoCastle: /* pieces not automatically shuffled */
1082 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1083 case VariantLosers: /* should work except for win condition,
1084 and doesn't know captures are mandatory */
1085 case VariantSuicide: /* should work except for win condition,
1086 and doesn't know captures are mandatory */
1087 case VariantGiveaway: /* should work except for win condition,
1088 and doesn't know captures are mandatory */
1089 case VariantTwoKings: /* should work */
1090 case VariantAtomic: /* should work except for win condition */
1091 case Variant3Check: /* should work except for win condition */
1092 case VariantShatranj: /* should work except for all win conditions */
1093 case VariantMakruk: /* should work except for daw countdown */
1094 case VariantBerolina: /* might work if TestLegality is off */
1095 case VariantCapaRandom: /* should work */
1096 case VariantJanus: /* should work */
1097 case VariantSuper: /* experimental */
1098 case VariantGreat: /* experimental, requires legality testing to be off */
1099 case VariantSChess: /* S-Chess, should work */
1100 case VariantSpartan: /* should work */
1107 int NextIntegerFromString( char ** str, long * value )
1112 while( *s == ' ' || *s == '\t' ) {
1118 if( *s >= '0' && *s <= '9' ) {
1119 while( *s >= '0' && *s <= '9' ) {
1120 *value = *value * 10 + (*s - '0');
1132 int NextTimeControlFromString( char ** str, long * value )
1135 int result = NextIntegerFromString( str, &temp );
1138 *value = temp * 60; /* Minutes */
1139 if( **str == ':' ) {
1141 result = NextIntegerFromString( str, &temp );
1142 *value += temp; /* Seconds */
1149 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1150 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1151 int result = -1, type = 0; long temp, temp2;
1153 if(**str != ':') return -1; // old params remain in force!
1155 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1156 if( NextIntegerFromString( str, &temp ) ) return -1;
1157 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1160 /* time only: incremental or sudden-death time control */
1161 if(**str == '+') { /* increment follows; read it */
1163 if(**str == '!') type = *(*str)++; // Bronstein TC
1164 if(result = NextIntegerFromString( str, &temp2)) return -1;
1165 *inc = temp2 * 1000;
1166 if(**str == '.') { // read fraction of increment
1167 char *start = ++(*str);
1168 if(result = NextIntegerFromString( str, &temp2)) return -1;
1170 while(start++ < *str) temp2 /= 10;
1174 *moves = 0; *tc = temp * 1000; *incType = type;
1178 (*str)++; /* classical time control */
1179 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1190 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1191 { /* [HGM] get time to add from the multi-session time-control string */
1192 int incType, moves=1; /* kludge to force reading of first session */
1193 long time, increment;
1196 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1197 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1199 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1200 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1201 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1202 if(movenr == -1) return time; /* last move before new session */
1203 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1204 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1205 if(!moves) return increment; /* current session is incremental */
1206 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1207 } while(movenr >= -1); /* try again for next session */
1209 return 0; // no new time quota on this move
1213 ParseTimeControl(tc, ti, mps)
1220 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1223 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1224 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1225 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1229 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1231 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1234 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1236 snprintf(buf, MSG_SIZ, ":%s", mytc);
1238 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1240 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245 /* Parse second time control */
1248 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1256 timeControl_2 = tc2 * 1000;
1266 timeControl = tc1 * 1000;
1269 timeIncrement = ti * 1000; /* convert to ms */
1270 movesPerSession = 0;
1273 movesPerSession = mps;
1281 if (appData.debugMode) {
1282 fprintf(debugFP, "%s\n", programVersion);
1285 set_cont_sequence(appData.wrapContSeq);
1286 if (appData.matchGames > 0) {
1287 appData.matchMode = TRUE;
1288 } else if (appData.matchMode) {
1289 appData.matchGames = 1;
1291 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1292 appData.matchGames = appData.sameColorGames;
1293 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1294 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1295 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1298 if (appData.noChessProgram || first.protocolVersion == 1) {
1301 /* kludge: allow timeout for initial "feature" commands */
1303 DisplayMessage("", _("Starting chess program"));
1304 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309 CalculateIndex(int index, int gameNr)
1310 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1312 if(index > 0) return index; // fixed nmber
1313 if(index == 0) return 1;
1314 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1315 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320 LoadGameOrPosition(int gameNr)
1321 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1322 if (*appData.loadGameFile != NULLCHAR) {
1323 if (!LoadGameFromFile(appData.loadGameFile,
1324 CalculateIndex(appData.loadGameIndex, gameNr),
1325 appData.loadGameFile, FALSE)) {
1326 DisplayFatalError(_("Bad game file"), 0, 1);
1329 } else if (*appData.loadPositionFile != NULLCHAR) {
1330 if (!LoadPositionFromFile(appData.loadPositionFile,
1331 CalculateIndex(appData.loadPositionIndex, gameNr),
1332 appData.loadPositionFile)) {
1333 DisplayFatalError(_("Bad position file"), 0, 1);
1341 ReserveGame(int gameNr, char resChar)
1343 FILE *tf = fopen(appData.tourneyFile, "r+");
1344 char *p, *q, c, buf[MSG_SIZ];
1345 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1346 safeStrCpy(buf, lastMsg, MSG_SIZ);
1347 DisplayMessage(_("Pick new game"), "");
1348 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1349 ParseArgsFromFile(tf);
1350 p = q = appData.results;
1351 if(appData.debugMode) {
1352 char *r = appData.participants;
1353 fprintf(debugFP, "results = '%s'\n", p);
1354 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1355 fprintf(debugFP, "\n");
1357 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1359 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1360 safeStrCpy(q, p, strlen(p) + 2);
1361 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1362 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1363 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1364 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1367 fseek(tf, -(strlen(p)+4), SEEK_END);
1369 if(c != '"') // depending on DOS or Unix line endings we can be one off
1370 fseek(tf, -(strlen(p)+2), SEEK_END);
1371 else fseek(tf, -(strlen(p)+3), SEEK_END);
1372 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1373 DisplayMessage(buf, "");
1374 free(p); appData.results = q;
1375 if(nextGame <= appData.matchGames && resChar != ' ' &&
1376 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1377 UnloadEngine(&first); // next game belongs to other pairing;
1378 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383 MatchEvent(int mode)
1384 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1386 if(matchMode) { // already in match mode: switch it off
1388 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1389 ModeHighlight(); // kludgey way to remove checkmark...
1392 // if(gameMode != BeginningOfGame) {
1393 // DisplayError(_("You can only start a match from the initial position."), 0);
1397 appData.matchGames = appData.defaultMatchGames;
1398 /* Set up machine vs. machine match */
1400 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1401 if(appData.tourneyFile[0]) {
1403 if(nextGame > appData.matchGames) {
1405 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1406 DisplayError(buf, 0);
1407 appData.tourneyFile[0] = 0;
1411 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1412 DisplayFatalError(_("Can't have a match with no chess programs"),
1417 matchGame = roundNr = 1;
1418 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1423 InitBackEnd3 P((void))
1425 GameMode initialMode;
1429 InitChessProgram(&first, startedFromSetupPosition);
1431 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1432 free(programVersion);
1433 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1434 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1437 if (appData.icsActive) {
1439 /* [DM] Make a console window if needed [HGM] merged ifs */
1445 if (*appData.icsCommPort != NULLCHAR)
1446 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1447 appData.icsCommPort);
1449 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1450 appData.icsHost, appData.icsPort);
1452 if( (len > MSG_SIZ) && appData.debugMode )
1453 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1455 DisplayFatalError(buf, err, 1);
1460 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1462 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1463 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1464 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1465 } else if (appData.noChessProgram) {
1471 if (*appData.cmailGameName != NULLCHAR) {
1473 OpenLoopback(&cmailPR);
1475 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1479 DisplayMessage("", "");
1480 if (StrCaseCmp(appData.initialMode, "") == 0) {
1481 initialMode = BeginningOfGame;
1482 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1483 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1484 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1485 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1488 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1489 initialMode = TwoMachinesPlay;
1490 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1491 initialMode = AnalyzeFile;
1492 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1493 initialMode = AnalyzeMode;
1494 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1495 initialMode = MachinePlaysWhite;
1496 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1497 initialMode = MachinePlaysBlack;
1498 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1499 initialMode = EditGame;
1500 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1501 initialMode = EditPosition;
1502 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1503 initialMode = Training;
1505 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1506 if( (len > MSG_SIZ) && appData.debugMode )
1507 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1509 DisplayFatalError(buf, 0, 2);
1513 if (appData.matchMode) {
1514 if(appData.tourneyFile[0]) { // start tourney from command line
1516 if(f = fopen(appData.tourneyFile, "r")) {
1517 ParseArgsFromFile(f); // make sure tourney parmeters re known
1519 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1522 } else if (*appData.cmailGameName != NULLCHAR) {
1523 /* Set up cmail mode */
1524 ReloadCmailMsgEvent(TRUE);
1526 /* Set up other modes */
1527 if (initialMode == AnalyzeFile) {
1528 if (*appData.loadGameFile == NULLCHAR) {
1529 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1533 if (*appData.loadGameFile != NULLCHAR) {
1534 (void) LoadGameFromFile(appData.loadGameFile,
1535 appData.loadGameIndex,
1536 appData.loadGameFile, TRUE);
1537 } else if (*appData.loadPositionFile != NULLCHAR) {
1538 (void) LoadPositionFromFile(appData.loadPositionFile,
1539 appData.loadPositionIndex,
1540 appData.loadPositionFile);
1541 /* [HGM] try to make self-starting even after FEN load */
1542 /* to allow automatic setup of fairy variants with wtm */
1543 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1544 gameMode = BeginningOfGame;
1545 setboardSpoiledMachineBlack = 1;
1547 /* [HGM] loadPos: make that every new game uses the setup */
1548 /* from file as long as we do not switch variant */
1549 if(!blackPlaysFirst) {
1550 startedFromPositionFile = TRUE;
1551 CopyBoard(filePosition, boards[0]);
1554 if (initialMode == AnalyzeMode) {
1555 if (appData.noChessProgram) {
1556 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1559 if (appData.icsActive) {
1560 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1564 } else if (initialMode == AnalyzeFile) {
1565 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1566 ShowThinkingEvent();
1568 AnalysisPeriodicEvent(1);
1569 } else if (initialMode == MachinePlaysWhite) {
1570 if (appData.noChessProgram) {
1571 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1575 if (appData.icsActive) {
1576 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1580 MachineWhiteEvent();
1581 } else if (initialMode == MachinePlaysBlack) {
1582 if (appData.noChessProgram) {
1583 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1587 if (appData.icsActive) {
1588 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1592 MachineBlackEvent();
1593 } else if (initialMode == TwoMachinesPlay) {
1594 if (appData.noChessProgram) {
1595 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1599 if (appData.icsActive) {
1600 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605 } else if (initialMode == EditGame) {
1607 } else if (initialMode == EditPosition) {
1608 EditPositionEvent();
1609 } else if (initialMode == Training) {
1610 if (*appData.loadGameFile == NULLCHAR) {
1611 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1620 * Establish will establish a contact to a remote host.port.
1621 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1622 * used to talk to the host.
1623 * Returns 0 if okay, error code if not.
1630 if (*appData.icsCommPort != NULLCHAR) {
1631 /* Talk to the host through a serial comm port */
1632 return OpenCommPort(appData.icsCommPort, &icsPR);
1634 } else if (*appData.gateway != NULLCHAR) {
1635 if (*appData.remoteShell == NULLCHAR) {
1636 /* Use the rcmd protocol to run telnet program on a gateway host */
1637 snprintf(buf, sizeof(buf), "%s %s %s",
1638 appData.telnetProgram, appData.icsHost, appData.icsPort);
1639 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1642 /* Use the rsh program to run telnet program on a gateway host */
1643 if (*appData.remoteUser == NULLCHAR) {
1644 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1645 appData.gateway, appData.telnetProgram,
1646 appData.icsHost, appData.icsPort);
1648 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1649 appData.remoteShell, appData.gateway,
1650 appData.remoteUser, appData.telnetProgram,
1651 appData.icsHost, appData.icsPort);
1653 return StartChildProcess(buf, "", &icsPR);
1656 } else if (appData.useTelnet) {
1657 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1660 /* TCP socket interface differs somewhat between
1661 Unix and NT; handle details in the front end.
1663 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1667 void EscapeExpand(char *p, char *q)
1668 { // [HGM] initstring: routine to shape up string arguments
1669 while(*p++ = *q++) if(p[-1] == '\\')
1671 case 'n': p[-1] = '\n'; break;
1672 case 'r': p[-1] = '\r'; break;
1673 case 't': p[-1] = '\t'; break;
1674 case '\\': p[-1] = '\\'; break;
1675 case 0: *p = 0; return;
1676 default: p[-1] = q[-1]; break;
1681 show_bytes(fp, buf, count)
1687 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1688 fprintf(fp, "\\%03o", *buf & 0xff);
1697 /* Returns an errno value */
1699 OutputMaybeTelnet(pr, message, count, outError)
1705 char buf[8192], *p, *q, *buflim;
1706 int left, newcount, outcount;
1708 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1709 *appData.gateway != NULLCHAR) {
1710 if (appData.debugMode) {
1711 fprintf(debugFP, ">ICS: ");
1712 show_bytes(debugFP, message, count);
1713 fprintf(debugFP, "\n");
1715 return OutputToProcess(pr, message, count, outError);
1718 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1725 if (appData.debugMode) {
1726 fprintf(debugFP, ">ICS: ");
1727 show_bytes(debugFP, buf, newcount);
1728 fprintf(debugFP, "\n");
1730 outcount = OutputToProcess(pr, buf, newcount, outError);
1731 if (outcount < newcount) return -1; /* to be sure */
1738 } else if (((unsigned char) *p) == TN_IAC) {
1739 *q++ = (char) TN_IAC;
1746 if (appData.debugMode) {
1747 fprintf(debugFP, ">ICS: ");
1748 show_bytes(debugFP, buf, newcount);
1749 fprintf(debugFP, "\n");
1751 outcount = OutputToProcess(pr, buf, newcount, outError);
1752 if (outcount < newcount) return -1; /* to be sure */
1757 read_from_player(isr, closure, message, count, error)
1764 int outError, outCount;
1765 static int gotEof = 0;
1767 /* Pass data read from player on to ICS */
1770 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1771 if (outCount < count) {
1772 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1774 } else if (count < 0) {
1775 RemoveInputSource(isr);
1776 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1777 } else if (gotEof++ > 0) {
1778 RemoveInputSource(isr);
1779 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1785 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1786 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1787 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1788 SendToICS("date\n");
1789 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1792 /* added routine for printf style output to ics */
1793 void ics_printf(char *format, ...)
1795 char buffer[MSG_SIZ];
1798 va_start(args, format);
1799 vsnprintf(buffer, sizeof(buffer), format, args);
1800 buffer[sizeof(buffer)-1] = '\0';
1809 int count, outCount, outError;
1811 if (icsPR == NULL) return;
1814 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1815 if (outCount < count) {
1816 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820 /* This is used for sending logon scripts to the ICS. Sending
1821 without a delay causes problems when using timestamp on ICC
1822 (at least on my machine). */
1824 SendToICSDelayed(s,msdelay)
1828 int count, outCount, outError;
1830 if (icsPR == NULL) return;
1833 if (appData.debugMode) {
1834 fprintf(debugFP, ">ICS: ");
1835 show_bytes(debugFP, s, count);
1836 fprintf(debugFP, "\n");
1838 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1840 if (outCount < count) {
1841 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846 /* Remove all highlighting escape sequences in s
1847 Also deletes any suffix starting with '('
1850 StripHighlightAndTitle(s)
1853 static char retbuf[MSG_SIZ];
1856 while (*s != NULLCHAR) {
1857 while (*s == '\033') {
1858 while (*s != NULLCHAR && !isalpha(*s)) s++;
1859 if (*s != NULLCHAR) s++;
1861 while (*s != NULLCHAR && *s != '\033') {
1862 if (*s == '(' || *s == '[') {
1873 /* Remove all highlighting escape sequences in s */
1878 static char retbuf[MSG_SIZ];
1881 while (*s != NULLCHAR) {
1882 while (*s == '\033') {
1883 while (*s != NULLCHAR && !isalpha(*s)) s++;
1884 if (*s != NULLCHAR) s++;
1886 while (*s != NULLCHAR && *s != '\033') {
1894 char *variantNames[] = VARIANT_NAMES;
1899 return variantNames[v];
1903 /* Identify a variant from the strings the chess servers use or the
1904 PGN Variant tag names we use. */
1911 VariantClass v = VariantNormal;
1912 int i, found = FALSE;
1918 /* [HGM] skip over optional board-size prefixes */
1919 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1920 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1921 while( *e++ != '_');
1924 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1928 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1929 if (StrCaseStr(e, variantNames[i])) {
1930 v = (VariantClass) i;
1937 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1938 || StrCaseStr(e, "wild/fr")
1939 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1940 v = VariantFischeRandom;
1941 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1942 (i = 1, p = StrCaseStr(e, "w"))) {
1944 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1951 case 0: /* FICS only, actually */
1953 /* Castling legal even if K starts on d-file */
1954 v = VariantWildCastle;
1959 /* Castling illegal even if K & R happen to start in
1960 normal positions. */
1961 v = VariantNoCastle;
1974 /* Castling legal iff K & R start in normal positions */
1980 /* Special wilds for position setup; unclear what to do here */
1981 v = VariantLoadable;
1984 /* Bizarre ICC game */
1985 v = VariantTwoKings;
1988 v = VariantKriegspiel;
1994 v = VariantFischeRandom;
1997 v = VariantCrazyhouse;
2000 v = VariantBughouse;
2006 /* Not quite the same as FICS suicide! */
2007 v = VariantGiveaway;
2013 v = VariantShatranj;
2016 /* Temporary names for future ICC types. The name *will* change in
2017 the next xboard/WinBoard release after ICC defines it. */
2055 v = VariantCapablanca;
2058 v = VariantKnightmate;
2064 v = VariantCylinder;
2070 v = VariantCapaRandom;
2073 v = VariantBerolina;
2085 /* Found "wild" or "w" in the string but no number;
2086 must assume it's normal chess. */
2090 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2091 if( (len > MSG_SIZ) && appData.debugMode )
2092 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2094 DisplayError(buf, 0);
2100 if (appData.debugMode) {
2101 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2102 e, wnum, VariantName(v));
2107 static int leftover_start = 0, leftover_len = 0;
2108 char star_match[STAR_MATCH_N][MSG_SIZ];
2110 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2111 advance *index beyond it, and set leftover_start to the new value of
2112 *index; else return FALSE. If pattern contains the character '*', it
2113 matches any sequence of characters not containing '\r', '\n', or the
2114 character following the '*' (if any), and the matched sequence(s) are
2115 copied into star_match.
2118 looking_at(buf, index, pattern)
2123 char *bufp = &buf[*index], *patternp = pattern;
2125 char *matchp = star_match[0];
2128 if (*patternp == NULLCHAR) {
2129 *index = leftover_start = bufp - buf;
2133 if (*bufp == NULLCHAR) return FALSE;
2134 if (*patternp == '*') {
2135 if (*bufp == *(patternp + 1)) {
2137 matchp = star_match[++star_count];
2141 } else if (*bufp == '\n' || *bufp == '\r') {
2143 if (*patternp == NULLCHAR)
2148 *matchp++ = *bufp++;
2152 if (*patternp != *bufp) return FALSE;
2159 SendToPlayer(data, length)
2163 int error, outCount;
2164 outCount = OutputToProcess(NoProc, data, length, &error);
2165 if (outCount < length) {
2166 DisplayFatalError(_("Error writing to display"), error, 1);
2171 PackHolding(packed, holding)
2183 switch (runlength) {
2194 sprintf(q, "%d", runlength);
2206 /* Telnet protocol requests from the front end */
2208 TelnetRequest(ddww, option)
2209 unsigned char ddww, option;
2211 unsigned char msg[3];
2212 int outCount, outError;
2214 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2216 if (appData.debugMode) {
2217 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2233 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2242 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2245 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2252 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2259 if (!appData.icsActive) return;
2260 TelnetRequest(TN_DO, TN_ECHO);
2266 if (!appData.icsActive) return;
2267 TelnetRequest(TN_DONT, TN_ECHO);
2271 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2273 /* put the holdings sent to us by the server on the board holdings area */
2274 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2278 if(gameInfo.holdingsWidth < 2) return;
2279 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2280 return; // prevent overwriting by pre-board holdings
2282 if( (int)lowestPiece >= BlackPawn ) {
2285 holdingsStartRow = BOARD_HEIGHT-1;
2288 holdingsColumn = BOARD_WIDTH-1;
2289 countsColumn = BOARD_WIDTH-2;
2290 holdingsStartRow = 0;
2294 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2295 board[i][holdingsColumn] = EmptySquare;
2296 board[i][countsColumn] = (ChessSquare) 0;
2298 while( (p=*holdings++) != NULLCHAR ) {
2299 piece = CharToPiece( ToUpper(p) );
2300 if(piece == EmptySquare) continue;
2301 /*j = (int) piece - (int) WhitePawn;*/
2302 j = PieceToNumber(piece);
2303 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2304 if(j < 0) continue; /* should not happen */
2305 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2306 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2307 board[holdingsStartRow+j*direction][countsColumn]++;
2313 VariantSwitch(Board board, VariantClass newVariant)
2315 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2316 static Board oldBoard;
2318 startedFromPositionFile = FALSE;
2319 if(gameInfo.variant == newVariant) return;
2321 /* [HGM] This routine is called each time an assignment is made to
2322 * gameInfo.variant during a game, to make sure the board sizes
2323 * are set to match the new variant. If that means adding or deleting
2324 * holdings, we shift the playing board accordingly
2325 * This kludge is needed because in ICS observe mode, we get boards
2326 * of an ongoing game without knowing the variant, and learn about the
2327 * latter only later. This can be because of the move list we requested,
2328 * in which case the game history is refilled from the beginning anyway,
2329 * but also when receiving holdings of a crazyhouse game. In the latter
2330 * case we want to add those holdings to the already received position.
2334 if (appData.debugMode) {
2335 fprintf(debugFP, "Switch board from %s to %s\n",
2336 VariantName(gameInfo.variant), VariantName(newVariant));
2337 setbuf(debugFP, NULL);
2339 shuffleOpenings = 0; /* [HGM] shuffle */
2340 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2344 newWidth = 9; newHeight = 9;
2345 gameInfo.holdingsSize = 7;
2346 case VariantBughouse:
2347 case VariantCrazyhouse:
2348 newHoldingsWidth = 2; break;
2352 newHoldingsWidth = 2;
2353 gameInfo.holdingsSize = 8;
2356 case VariantCapablanca:
2357 case VariantCapaRandom:
2360 newHoldingsWidth = gameInfo.holdingsSize = 0;
2363 if(newWidth != gameInfo.boardWidth ||
2364 newHeight != gameInfo.boardHeight ||
2365 newHoldingsWidth != gameInfo.holdingsWidth ) {
2367 /* shift position to new playing area, if needed */
2368 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2369 for(i=0; i<BOARD_HEIGHT; i++)
2370 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2371 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2373 for(i=0; i<newHeight; i++) {
2374 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2375 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2377 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2378 for(i=0; i<BOARD_HEIGHT; i++)
2379 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2380 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2383 gameInfo.boardWidth = newWidth;
2384 gameInfo.boardHeight = newHeight;
2385 gameInfo.holdingsWidth = newHoldingsWidth;
2386 gameInfo.variant = newVariant;
2387 InitDrawingSizes(-2, 0);
2388 } else gameInfo.variant = newVariant;
2389 CopyBoard(oldBoard, board); // remember correctly formatted board
2390 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2391 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2394 static int loggedOn = FALSE;
2396 /*-- Game start info cache: --*/
2398 char gs_kind[MSG_SIZ];
2399 static char player1Name[128] = "";
2400 static char player2Name[128] = "";
2401 static char cont_seq[] = "\n\\ ";
2402 static int player1Rating = -1;
2403 static int player2Rating = -1;
2404 /*----------------------------*/
2406 ColorClass curColor = ColorNormal;
2407 int suppressKibitz = 0;
2410 Boolean soughtPending = FALSE;
2411 Boolean seekGraphUp;
2412 #define MAX_SEEK_ADS 200
2414 char *seekAdList[MAX_SEEK_ADS];
2415 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2416 float tcList[MAX_SEEK_ADS];
2417 char colorList[MAX_SEEK_ADS];
2418 int nrOfSeekAds = 0;
2419 int minRating = 1010, maxRating = 2800;
2420 int hMargin = 10, vMargin = 20, h, w;
2421 extern int squareSize, lineGap;
2426 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2427 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2428 if(r < minRating+100 && r >=0 ) r = minRating+100;
2429 if(r > maxRating) r = maxRating;
2430 if(tc < 1.) tc = 1.;
2431 if(tc > 95.) tc = 95.;
2432 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2433 y = ((double)r - minRating)/(maxRating - minRating)
2434 * (h-vMargin-squareSize/8-1) + vMargin;
2435 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2436 if(strstr(seekAdList[i], " u ")) color = 1;
2437 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2438 !strstr(seekAdList[i], "bullet") &&
2439 !strstr(seekAdList[i], "blitz") &&
2440 !strstr(seekAdList[i], "standard") ) color = 2;
2441 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2442 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2446 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2448 char buf[MSG_SIZ], *ext = "";
2449 VariantClass v = StringToVariant(type);
2450 if(strstr(type, "wild")) {
2451 ext = type + 4; // append wild number
2452 if(v == VariantFischeRandom) type = "chess960"; else
2453 if(v == VariantLoadable) type = "setup"; else
2454 type = VariantName(v);
2456 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2457 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2458 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2459 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2460 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2461 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2462 seekNrList[nrOfSeekAds] = nr;
2463 zList[nrOfSeekAds] = 0;
2464 seekAdList[nrOfSeekAds++] = StrSave(buf);
2465 if(plot) PlotSeekAd(nrOfSeekAds-1);
2472 int x = xList[i], y = yList[i], d=squareSize/4, k;
2473 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2474 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2475 // now replot every dot that overlapped
2476 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2477 int xx = xList[k], yy = yList[k];
2478 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2479 DrawSeekDot(xx, yy, colorList[k]);
2484 RemoveSeekAd(int nr)
2487 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2489 if(seekAdList[i]) free(seekAdList[i]);
2490 seekAdList[i] = seekAdList[--nrOfSeekAds];
2491 seekNrList[i] = seekNrList[nrOfSeekAds];
2492 ratingList[i] = ratingList[nrOfSeekAds];
2493 colorList[i] = colorList[nrOfSeekAds];
2494 tcList[i] = tcList[nrOfSeekAds];
2495 xList[i] = xList[nrOfSeekAds];
2496 yList[i] = yList[nrOfSeekAds];
2497 zList[i] = zList[nrOfSeekAds];
2498 seekAdList[nrOfSeekAds] = NULL;
2504 MatchSoughtLine(char *line)
2506 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2507 int nr, base, inc, u=0; char dummy;
2509 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2510 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2512 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2513 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2514 // match: compact and save the line
2515 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2525 if(!seekGraphUp) return FALSE;
2526 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2527 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2529 DrawSeekBackground(0, 0, w, h);
2530 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2531 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2532 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2533 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2535 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2538 snprintf(buf, MSG_SIZ, "%d", i);
2539 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2542 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2543 for(i=1; i<100; i+=(i<10?1:5)) {
2544 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2545 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2546 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2548 snprintf(buf, MSG_SIZ, "%d", i);
2549 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2552 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2556 int SeekGraphClick(ClickType click, int x, int y, int moving)
2558 static int lastDown = 0, displayed = 0, lastSecond;
2559 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2560 if(click == Release || moving) return FALSE;
2562 soughtPending = TRUE;
2563 SendToICS(ics_prefix);
2564 SendToICS("sought\n"); // should this be "sought all"?
2565 } else { // issue challenge based on clicked ad
2566 int dist = 10000; int i, closest = 0, second = 0;
2567 for(i=0; i<nrOfSeekAds; i++) {
2568 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2569 if(d < dist) { dist = d; closest = i; }
2570 second += (d - zList[i] < 120); // count in-range ads
2571 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2575 second = (second > 1);
2576 if(displayed != closest || second != lastSecond) {
2577 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2578 lastSecond = second; displayed = closest;
2580 if(click == Press) {
2581 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2584 } // on press 'hit', only show info
2585 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2586 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2587 SendToICS(ics_prefix);
2589 return TRUE; // let incoming board of started game pop down the graph
2590 } else if(click == Release) { // release 'miss' is ignored
2591 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2592 if(moving == 2) { // right up-click
2593 nrOfSeekAds = 0; // refresh graph
2594 soughtPending = TRUE;
2595 SendToICS(ics_prefix);
2596 SendToICS("sought\n"); // should this be "sought all"?
2599 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2600 // press miss or release hit 'pop down' seek graph
2601 seekGraphUp = FALSE;
2602 DrawPosition(TRUE, NULL);
2608 read_from_ics(isr, closure, data, count, error)
2615 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2616 #define STARTED_NONE 0
2617 #define STARTED_MOVES 1
2618 #define STARTED_BOARD 2
2619 #define STARTED_OBSERVE 3
2620 #define STARTED_HOLDINGS 4
2621 #define STARTED_CHATTER 5
2622 #define STARTED_COMMENT 6
2623 #define STARTED_MOVES_NOHIDE 7
2625 static int started = STARTED_NONE;
2626 static char parse[20000];
2627 static int parse_pos = 0;
2628 static char buf[BUF_SIZE + 1];
2629 static int firstTime = TRUE, intfSet = FALSE;
2630 static ColorClass prevColor = ColorNormal;
2631 static int savingComment = FALSE;
2632 static int cmatch = 0; // continuation sequence match
2639 int backup; /* [DM] For zippy color lines */
2641 char talker[MSG_SIZ]; // [HGM] chat
2644 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2646 if (appData.debugMode) {
2648 fprintf(debugFP, "<ICS: ");
2649 show_bytes(debugFP, data, count);
2650 fprintf(debugFP, "\n");
2654 if (appData.debugMode) { int f = forwardMostMove;
2655 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2656 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2657 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2660 /* If last read ended with a partial line that we couldn't parse,
2661 prepend it to the new read and try again. */
2662 if (leftover_len > 0) {
2663 for (i=0; i<leftover_len; i++)
2664 buf[i] = buf[leftover_start + i];
2667 /* copy new characters into the buffer */
2668 bp = buf + leftover_len;
2669 buf_len=leftover_len;
2670 for (i=0; i<count; i++)
2673 if (data[i] == '\r')
2676 // join lines split by ICS?
2677 if (!appData.noJoin)
2680 Joining just consists of finding matches against the
2681 continuation sequence, and discarding that sequence
2682 if found instead of copying it. So, until a match
2683 fails, there's nothing to do since it might be the
2684 complete sequence, and thus, something we don't want
2687 if (data[i] == cont_seq[cmatch])
2690 if (cmatch == strlen(cont_seq))
2692 cmatch = 0; // complete match. just reset the counter
2695 it's possible for the ICS to not include the space
2696 at the end of the last word, making our [correct]
2697 join operation fuse two separate words. the server
2698 does this when the space occurs at the width setting.
2700 if (!buf_len || buf[buf_len-1] != ' ')
2711 match failed, so we have to copy what matched before
2712 falling through and copying this character. In reality,
2713 this will only ever be just the newline character, but
2714 it doesn't hurt to be precise.
2716 strncpy(bp, cont_seq, cmatch);
2728 buf[buf_len] = NULLCHAR;
2729 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734 while (i < buf_len) {
2735 /* Deal with part of the TELNET option negotiation
2736 protocol. We refuse to do anything beyond the
2737 defaults, except that we allow the WILL ECHO option,
2738 which ICS uses to turn off password echoing when we are
2739 directly connected to it. We reject this option
2740 if localLineEditing mode is on (always on in xboard)
2741 and we are talking to port 23, which might be a real
2742 telnet server that will try to keep WILL ECHO on permanently.
2744 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2745 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2746 unsigned char option;
2748 switch ((unsigned char) buf[++i]) {
2750 if (appData.debugMode)
2751 fprintf(debugFP, "\n<WILL ");
2752 switch (option = (unsigned char) buf[++i]) {
2754 if (appData.debugMode)
2755 fprintf(debugFP, "ECHO ");
2756 /* Reply only if this is a change, according
2757 to the protocol rules. */
2758 if (remoteEchoOption) break;
2759 if (appData.localLineEditing &&
2760 atoi(appData.icsPort) == TN_PORT) {
2761 TelnetRequest(TN_DONT, TN_ECHO);
2764 TelnetRequest(TN_DO, TN_ECHO);
2765 remoteEchoOption = TRUE;
2769 if (appData.debugMode)
2770 fprintf(debugFP, "%d ", option);
2771 /* Whatever this is, we don't want it. */
2772 TelnetRequest(TN_DONT, option);
2777 if (appData.debugMode)
2778 fprintf(debugFP, "\n<WONT ");
2779 switch (option = (unsigned char) buf[++i]) {
2781 if (appData.debugMode)
2782 fprintf(debugFP, "ECHO ");
2783 /* Reply only if this is a change, according
2784 to the protocol rules. */
2785 if (!remoteEchoOption) break;
2787 TelnetRequest(TN_DONT, TN_ECHO);
2788 remoteEchoOption = FALSE;
2791 if (appData.debugMode)
2792 fprintf(debugFP, "%d ", (unsigned char) option);
2793 /* Whatever this is, it must already be turned
2794 off, because we never agree to turn on
2795 anything non-default, so according to the
2796 protocol rules, we don't reply. */
2801 if (appData.debugMode)
2802 fprintf(debugFP, "\n<DO ");
2803 switch (option = (unsigned char) buf[++i]) {
2805 /* Whatever this is, we refuse to do it. */
2806 if (appData.debugMode)
2807 fprintf(debugFP, "%d ", option);
2808 TelnetRequest(TN_WONT, option);
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<DONT ");
2815 switch (option = (unsigned char) buf[++i]) {
2817 if (appData.debugMode)
2818 fprintf(debugFP, "%d ", option);
2819 /* Whatever this is, we are already not doing
2820 it, because we never agree to do anything
2821 non-default, so according to the protocol
2822 rules, we don't reply. */
2827 if (appData.debugMode)
2828 fprintf(debugFP, "\n<IAC ");
2829 /* Doubled IAC; pass it through */
2833 if (appData.debugMode)
2834 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2835 /* Drop all other telnet commands on the floor */
2838 if (oldi > next_out)
2839 SendToPlayer(&buf[next_out], oldi - next_out);
2845 /* OK, this at least will *usually* work */
2846 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2850 if (loggedOn && !intfSet) {
2851 if (ics_type == ICS_ICC) {
2852 snprintf(str, MSG_SIZ,
2853 "/set-quietly interface %s\n/set-quietly style 12\n",
2855 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2856 strcat(str, "/set-2 51 1\n/set seek 1\n");
2857 } else if (ics_type == ICS_CHESSNET) {
2858 snprintf(str, MSG_SIZ, "/style 12\n");
2860 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2861 strcat(str, programVersion);
2862 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2863 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2866 strcat(str, "$iset nohighlight 1\n");
2868 strcat(str, "$iset lock 1\n$style 12\n");
2871 NotifyFrontendLogin();
2875 if (started == STARTED_COMMENT) {
2876 /* Accumulate characters in comment */
2877 parse[parse_pos++] = buf[i];
2878 if (buf[i] == '\n') {
2879 parse[parse_pos] = NULLCHAR;
2880 if(chattingPartner>=0) {
2882 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2883 OutputChatMessage(chattingPartner, mess);
2884 chattingPartner = -1;
2885 next_out = i+1; // [HGM] suppress printing in ICS window
2887 if(!suppressKibitz) // [HGM] kibitz
2888 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2889 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2890 int nrDigit = 0, nrAlph = 0, j;
2891 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2892 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2893 parse[parse_pos] = NULLCHAR;
2894 // try to be smart: if it does not look like search info, it should go to
2895 // ICS interaction window after all, not to engine-output window.
2896 for(j=0; j<parse_pos; j++) { // count letters and digits
2897 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2898 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2899 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2901 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2902 int depth=0; float score;
2903 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2904 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2905 pvInfoList[forwardMostMove-1].depth = depth;
2906 pvInfoList[forwardMostMove-1].score = 100*score;
2908 OutputKibitz(suppressKibitz, parse);
2911 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2912 SendToPlayer(tmp, strlen(tmp));
2914 next_out = i+1; // [HGM] suppress printing in ICS window
2916 started = STARTED_NONE;
2918 /* Don't match patterns against characters in comment */
2923 if (started == STARTED_CHATTER) {
2924 if (buf[i] != '\n') {
2925 /* Don't match patterns against characters in chatter */
2929 started = STARTED_NONE;
2930 if(suppressKibitz) next_out = i+1;
2933 /* Kludge to deal with rcmd protocol */
2934 if (firstTime && looking_at(buf, &i, "\001*")) {
2935 DisplayFatalError(&buf[1], 0, 1);
2941 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2944 if (appData.debugMode)
2945 fprintf(debugFP, "ics_type %d\n", ics_type);
2948 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2949 ics_type = ICS_FICS;
2951 if (appData.debugMode)
2952 fprintf(debugFP, "ics_type %d\n", ics_type);
2955 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2956 ics_type = ICS_CHESSNET;
2958 if (appData.debugMode)
2959 fprintf(debugFP, "ics_type %d\n", ics_type);
2964 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2965 looking_at(buf, &i, "Logging you in as \"*\"") ||
2966 looking_at(buf, &i, "will be \"*\""))) {
2967 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2971 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2973 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2974 DisplayIcsInteractionTitle(buf);
2975 have_set_title = TRUE;
2978 /* skip finger notes */
2979 if (started == STARTED_NONE &&
2980 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2981 (buf[i] == '1' && buf[i+1] == '0')) &&
2982 buf[i+2] == ':' && buf[i+3] == ' ') {
2983 started = STARTED_CHATTER;
2989 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2990 if(appData.seekGraph) {
2991 if(soughtPending && MatchSoughtLine(buf+i)) {
2992 i = strstr(buf+i, "rated") - buf;
2993 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2994 next_out = leftover_start = i;
2995 started = STARTED_CHATTER;
2996 suppressKibitz = TRUE;
2999 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3000 && looking_at(buf, &i, "* ads displayed")) {
3001 soughtPending = FALSE;
3006 if(appData.autoRefresh) {
3007 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3008 int s = (ics_type == ICS_ICC); // ICC format differs
3010 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3011 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3012 looking_at(buf, &i, "*% "); // eat prompt
3013 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3014 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015 next_out = i; // suppress
3018 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3019 char *p = star_match[0];
3021 if(seekGraphUp) RemoveSeekAd(atoi(p));
3022 while(*p && *p++ != ' '); // next
3024 looking_at(buf, &i, "*% "); // eat prompt
3025 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032 /* skip formula vars */
3033 if (started == STARTED_NONE &&
3034 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3035 started = STARTED_CHATTER;
3040 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3041 if (appData.autoKibitz && started == STARTED_NONE &&
3042 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3043 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3044 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3045 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3046 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3047 suppressKibitz = TRUE;
3048 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3051 && (gameMode == IcsPlayingWhite)) ||
3052 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3053 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3054 started = STARTED_CHATTER; // own kibitz we simply discard
3056 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3057 parse_pos = 0; parse[0] = NULLCHAR;
3058 savingComment = TRUE;
3059 suppressKibitz = gameMode != IcsObserving ? 2 :
3060 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3064 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3065 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3066 && atoi(star_match[0])) {
3067 // suppress the acknowledgements of our own autoKibitz
3069 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3071 SendToPlayer(star_match[0], strlen(star_match[0]));
3072 if(looking_at(buf, &i, "*% ")) // eat prompt
3073 suppressKibitz = FALSE;
3077 } // [HGM] kibitz: end of patch
3079 // [HGM] chat: intercept tells by users for which we have an open chat window
3081 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3082 looking_at(buf, &i, "* whispers:") ||
3083 looking_at(buf, &i, "* kibitzes:") ||
3084 looking_at(buf, &i, "* shouts:") ||
3085 looking_at(buf, &i, "* c-shouts:") ||
3086 looking_at(buf, &i, "--> * ") ||
3087 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3088 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3089 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3090 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3092 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3093 chattingPartner = -1;
3095 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3096 for(p=0; p<MAX_CHAT; p++) {
3097 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3098 talker[0] = '['; strcat(talker, "] ");
3099 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3100 chattingPartner = p; break;
3103 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3104 for(p=0; p<MAX_CHAT; p++) {
3105 if(!strcmp("kibitzes", chatPartner[p])) {
3106 talker[0] = '['; strcat(talker, "] ");
3107 chattingPartner = p; break;
3110 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3111 for(p=0; p<MAX_CHAT; p++) {
3112 if(!strcmp("whispers", chatPartner[p])) {
3113 talker[0] = '['; strcat(talker, "] ");
3114 chattingPartner = p; break;
3117 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3118 if(buf[i-8] == '-' && buf[i-3] == 't')
3119 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3120 if(!strcmp("c-shouts", chatPartner[p])) {
3121 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3122 chattingPartner = p; break;
3125 if(chattingPartner < 0)
3126 for(p=0; p<MAX_CHAT; p++) {
3127 if(!strcmp("shouts", chatPartner[p])) {
3128 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3129 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3130 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3131 chattingPartner = p; break;
3135 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3136 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3137 talker[0] = 0; Colorize(ColorTell, FALSE);
3138 chattingPartner = p; break;
3140 if(chattingPartner<0) i = oldi; else {
3141 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3142 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 started = STARTED_COMMENT;
3145 parse_pos = 0; parse[0] = NULLCHAR;
3146 savingComment = 3 + chattingPartner; // counts as TRUE
3147 suppressKibitz = TRUE;
3150 } // [HGM] chat: end of patch
3153 if (appData.zippyTalk || appData.zippyPlay) {
3154 /* [DM] Backup address for color zippy lines */
3156 if (loggedOn == TRUE)
3157 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3158 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3160 } // [DM] 'else { ' deleted
3162 /* Regular tells and says */
3163 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3164 looking_at(buf, &i, "* (your partner) tells you: ") ||
3165 looking_at(buf, &i, "* says: ") ||
3166 /* Don't color "message" or "messages" output */
3167 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3168 looking_at(buf, &i, "*. * at *:*: ") ||
3169 looking_at(buf, &i, "--* (*:*): ") ||
3170 /* Message notifications (same color as tells) */
3171 looking_at(buf, &i, "* has left a message ") ||
3172 looking_at(buf, &i, "* just sent you a message:\n") ||
3173 /* Whispers and kibitzes */
3174 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3175 looking_at(buf, &i, "* kibitzes: ") ||
3177 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3179 if (tkind == 1 && strchr(star_match[0], ':')) {
3180 /* Avoid "tells you:" spoofs in channels */
3183 if (star_match[0][0] == NULLCHAR ||
3184 strchr(star_match[0], ' ') ||
3185 (tkind == 3 && strchr(star_match[1], ' '))) {
3186 /* Reject bogus matches */
3189 if (appData.colorize) {
3190 if (oldi > next_out) {
3191 SendToPlayer(&buf[next_out], oldi - next_out);
3196 Colorize(ColorTell, FALSE);
3197 curColor = ColorTell;
3200 Colorize(ColorKibitz, FALSE);
3201 curColor = ColorKibitz;
3204 p = strrchr(star_match[1], '(');
3211 Colorize(ColorChannel1, FALSE);
3212 curColor = ColorChannel1;
3214 Colorize(ColorChannel, FALSE);
3215 curColor = ColorChannel;
3219 curColor = ColorNormal;
3223 if (started == STARTED_NONE && appData.autoComment &&
3224 (gameMode == IcsObserving ||
3225 gameMode == IcsPlayingWhite ||
3226 gameMode == IcsPlayingBlack)) {
3227 parse_pos = i - oldi;
3228 memcpy(parse, &buf[oldi], parse_pos);
3229 parse[parse_pos] = NULLCHAR;
3230 started = STARTED_COMMENT;
3231 savingComment = TRUE;
3233 started = STARTED_CHATTER;
3234 savingComment = FALSE;
3241 if (looking_at(buf, &i, "* s-shouts: ") ||
3242 looking_at(buf, &i, "* c-shouts: ")) {
3243 if (appData.colorize) {
3244 if (oldi > next_out) {
3245 SendToPlayer(&buf[next_out], oldi - next_out);
3248 Colorize(ColorSShout, FALSE);
3249 curColor = ColorSShout;
3252 started = STARTED_CHATTER;
3256 if (looking_at(buf, &i, "--->")) {
3261 if (looking_at(buf, &i, "* shouts: ") ||
3262 looking_at(buf, &i, "--> ")) {
3263 if (appData.colorize) {
3264 if (oldi > next_out) {
3265 SendToPlayer(&buf[next_out], oldi - next_out);
3268 Colorize(ColorShout, FALSE);
3269 curColor = ColorShout;
3272 started = STARTED_CHATTER;
3276 if (looking_at( buf, &i, "Challenge:")) {
3277 if (appData.colorize) {
3278 if (oldi > next_out) {
3279 SendToPlayer(&buf[next_out], oldi - next_out);
3282 Colorize(ColorChallenge, FALSE);
3283 curColor = ColorChallenge;
3289 if (looking_at(buf, &i, "* offers you") ||
3290 looking_at(buf, &i, "* offers to be") ||
3291 looking_at(buf, &i, "* would like to") ||
3292 looking_at(buf, &i, "* requests to") ||
3293 looking_at(buf, &i, "Your opponent offers") ||
3294 looking_at(buf, &i, "Your opponent requests")) {
3296 if (appData.colorize) {
3297 if (oldi > next_out) {
3298 SendToPlayer(&buf[next_out], oldi - next_out);
3301 Colorize(ColorRequest, FALSE);
3302 curColor = ColorRequest;
3307 if (looking_at(buf, &i, "* (*) seeking")) {
3308 if (appData.colorize) {
3309 if (oldi > next_out) {
3310 SendToPlayer(&buf[next_out], oldi - next_out);
3313 Colorize(ColorSeek, FALSE);
3314 curColor = ColorSeek;
3319 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3321 if (looking_at(buf, &i, "\\ ")) {
3322 if (prevColor != ColorNormal) {
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 Colorize(prevColor, TRUE);
3328 curColor = prevColor;
3330 if (savingComment) {
3331 parse_pos = i - oldi;
3332 memcpy(parse, &buf[oldi], parse_pos);
3333 parse[parse_pos] = NULLCHAR;
3334 started = STARTED_COMMENT;
3335 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3336 chattingPartner = savingComment - 3; // kludge to remember the box
3338 started = STARTED_CHATTER;
3343 if (looking_at(buf, &i, "Black Strength :") ||
3344 looking_at(buf, &i, "<<< style 10 board >>>") ||
3345 looking_at(buf, &i, "<10>") ||
3346 looking_at(buf, &i, "#@#")) {
3347 /* Wrong board style */
3349 SendToICS(ics_prefix);
3350 SendToICS("set style 12\n");
3351 SendToICS(ics_prefix);
3352 SendToICS("refresh\n");
3356 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3358 have_sent_ICS_logon = 1;
3362 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3363 (looking_at(buf, &i, "\n<12> ") ||
3364 looking_at(buf, &i, "<12> "))) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 started = STARTED_BOARD;
3375 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3376 looking_at(buf, &i, "<b1> ")) {
3377 if (oldi > next_out) {
3378 SendToPlayer(&buf[next_out], oldi - next_out);
3381 started = STARTED_HOLDINGS;
3386 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3388 /* Header for a move list -- first line */
3390 switch (ics_getting_history) {
3394 case BeginningOfGame:
3395 /* User typed "moves" or "oldmoves" while we
3396 were idle. Pretend we asked for these
3397 moves and soak them up so user can step
3398 through them and/or save them.
3401 gameMode = IcsObserving;
3404 ics_getting_history = H_GOT_UNREQ_HEADER;
3406 case EditGame: /*?*/
3407 case EditPosition: /*?*/
3408 /* Should above feature work in these modes too? */
3409 /* For now it doesn't */
3410 ics_getting_history = H_GOT_UNWANTED_HEADER;
3413 ics_getting_history = H_GOT_UNWANTED_HEADER;
3418 /* Is this the right one? */
3419 if (gameInfo.white && gameInfo.black &&
3420 strcmp(gameInfo.white, star_match[0]) == 0 &&
3421 strcmp(gameInfo.black, star_match[2]) == 0) {
3423 ics_getting_history = H_GOT_REQ_HEADER;
3426 case H_GOT_REQ_HEADER:
3427 case H_GOT_UNREQ_HEADER:
3428 case H_GOT_UNWANTED_HEADER:
3429 case H_GETTING_MOVES:
3430 /* Should not happen */
3431 DisplayError(_("Error gathering move list: two headers"), 0);
3432 ics_getting_history = H_FALSE;
3436 /* Save player ratings into gameInfo if needed */
3437 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3438 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3439 (gameInfo.whiteRating == -1 ||
3440 gameInfo.blackRating == -1)) {
3442 gameInfo.whiteRating = string_to_rating(star_match[1]);
3443 gameInfo.blackRating = string_to_rating(star_match[3]);
3444 if (appData.debugMode)
3445 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3446 gameInfo.whiteRating, gameInfo.blackRating);
3451 if (looking_at(buf, &i,
3452 "* * match, initial time: * minute*, increment: * second")) {
3453 /* Header for a move list -- second line */
3454 /* Initial board will follow if this is a wild game */
3455 if (gameInfo.event != NULL) free(gameInfo.event);
3456 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3457 gameInfo.event = StrSave(str);
3458 /* [HGM] we switched variant. Translate boards if needed. */
3459 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3463 if (looking_at(buf, &i, "Move ")) {
3464 /* Beginning of a move list */
3465 switch (ics_getting_history) {
3467 /* Normally should not happen */
3468 /* Maybe user hit reset while we were parsing */
3471 /* Happens if we are ignoring a move list that is not
3472 * the one we just requested. Common if the user
3473 * tries to observe two games without turning off
3476 case H_GETTING_MOVES:
3477 /* Should not happen */
3478 DisplayError(_("Error gathering move list: nested"), 0);
3479 ics_getting_history = H_FALSE;
3481 case H_GOT_REQ_HEADER:
3482 ics_getting_history = H_GETTING_MOVES;
3483 started = STARTED_MOVES;
3485 if (oldi > next_out) {
3486 SendToPlayer(&buf[next_out], oldi - next_out);
3489 case H_GOT_UNREQ_HEADER:
3490 ics_getting_history = H_GETTING_MOVES;
3491 started = STARTED_MOVES_NOHIDE;
3494 case H_GOT_UNWANTED_HEADER:
3495 ics_getting_history = H_FALSE;
3501 if (looking_at(buf, &i, "% ") ||
3502 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3503 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3504 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3505 soughtPending = FALSE;
3509 if(suppressKibitz) next_out = i;
3510 savingComment = FALSE;
3514 case STARTED_MOVES_NOHIDE:
3515 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3516 parse[parse_pos + i - oldi] = NULLCHAR;
3517 ParseGameHistory(parse);
3519 if (appData.zippyPlay && first.initDone) {
3520 FeedMovesToProgram(&first, forwardMostMove);
3521 if (gameMode == IcsPlayingWhite) {
3522 if (WhiteOnMove(forwardMostMove)) {
3523 if (first.sendTime) {
3524 if (first.useColors) {
3525 SendToProgram("black\n", &first);
3527 SendTimeRemaining(&first, TRUE);
3529 if (first.useColors) {
3530 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3532 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3533 first.maybeThinking = TRUE;
3535 if (first.usePlayother) {
3536 if (first.sendTime) {
3537 SendTimeRemaining(&first, TRUE);
3539 SendToProgram("playother\n", &first);
3545 } else if (gameMode == IcsPlayingBlack) {
3546 if (!WhiteOnMove(forwardMostMove)) {
3547 if (first.sendTime) {
3548 if (first.useColors) {
3549 SendToProgram("white\n", &first);
3551 SendTimeRemaining(&first, FALSE);
3553 if (first.useColors) {
3554 SendToProgram("black\n", &first);
3556 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3557 first.maybeThinking = TRUE;
3559 if (first.usePlayother) {
3560 if (first.sendTime) {
3561 SendTimeRemaining(&first, FALSE);
3563 SendToProgram("playother\n", &first);
3572 if (gameMode == IcsObserving && ics_gamenum == -1) {
3573 /* Moves came from oldmoves or moves command
3574 while we weren't doing anything else.
3576 currentMove = forwardMostMove;
3577 ClearHighlights();/*!!could figure this out*/
3578 flipView = appData.flipView;
3579 DrawPosition(TRUE, boards[currentMove]);
3580 DisplayBothClocks();
3581 snprintf(str, MSG_SIZ, "%s vs. %s",
3582 gameInfo.white, gameInfo.black);
3586 /* Moves were history of an active game */
3587 if (gameInfo.resultDetails != NULL) {
3588 free(gameInfo.resultDetails);
3589 gameInfo.resultDetails = NULL;
3592 HistorySet(parseList, backwardMostMove,
3593 forwardMostMove, currentMove-1);
3594 DisplayMove(currentMove - 1);
3595 if (started == STARTED_MOVES) next_out = i;
3596 started = STARTED_NONE;
3597 ics_getting_history = H_FALSE;
3600 case STARTED_OBSERVE:
3601 started = STARTED_NONE;
3602 SendToICS(ics_prefix);
3603 SendToICS("refresh\n");
3609 if(bookHit) { // [HGM] book: simulate book reply
3610 static char bookMove[MSG_SIZ]; // a bit generous?
3612 programStats.nodes = programStats.depth = programStats.time =
3613 programStats.score = programStats.got_only_move = 0;
3614 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3616 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3617 strcat(bookMove, bookHit);
3618 HandleMachineMove(bookMove, &first);
3623 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3624 started == STARTED_HOLDINGS ||
3625 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3626 /* Accumulate characters in move list or board */
3627 parse[parse_pos++] = buf[i];
3630 /* Start of game messages. Mostly we detect start of game
3631 when the first board image arrives. On some versions
3632 of the ICS, though, we need to do a "refresh" after starting
3633 to observe in order to get the current board right away. */
3634 if (looking_at(buf, &i, "Adding game * to observation list")) {
3635 started = STARTED_OBSERVE;
3639 /* Handle auto-observe */
3640 if (appData.autoObserve &&
3641 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3642 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3644 /* Choose the player that was highlighted, if any. */
3645 if (star_match[0][0] == '\033' ||
3646 star_match[1][0] != '\033') {
3647 player = star_match[0];
3649 player = star_match[2];
3651 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3652 ics_prefix, StripHighlightAndTitle(player));
3655 /* Save ratings from notify string */
3656 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3657 player1Rating = string_to_rating(star_match[1]);
3658 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3659 player2Rating = string_to_rating(star_match[3]);
3661 if (appData.debugMode)
3663 "Ratings from 'Game notification:' %s %d, %s %d\n",
3664 player1Name, player1Rating,
3665 player2Name, player2Rating);
3670 /* Deal with automatic examine mode after a game,
3671 and with IcsObserving -> IcsExamining transition */
3672 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3673 looking_at(buf, &i, "has made you an examiner of game *")) {
3675 int gamenum = atoi(star_match[0]);
3676 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3677 gamenum == ics_gamenum) {
3678 /* We were already playing or observing this game;
3679 no need to refetch history */
3680 gameMode = IcsExamining;
3682 pauseExamForwardMostMove = forwardMostMove;
3683 } else if (currentMove < forwardMostMove) {
3684 ForwardInner(forwardMostMove);
3687 /* I don't think this case really can happen */
3688 SendToICS(ics_prefix);
3689 SendToICS("refresh\n");
3694 /* Error messages */
3695 // if (ics_user_moved) {
3696 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3697 if (looking_at(buf, &i, "Illegal move") ||
3698 looking_at(buf, &i, "Not a legal move") ||
3699 looking_at(buf, &i, "Your king is in check") ||
3700 looking_at(buf, &i, "It isn't your turn") ||
3701 looking_at(buf, &i, "It is not your move")) {
3703 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3704 currentMove = forwardMostMove-1;
3705 DisplayMove(currentMove - 1); /* before DMError */
3706 DrawPosition(FALSE, boards[currentMove]);
3707 SwitchClocks(forwardMostMove-1); // [HGM] race
3708 DisplayBothClocks();
3710 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3716 if (looking_at(buf, &i, "still have time") ||
3717 looking_at(buf, &i, "not out of time") ||
3718 looking_at(buf, &i, "either player is out of time") ||
3719 looking_at(buf, &i, "has timeseal; checking")) {
3720 /* We must have called his flag a little too soon */
3721 whiteFlag = blackFlag = FALSE;
3725 if (looking_at(buf, &i, "added * seconds to") ||
3726 looking_at(buf, &i, "seconds were added to")) {
3727 /* Update the clocks */
3728 SendToICS(ics_prefix);
3729 SendToICS("refresh\n");
3733 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3734 ics_clock_paused = TRUE;
3739 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3740 ics_clock_paused = FALSE;
3745 /* Grab player ratings from the Creating: message.
3746 Note we have to check for the special case when
3747 the ICS inserts things like [white] or [black]. */
3748 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3749 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3751 0 player 1 name (not necessarily white)
3753 2 empty, white, or black (IGNORED)
3754 3 player 2 name (not necessarily black)
3757 The names/ratings are sorted out when the game
3758 actually starts (below).
3760 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3761 player1Rating = string_to_rating(star_match[1]);
3762 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3763 player2Rating = string_to_rating(star_match[4]);
3765 if (appData.debugMode)
3767 "Ratings from 'Creating:' %s %d, %s %d\n",
3768 player1Name, player1Rating,
3769 player2Name, player2Rating);
3774 /* Improved generic start/end-of-game messages */
3775 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3776 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3777 /* If tkind == 0: */
3778 /* star_match[0] is the game number */
3779 /* [1] is the white player's name */
3780 /* [2] is the black player's name */
3781 /* For end-of-game: */
3782 /* [3] is the reason for the game end */
3783 /* [4] is a PGN end game-token, preceded by " " */
3784 /* For start-of-game: */
3785 /* [3] begins with "Creating" or "Continuing" */
3786 /* [4] is " *" or empty (don't care). */
3787 int gamenum = atoi(star_match[0]);
3788 char *whitename, *blackname, *why, *endtoken;
3789 ChessMove endtype = EndOfFile;
3792 whitename = star_match[1];
3793 blackname = star_match[2];
3794 why = star_match[3];
3795 endtoken = star_match[4];
3797 whitename = star_match[1];
3798 blackname = star_match[3];
3799 why = star_match[5];
3800 endtoken = star_match[6];
3803 /* Game start messages */
3804 if (strncmp(why, "Creating ", 9) == 0 ||
3805 strncmp(why, "Continuing ", 11) == 0) {
3806 gs_gamenum = gamenum;
3807 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3808 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3810 if (appData.zippyPlay) {
3811 ZippyGameStart(whitename, blackname);
3814 partnerBoardValid = FALSE; // [HGM] bughouse
3818 /* Game end messages */
3819 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3820 ics_gamenum != gamenum) {
3823 while (endtoken[0] == ' ') endtoken++;
3824 switch (endtoken[0]) {
3827 endtype = GameUnfinished;
3830 endtype = BlackWins;
3833 if (endtoken[1] == '/')
3834 endtype = GameIsDrawn;
3836 endtype = WhiteWins;
3839 GameEnds(endtype, why, GE_ICS);
3841 if (appData.zippyPlay && first.initDone) {
3842 ZippyGameEnd(endtype, why);
3843 if (first.pr == NULL) {
3844 /* Start the next process early so that we'll
3845 be ready for the next challenge */
3846 StartChessProgram(&first);
3848 /* Send "new" early, in case this command takes
3849 a long time to finish, so that we'll be ready
3850 for the next challenge. */
3851 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3855 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3859 if (looking_at(buf, &i, "Removing game * from observation") ||
3860 looking_at(buf, &i, "no longer observing game *") ||
3861 looking_at(buf, &i, "Game * (*) has no examiners")) {
3862 if (gameMode == IcsObserving &&
3863 atoi(star_match[0]) == ics_gamenum)
3865 /* icsEngineAnalyze */
3866 if (appData.icsEngineAnalyze) {
3873 ics_user_moved = FALSE;
3878 if (looking_at(buf, &i, "no longer examining game *")) {
3879 if (gameMode == IcsExamining &&
3880 atoi(star_match[0]) == ics_gamenum)
3884 ics_user_moved = FALSE;
3889 /* Advance leftover_start past any newlines we find,
3890 so only partial lines can get reparsed */
3891 if (looking_at(buf, &i, "\n")) {
3892 prevColor = curColor;
3893 if (curColor != ColorNormal) {
3894 if (oldi > next_out) {
3895 SendToPlayer(&buf[next_out], oldi - next_out);
3898 Colorize(ColorNormal, FALSE);
3899 curColor = ColorNormal;
3901 if (started == STARTED_BOARD) {
3902 started = STARTED_NONE;
3903 parse[parse_pos] = NULLCHAR;
3904 ParseBoard12(parse);
3907 /* Send premove here */
3908 if (appData.premove) {
3910 if (currentMove == 0 &&
3911 gameMode == IcsPlayingWhite &&
3912 appData.premoveWhite) {
3913 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3914 if (appData.debugMode)
3915 fprintf(debugFP, "Sending premove:\n");
3917 } else if (currentMove == 1 &&
3918 gameMode == IcsPlayingBlack &&
3919 appData.premoveBlack) {
3920 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3921 if (appData.debugMode)
3922 fprintf(debugFP, "Sending premove:\n");
3924 } else if (gotPremove) {
3926 ClearPremoveHighlights();
3927 if (appData.debugMode)
3928 fprintf(debugFP, "Sending premove:\n");
3929 UserMoveEvent(premoveFromX, premoveFromY,
3930 premoveToX, premoveToY,
3935 /* Usually suppress following prompt */
3936 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3937 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3938 if (looking_at(buf, &i, "*% ")) {
3939 savingComment = FALSE;
3944 } else if (started == STARTED_HOLDINGS) {
3946 char new_piece[MSG_SIZ];
3947 started = STARTED_NONE;
3948 parse[parse_pos] = NULLCHAR;
3949 if (appData.debugMode)
3950 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3951 parse, currentMove);
3952 if (sscanf(parse, " game %d", &gamenum) == 1) {
3953 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3954 if (gameInfo.variant == VariantNormal) {
3955 /* [HGM] We seem to switch variant during a game!
3956 * Presumably no holdings were displayed, so we have
3957 * to move the position two files to the right to
3958 * create room for them!
3960 VariantClass newVariant;
3961 switch(gameInfo.boardWidth) { // base guess on board width
3962 case 9: newVariant = VariantShogi; break;
3963 case 10: newVariant = VariantGreat; break;
3964 default: newVariant = VariantCrazyhouse; break;
3966 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3967 /* Get a move list just to see the header, which
3968 will tell us whether this is really bug or zh */
3969 if (ics_getting_history == H_FALSE) {
3970 ics_getting_history = H_REQUESTED;
3971 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3975 new_piece[0] = NULLCHAR;
3976 sscanf(parse, "game %d white [%s black [%s <- %s",
3977 &gamenum, white_holding, black_holding,
3979 white_holding[strlen(white_holding)-1] = NULLCHAR;
3980 black_holding[strlen(black_holding)-1] = NULLCHAR;
3981 /* [HGM] copy holdings to board holdings area */
3982 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3983 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3984 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3986 if (appData.zippyPlay && first.initDone) {
3987 ZippyHoldings(white_holding, black_holding,
3991 if (tinyLayout || smallLayout) {
3992 char wh[16], bh[16];
3993 PackHolding(wh, white_holding);
3994 PackHolding(bh, black_holding);
3995 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3996 gameInfo.white, gameInfo.black);
3998 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3999 gameInfo.white, white_holding,
4000 gameInfo.black, black_holding);
4002 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4003 DrawPosition(FALSE, boards[currentMove]);
4005 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4006 sscanf(parse, "game %d white [%s black [%s <- %s",
4007 &gamenum, white_holding, black_holding,
4009 white_holding[strlen(white_holding)-1] = NULLCHAR;
4010 black_holding[strlen(black_holding)-1] = NULLCHAR;
4011 /* [HGM] copy holdings to partner-board holdings area */
4012 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4013 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4014 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4015 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4016 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4019 /* Suppress following prompt */
4020 if (looking_at(buf, &i, "*% ")) {
4021 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4022 savingComment = FALSE;
4030 i++; /* skip unparsed character and loop back */
4033 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4034 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4035 // SendToPlayer(&buf[next_out], i - next_out);
4036 started != STARTED_HOLDINGS && leftover_start > next_out) {
4037 SendToPlayer(&buf[next_out], leftover_start - next_out);
4041 leftover_len = buf_len - leftover_start;
4042 /* if buffer ends with something we couldn't parse,
4043 reparse it after appending the next read */
4045 } else if (count == 0) {
4046 RemoveInputSource(isr);
4047 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4049 DisplayFatalError(_("Error reading from ICS"), error, 1);
4054 /* Board style 12 looks like this:
4056 <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
4058 * The "<12> " is stripped before it gets to this routine. The two
4059 * trailing 0's (flip state and clock ticking) are later addition, and
4060 * some chess servers may not have them, or may have only the first.
4061 * Additional trailing fields may be added in the future.
4064 #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"
4066 #define RELATION_OBSERVING_PLAYED 0
4067 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4068 #define RELATION_PLAYING_MYMOVE 1
4069 #define RELATION_PLAYING_NOTMYMOVE -1
4070 #define RELATION_EXAMINING 2
4071 #define RELATION_ISOLATED_BOARD -3
4072 #define RELATION_STARTING_POSITION -4 /* FICS only */
4075 ParseBoard12(string)
4078 GameMode newGameMode;
4079 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4080 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4081 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4082 char to_play, board_chars[200];
4083 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4084 char black[32], white[32];
4086 int prevMove = currentMove;
4089 int fromX, fromY, toX, toY;
4091 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4092 char *bookHit = NULL; // [HGM] book
4093 Boolean weird = FALSE, reqFlag = FALSE;
4095 fromX = fromY = toX = toY = -1;
4099 if (appData.debugMode)
4100 fprintf(debugFP, _("Parsing board: %s\n"), string);
4102 move_str[0] = NULLCHAR;
4103 elapsed_time[0] = NULLCHAR;
4104 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4106 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4107 if(string[i] == ' ') { ranks++; files = 0; }
4109 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4112 for(j = 0; j <i; j++) board_chars[j] = string[j];
4113 board_chars[i] = '\0';
4116 n = sscanf(string, PATTERN, &to_play, &double_push,
4117 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4118 &gamenum, white, black, &relation, &basetime, &increment,
4119 &white_stren, &black_stren, &white_time, &black_time,
4120 &moveNum, str, elapsed_time, move_str, &ics_flip,
4124 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4125 DisplayError(str, 0);
4129 /* Convert the move number to internal form */
4130 moveNum = (moveNum - 1) * 2;
4131 if (to_play == 'B') moveNum++;
4132 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4133 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4139 case RELATION_OBSERVING_PLAYED:
4140 case RELATION_OBSERVING_STATIC:
4141 if (gamenum == -1) {
4142 /* Old ICC buglet */
4143 relation = RELATION_OBSERVING_STATIC;
4145 newGameMode = IcsObserving;
4147 case RELATION_PLAYING_MYMOVE:
4148 case RELATION_PLAYING_NOTMYMOVE:
4150 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4151 IcsPlayingWhite : IcsPlayingBlack;
4153 case RELATION_EXAMINING:
4154 newGameMode = IcsExamining;
4156 case RELATION_ISOLATED_BOARD:
4158 /* Just display this board. If user was doing something else,
4159 we will forget about it until the next board comes. */
4160 newGameMode = IcsIdle;
4162 case RELATION_STARTING_POSITION:
4163 newGameMode = gameMode;
4167 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4168 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4169 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4171 for (k = 0; k < ranks; k++) {
4172 for (j = 0; j < files; j++)
4173 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4174 if(gameInfo.holdingsWidth > 1) {
4175 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4176 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4179 CopyBoard(partnerBoard, board);
4180 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4181 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4182 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4183 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4184 if(toSqr = strchr(str, '-')) {
4185 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4186 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4187 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4188 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4189 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4190 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4191 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4192 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4193 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4194 DisplayMessage(partnerStatus, "");
4195 partnerBoardValid = TRUE;
4199 /* Modify behavior for initial board display on move listing
4202 switch (ics_getting_history) {
4206 case H_GOT_REQ_HEADER:
4207 case H_GOT_UNREQ_HEADER:
4208 /* This is the initial position of the current game */
4209 gamenum = ics_gamenum;
4210 moveNum = 0; /* old ICS bug workaround */
4211 if (to_play == 'B') {
4212 startedFromSetupPosition = TRUE;
4213 blackPlaysFirst = TRUE;
4215 if (forwardMostMove == 0) forwardMostMove = 1;
4216 if (backwardMostMove == 0) backwardMostMove = 1;
4217 if (currentMove == 0) currentMove = 1;
4219 newGameMode = gameMode;
4220 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4222 case H_GOT_UNWANTED_HEADER:
4223 /* This is an initial board that we don't want */
4225 case H_GETTING_MOVES:
4226 /* Should not happen */
4227 DisplayError(_("Error gathering move list: extra board"), 0);
4228 ics_getting_history = H_FALSE;
4232 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4233 weird && (int)gameInfo.variant < (int)VariantShogi) {
4234 /* [HGM] We seem to have switched variant unexpectedly
4235 * Try to guess new variant from board size
4237 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4238 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4239 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4240 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4241 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4242 if(!weird) newVariant = VariantNormal;
4243 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4244 /* Get a move list just to see the header, which
4245 will tell us whether this is really bug or zh */
4246 if (ics_getting_history == H_FALSE) {
4247 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4248 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4253 /* Take action if this is the first board of a new game, or of a
4254 different game than is currently being displayed. */
4255 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4256 relation == RELATION_ISOLATED_BOARD) {
4258 /* Forget the old game and get the history (if any) of the new one */
4259 if (gameMode != BeginningOfGame) {
4263 if (appData.autoRaiseBoard) BoardToTop();
4265 if (gamenum == -1) {
4266 newGameMode = IcsIdle;
4267 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4268 appData.getMoveList && !reqFlag) {
4269 /* Need to get game history */
4270 ics_getting_history = H_REQUESTED;
4271 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4275 /* Initially flip the board to have black on the bottom if playing
4276 black or if the ICS flip flag is set, but let the user change
4277 it with the Flip View button. */
4278 flipView = appData.autoFlipView ?
4279 (newGameMode == IcsPlayingBlack) || ics_flip :
4282 /* Done with values from previous mode; copy in new ones */
4283 gameMode = newGameMode;
4285 ics_gamenum = gamenum;
4286 if (gamenum == gs_gamenum) {
4287 int klen = strlen(gs_kind);
4288 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4289 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4290 gameInfo.event = StrSave(str);
4292 gameInfo.event = StrSave("ICS game");
4294 gameInfo.site = StrSave(appData.icsHost);
4295 gameInfo.date = PGNDate();
4296 gameInfo.round = StrSave("-");
4297 gameInfo.white = StrSave(white);
4298 gameInfo.black = StrSave(black);
4299 timeControl = basetime * 60 * 1000;
4301 timeIncrement = increment * 1000;
4302 movesPerSession = 0;
4303 gameInfo.timeControl = TimeControlTagValue();
4304 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4305 if (appData.debugMode) {
4306 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4307 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4308 setbuf(debugFP, NULL);
4311 gameInfo.outOfBook = NULL;
4313 /* Do we have the ratings? */
4314 if (strcmp(player1Name, white) == 0 &&
4315 strcmp(player2Name, black) == 0) {
4316 if (appData.debugMode)
4317 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4318 player1Rating, player2Rating);
4319 gameInfo.whiteRating = player1Rating;
4320 gameInfo.blackRating = player2Rating;
4321 } else if (strcmp(player2Name, white) == 0 &&
4322 strcmp(player1Name, black) == 0) {
4323 if (appData.debugMode)
4324 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4325 player2Rating, player1Rating);
4326 gameInfo.whiteRating = player2Rating;
4327 gameInfo.blackRating = player1Rating;
4329 player1Name[0] = player2Name[0] = NULLCHAR;
4331 /* Silence shouts if requested */
4332 if (appData.quietPlay &&
4333 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4334 SendToICS(ics_prefix);
4335 SendToICS("set shout 0\n");
4339 /* Deal with midgame name changes */
4341 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4342 if (gameInfo.white) free(gameInfo.white);
4343 gameInfo.white = StrSave(white);
4345 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4346 if (gameInfo.black) free(gameInfo.black);
4347 gameInfo.black = StrSave(black);
4351 /* Throw away game result if anything actually changes in examine mode */
4352 if (gameMode == IcsExamining && !newGame) {
4353 gameInfo.result = GameUnfinished;
4354 if (gameInfo.resultDetails != NULL) {
4355 free(gameInfo.resultDetails);
4356 gameInfo.resultDetails = NULL;
4360 /* In pausing && IcsExamining mode, we ignore boards coming
4361 in if they are in a different variation than we are. */
4362 if (pauseExamInvalid) return;
4363 if (pausing && gameMode == IcsExamining) {
4364 if (moveNum <= pauseExamForwardMostMove) {
4365 pauseExamInvalid = TRUE;
4366 forwardMostMove = pauseExamForwardMostMove;
4371 if (appData.debugMode) {
4372 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4374 /* Parse the board */
4375 for (k = 0; k < ranks; k++) {
4376 for (j = 0; j < files; j++)
4377 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4378 if(gameInfo.holdingsWidth > 1) {
4379 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4380 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4383 CopyBoard(boards[moveNum], board);
4384 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4386 startedFromSetupPosition =
4387 !CompareBoards(board, initialPosition);
4388 if(startedFromSetupPosition)
4389 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4392 /* [HGM] Set castling rights. Take the outermost Rooks,
4393 to make it also work for FRC opening positions. Note that board12
4394 is really defective for later FRC positions, as it has no way to
4395 indicate which Rook can castle if they are on the same side of King.
4396 For the initial position we grant rights to the outermost Rooks,
4397 and remember thos rights, and we then copy them on positions
4398 later in an FRC game. This means WB might not recognize castlings with
4399 Rooks that have moved back to their original position as illegal,
4400 but in ICS mode that is not its job anyway.
4402 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4403 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4405 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4406 if(board[0][i] == WhiteRook) j = i;
4407 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4408 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4409 if(board[0][i] == WhiteRook) j = i;
4410 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4411 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4412 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4413 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4414 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4415 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4416 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4419 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4420 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4421 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4422 if(board[BOARD_HEIGHT-1][k] == bKing)
4423 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4424 if(gameInfo.variant == VariantTwoKings) {
4425 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4426 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4427 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4430 r = boards[moveNum][CASTLING][0] = initialRights[0];
4431 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4432 r = boards[moveNum][CASTLING][1] = initialRights[1];
4433 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4434 r = boards[moveNum][CASTLING][3] = initialRights[3];
4435 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4436 r = boards[moveNum][CASTLING][4] = initialRights[4];
4437 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4438 /* wildcastle kludge: always assume King has rights */
4439 r = boards[moveNum][CASTLING][2] = initialRights[2];
4440 r = boards[moveNum][CASTLING][5] = initialRights[5];
4442 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4443 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4446 if (ics_getting_history == H_GOT_REQ_HEADER ||
4447 ics_getting_history == H_GOT_UNREQ_HEADER) {
4448 /* This was an initial position from a move list, not
4449 the current position */
4453 /* Update currentMove and known move number limits */
4454 newMove = newGame || moveNum > forwardMostMove;
4457 forwardMostMove = backwardMostMove = currentMove = moveNum;
4458 if (gameMode == IcsExamining && moveNum == 0) {
4459 /* Workaround for ICS limitation: we are not told the wild
4460 type when starting to examine a game. But if we ask for
4461 the move list, the move list header will tell us */
4462 ics_getting_history = H_REQUESTED;
4463 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4466 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4467 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4469 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4470 /* [HGM] applied this also to an engine that is silently watching */
4471 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4472 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4473 gameInfo.variant == currentlyInitializedVariant) {
4474 takeback = forwardMostMove - moveNum;
4475 for (i = 0; i < takeback; i++) {
4476 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4477 SendToProgram("undo\n", &first);
4482 forwardMostMove = moveNum;
4483 if (!pausing || currentMove > forwardMostMove)
4484 currentMove = forwardMostMove;
4486 /* New part of history that is not contiguous with old part */
4487 if (pausing && gameMode == IcsExamining) {
4488 pauseExamInvalid = TRUE;
4489 forwardMostMove = pauseExamForwardMostMove;
4492 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4494 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4495 // [HGM] when we will receive the move list we now request, it will be
4496 // fed to the engine from the first move on. So if the engine is not
4497 // in the initial position now, bring it there.
4498 InitChessProgram(&first, 0);
4501 ics_getting_history = H_REQUESTED;
4502 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505 forwardMostMove = backwardMostMove = currentMove = moveNum;
4508 /* Update the clocks */
4509 if (strchr(elapsed_time, '.')) {
4511 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4512 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4514 /* Time is in seconds */
4515 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4516 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4521 if (appData.zippyPlay && newGame &&
4522 gameMode != IcsObserving && gameMode != IcsIdle &&
4523 gameMode != IcsExamining)
4524 ZippyFirstBoard(moveNum, basetime, increment);
4527 /* Put the move on the move list, first converting
4528 to canonical algebraic form. */
4530 if (appData.debugMode) {
4531 if (appData.debugMode) { int f = forwardMostMove;
4532 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4533 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4534 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4536 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4537 fprintf(debugFP, "moveNum = %d\n", moveNum);
4538 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4539 setbuf(debugFP, NULL);
4541 if (moveNum <= backwardMostMove) {
4542 /* We don't know what the board looked like before
4544 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4545 strcat(parseList[moveNum - 1], " ");
4546 strcat(parseList[moveNum - 1], elapsed_time);
4547 moveList[moveNum - 1][0] = NULLCHAR;
4548 } else if (strcmp(move_str, "none") == 0) {
4549 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4550 /* Again, we don't know what the board looked like;
4551 this is really the start of the game. */
4552 parseList[moveNum - 1][0] = NULLCHAR;
4553 moveList[moveNum - 1][0] = NULLCHAR;
4554 backwardMostMove = moveNum;
4555 startedFromSetupPosition = TRUE;
4556 fromX = fromY = toX = toY = -1;
4558 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4559 // So we parse the long-algebraic move string in stead of the SAN move
4560 int valid; char buf[MSG_SIZ], *prom;
4562 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4563 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4564 // str looks something like "Q/a1-a2"; kill the slash
4566 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4567 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4568 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4569 strcat(buf, prom); // long move lacks promo specification!
4570 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4571 if(appData.debugMode)
4572 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4573 safeStrCpy(move_str, buf, MSG_SIZ);
4575 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4576 &fromX, &fromY, &toX, &toY, &promoChar)
4577 || ParseOneMove(buf, moveNum - 1, &moveType,
4578 &fromX, &fromY, &toX, &toY, &promoChar);
4579 // end of long SAN patch
4581 (void) CoordsToAlgebraic(boards[moveNum - 1],
4582 PosFlags(moveNum - 1),
4583 fromY, fromX, toY, toX, promoChar,
4584 parseList[moveNum-1]);
4585 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4591 if(gameInfo.variant != VariantShogi)
4592 strcat(parseList[moveNum - 1], "+");
4595 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4596 strcat(parseList[moveNum - 1], "#");
4599 strcat(parseList[moveNum - 1], " ");
4600 strcat(parseList[moveNum - 1], elapsed_time);
4601 /* currentMoveString is set as a side-effect of ParseOneMove */
4602 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4603 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4604 strcat(moveList[moveNum - 1], "\n");
4606 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4607 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4608 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4609 ChessSquare old, new = boards[moveNum][k][j];
4610 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4611 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4612 if(old == new) continue;
4613 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4614 else if(new == WhiteWazir || new == BlackWazir) {
4615 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4616 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4617 else boards[moveNum][k][j] = old; // preserve type of Gold
4618 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4619 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4622 /* Move from ICS was illegal!? Punt. */
4623 if (appData.debugMode) {
4624 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4625 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4627 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4628 strcat(parseList[moveNum - 1], " ");
4629 strcat(parseList[moveNum - 1], elapsed_time);
4630 moveList[moveNum - 1][0] = NULLCHAR;
4631 fromX = fromY = toX = toY = -1;
4634 if (appData.debugMode) {
4635 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4636 setbuf(debugFP, NULL);
4640 /* Send move to chess program (BEFORE animating it). */
4641 if (appData.zippyPlay && !newGame && newMove &&
4642 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4644 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4645 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4646 if (moveList[moveNum - 1][0] == NULLCHAR) {
4647 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4649 DisplayError(str, 0);
4651 if (first.sendTime) {
4652 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4654 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4655 if (firstMove && !bookHit) {
4657 if (first.useColors) {
4658 SendToProgram(gameMode == IcsPlayingWhite ?
4660 "black\ngo\n", &first);
4662 SendToProgram("go\n", &first);
4664 first.maybeThinking = TRUE;
4667 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4668 if (moveList[moveNum - 1][0] == NULLCHAR) {
4669 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4670 DisplayError(str, 0);
4672 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4673 SendMoveToProgram(moveNum - 1, &first);
4680 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4681 /* If move comes from a remote source, animate it. If it
4682 isn't remote, it will have already been animated. */
4683 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4684 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4686 if (!pausing && appData.highlightLastMove) {
4687 SetHighlights(fromX, fromY, toX, toY);
4691 /* Start the clocks */
4692 whiteFlag = blackFlag = FALSE;
4693 appData.clockMode = !(basetime == 0 && increment == 0);
4695 ics_clock_paused = TRUE;
4697 } else if (ticking == 1) {
4698 ics_clock_paused = FALSE;
4700 if (gameMode == IcsIdle ||
4701 relation == RELATION_OBSERVING_STATIC ||
4702 relation == RELATION_EXAMINING ||
4704 DisplayBothClocks();
4708 /* Display opponents and material strengths */
4709 if (gameInfo.variant != VariantBughouse &&
4710 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4711 if (tinyLayout || smallLayout) {
4712 if(gameInfo.variant == VariantNormal)
4713 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4714 gameInfo.white, white_stren, gameInfo.black, black_stren,
4715 basetime, increment);
4717 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4718 gameInfo.white, white_stren, gameInfo.black, black_stren,
4719 basetime, increment, (int) gameInfo.variant);
4721 if(gameInfo.variant == VariantNormal)
4722 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4723 gameInfo.white, white_stren, gameInfo.black, black_stren,
4724 basetime, increment);
4726 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4727 gameInfo.white, white_stren, gameInfo.black, black_stren,
4728 basetime, increment, VariantName(gameInfo.variant));
4731 if (appData.debugMode) {
4732 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4737 /* Display the board */
4738 if (!pausing && !appData.noGUI) {
4740 if (appData.premove)
4742 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4743 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4744 ClearPremoveHighlights();
4746 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4747 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4748 DrawPosition(j, boards[currentMove]);
4750 DisplayMove(moveNum - 1);
4751 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4752 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4753 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4754 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4758 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4760 if(bookHit) { // [HGM] book: simulate book reply
4761 static char bookMove[MSG_SIZ]; // a bit generous?
4763 programStats.nodes = programStats.depth = programStats.time =
4764 programStats.score = programStats.got_only_move = 0;
4765 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4767 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4768 strcat(bookMove, bookHit);
4769 HandleMachineMove(bookMove, &first);
4778 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4779 ics_getting_history = H_REQUESTED;
4780 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4786 AnalysisPeriodicEvent(force)
4789 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4790 && !force) || !appData.periodicUpdates)
4793 /* Send . command to Crafty to collect stats */
4794 SendToProgram(".\n", &first);
4796 /* Don't send another until we get a response (this makes
4797 us stop sending to old Crafty's which don't understand
4798 the "." command (sending illegal cmds resets node count & time,
4799 which looks bad)) */
4800 programStats.ok_to_send = 0;
4803 void ics_update_width(new_width)
4806 ics_printf("set width %d\n", new_width);
4810 SendMoveToProgram(moveNum, cps)
4812 ChessProgramState *cps;
4816 if (cps->useUsermove) {
4817 SendToProgram("usermove ", cps);
4821 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4822 int len = space - parseList[moveNum];
4823 memcpy(buf, parseList[moveNum], len);
4825 buf[len] = NULLCHAR;
4827 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4829 SendToProgram(buf, cps);
4831 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4832 AlphaRank(moveList[moveNum], 4);
4833 SendToProgram(moveList[moveNum], cps);
4834 AlphaRank(moveList[moveNum], 4); // and back
4836 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4837 * the engine. It would be nice to have a better way to identify castle
4839 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4840 && cps->useOOCastle) {
4841 int fromX = moveList[moveNum][0] - AAA;
4842 int fromY = moveList[moveNum][1] - ONE;
4843 int toX = moveList[moveNum][2] - AAA;
4844 int toY = moveList[moveNum][3] - ONE;
4845 if((boards[moveNum][fromY][fromX] == WhiteKing
4846 && boards[moveNum][toY][toX] == WhiteRook)
4847 || (boards[moveNum][fromY][fromX] == BlackKing
4848 && boards[moveNum][toY][toX] == BlackRook)) {
4849 if(toX > fromX) SendToProgram("O-O\n", cps);
4850 else SendToProgram("O-O-O\n", cps);
4852 else SendToProgram(moveList[moveNum], cps);
4854 else SendToProgram(moveList[moveNum], cps);
4855 /* End of additions by Tord */
4858 /* [HGM] setting up the opening has brought engine in force mode! */
4859 /* Send 'go' if we are in a mode where machine should play. */
4860 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4861 (gameMode == TwoMachinesPlay ||
4863 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4865 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4866 SendToProgram("go\n", cps);
4867 if (appData.debugMode) {
4868 fprintf(debugFP, "(extra)\n");
4871 setboardSpoiledMachineBlack = 0;
4875 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4877 int fromX, fromY, toX, toY;
4880 char user_move[MSG_SIZ];
4884 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4885 (int)moveType, fromX, fromY, toX, toY);
4886 DisplayError(user_move + strlen("say "), 0);
4888 case WhiteKingSideCastle:
4889 case BlackKingSideCastle:
4890 case WhiteQueenSideCastleWild:
4891 case BlackQueenSideCastleWild:
4893 case WhiteHSideCastleFR:
4894 case BlackHSideCastleFR:
4896 snprintf(user_move, MSG_SIZ, "o-o\n");
4898 case WhiteQueenSideCastle:
4899 case BlackQueenSideCastle:
4900 case WhiteKingSideCastleWild:
4901 case BlackKingSideCastleWild:
4903 case WhiteASideCastleFR:
4904 case BlackASideCastleFR:
4906 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4908 case WhiteNonPromotion:
4909 case BlackNonPromotion:
4910 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4912 case WhitePromotion:
4913 case BlackPromotion:
4914 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4915 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4916 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4917 PieceToChar(WhiteFerz));
4918 else if(gameInfo.variant == VariantGreat)
4919 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4920 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921 PieceToChar(WhiteMan));
4923 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4924 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4930 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4931 ToUpper(PieceToChar((ChessSquare) fromX)),
4932 AAA + toX, ONE + toY);
4934 case IllegalMove: /* could be a variant we don't quite understand */
4935 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4937 case WhiteCapturesEnPassant:
4938 case BlackCapturesEnPassant:
4939 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4940 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4943 SendToICS(user_move);
4944 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4945 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4950 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4951 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4952 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4953 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4954 DisplayError("You cannot do this while you are playing or observing", 0);
4957 if(gameMode != IcsExamining) { // is this ever not the case?
4958 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4960 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4961 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4962 } else { // on FICS we must first go to general examine mode
4963 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4965 if(gameInfo.variant != VariantNormal) {
4966 // try figure out wild number, as xboard names are not always valid on ICS
4967 for(i=1; i<=36; i++) {
4968 snprintf(buf, MSG_SIZ, "wild/%d", i);
4969 if(StringToVariant(buf) == gameInfo.variant) break;
4971 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4972 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4973 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4974 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4975 SendToICS(ics_prefix);
4977 if(startedFromSetupPosition || backwardMostMove != 0) {
4978 fen = PositionToFEN(backwardMostMove, NULL);
4979 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4980 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4982 } else { // FICS: everything has to set by separate bsetup commands
4983 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4984 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4986 if(!WhiteOnMove(backwardMostMove)) {
4987 SendToICS("bsetup tomove black\n");
4989 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4990 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4992 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4993 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4995 i = boards[backwardMostMove][EP_STATUS];
4996 if(i >= 0) { // set e.p.
4997 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5003 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5004 SendToICS("bsetup done\n"); // switch to normal examining.
5006 for(i = backwardMostMove; i<last; i++) {
5008 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5011 SendToICS(ics_prefix);
5012 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5016 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5021 if (rf == DROP_RANK) {
5022 sprintf(move, "%c@%c%c\n",
5023 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5025 if (promoChar == 'x' || promoChar == NULLCHAR) {
5026 sprintf(move, "%c%c%c%c\n",
5027 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5029 sprintf(move, "%c%c%c%c%c\n",
5030 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5036 ProcessICSInitScript(f)
5041 while (fgets(buf, MSG_SIZ, f)) {
5042 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5049 static int lastX, lastY, selectFlag, dragging;
5054 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5055 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5056 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5057 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5058 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5059 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5062 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5063 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5064 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5065 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5067 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5068 appData.testLegality && (promoSweep == king ||
5069 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5070 ChangeDragPiece(promoSweep);
5073 int PromoScroll(int x, int y)
5077 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5078 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5079 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5080 if(!step) return FALSE;
5081 lastX = x; lastY = y;
5082 if((promoSweep < BlackPawn) == flipView) step = -step;
5083 if(step > 0) selectFlag = 1;
5084 if(!selectFlag) Sweep(step);
5091 ChessSquare piece = boards[currentMove][toY][toX];
5094 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5095 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5096 if(!step) step = -1;
5097 } while(PieceToChar(pieceSweep) == '.');
5098 boards[currentMove][toY][toX] = pieceSweep;
5099 DrawPosition(FALSE, boards[currentMove]);
5100 boards[currentMove][toY][toX] = piece;
5102 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5104 AlphaRank(char *move, int n)
5106 // char *p = move, c; int x, y;
5108 if (appData.debugMode) {
5109 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5113 move[2]>='0' && move[2]<='9' &&
5114 move[3]>='a' && move[3]<='x' ) {
5116 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5117 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5119 if(move[0]>='0' && move[0]<='9' &&
5120 move[1]>='a' && move[1]<='x' &&
5121 move[2]>='0' && move[2]<='9' &&
5122 move[3]>='a' && move[3]<='x' ) {
5123 /* input move, Shogi -> normal */
5124 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5125 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5126 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5127 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5130 move[3]>='0' && move[3]<='9' &&
5131 move[2]>='a' && move[2]<='x' ) {
5133 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5134 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5137 move[0]>='a' && move[0]<='x' &&
5138 move[3]>='0' && move[3]<='9' &&
5139 move[2]>='a' && move[2]<='x' ) {
5140 /* output move, normal -> Shogi */
5141 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5142 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5143 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5144 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5145 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5147 if (appData.debugMode) {
5148 fprintf(debugFP, " out = '%s'\n", move);
5152 char yy_textstr[8000];
5154 /* Parser for moves from gnuchess, ICS, or user typein box */
5156 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5159 ChessMove *moveType;
5160 int *fromX, *fromY, *toX, *toY;
5163 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5165 switch (*moveType) {
5166 case WhitePromotion:
5167 case BlackPromotion:
5168 case WhiteNonPromotion:
5169 case BlackNonPromotion:
5171 case WhiteCapturesEnPassant:
5172 case BlackCapturesEnPassant:
5173 case WhiteKingSideCastle:
5174 case WhiteQueenSideCastle:
5175 case BlackKingSideCastle:
5176 case BlackQueenSideCastle:
5177 case WhiteKingSideCastleWild:
5178 case WhiteQueenSideCastleWild:
5179 case BlackKingSideCastleWild:
5180 case BlackQueenSideCastleWild:
5181 /* Code added by Tord: */
5182 case WhiteHSideCastleFR:
5183 case WhiteASideCastleFR:
5184 case BlackHSideCastleFR:
5185 case BlackASideCastleFR:
5186 /* End of code added by Tord */
5187 case IllegalMove: /* bug or odd chess variant */
5188 *fromX = currentMoveString[0] - AAA;
5189 *fromY = currentMoveString[1] - ONE;
5190 *toX = currentMoveString[2] - AAA;
5191 *toY = currentMoveString[3] - ONE;
5192 *promoChar = currentMoveString[4];
5193 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5194 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5195 if (appData.debugMode) {
5196 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5198 *fromX = *fromY = *toX = *toY = 0;
5201 if (appData.testLegality) {
5202 return (*moveType != IllegalMove);
5204 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5205 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5210 *fromX = *moveType == WhiteDrop ?
5211 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5212 (int) CharToPiece(ToLower(currentMoveString[0]));
5214 *toX = currentMoveString[2] - AAA;
5215 *toY = currentMoveString[3] - ONE;
5216 *promoChar = NULLCHAR;
5220 case ImpossibleMove:
5230 if (appData.debugMode) {
5231 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5234 *fromX = *fromY = *toX = *toY = 0;
5235 *promoChar = NULLCHAR;
5240 Boolean pushed = FALSE;
5243 ParsePV(char *pv, Boolean storeComments)
5244 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5245 int fromX, fromY, toX, toY; char promoChar;
5250 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5251 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5254 endPV = forwardMostMove;
5256 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5257 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5258 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5259 if(appData.debugMode){
5260 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);
5262 if(!valid && nr == 0 &&
5263 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5264 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5265 // Hande case where played move is different from leading PV move
5266 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5267 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5268 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5269 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5270 endPV += 2; // if position different, keep this
5271 moveList[endPV-1][0] = fromX + AAA;
5272 moveList[endPV-1][1] = fromY + ONE;
5273 moveList[endPV-1][2] = toX + AAA;
5274 moveList[endPV-1][3] = toY + ONE;
5275 parseList[endPV-1][0] = NULLCHAR;
5276 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5279 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5280 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5281 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5282 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5283 valid++; // allow comments in PV
5287 if(endPV+1 > framePtr) break; // no space, truncate
5290 CopyBoard(boards[endPV], boards[endPV-1]);
5291 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5292 moveList[endPV-1][0] = fromX + AAA;
5293 moveList[endPV-1][1] = fromY + ONE;
5294 moveList[endPV-1][2] = toX + AAA;
5295 moveList[endPV-1][3] = toY + ONE;
5296 moveList[endPV-1][4] = promoChar;
5297 moveList[endPV-1][5] = NULLCHAR;
5298 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5300 CoordsToAlgebraic(boards[endPV - 1],
5301 PosFlags(endPV - 1),
5302 fromY, fromX, toY, toX, promoChar,
5303 parseList[endPV - 1]);
5305 parseList[endPV-1][0] = NULLCHAR;
5307 currentMove = endPV;
5308 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5309 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5310 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5311 DrawPosition(TRUE, boards[currentMove]);
5315 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5320 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5321 lastX = x; lastY = y;
5322 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5324 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5325 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5327 do{ while(buf[index] && buf[index] != '\n') index++;
5328 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5330 ParsePV(buf+startPV, FALSE);
5331 *start = startPV; *end = index-1;
5336 LoadPV(int x, int y)
5337 { // called on right mouse click to load PV
5338 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5339 lastX = x; lastY = y;
5340 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5347 if(endPV < 0) return;
5349 currentMove = forwardMostMove;
5350 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5351 ClearPremoveHighlights();
5352 DrawPosition(TRUE, boards[currentMove]);
5356 MovePV(int x, int y, int h)
5357 { // step through PV based on mouse coordinates (called on mouse move)
5358 int margin = h>>3, step = 0;
5360 // we must somehow check if right button is still down (might be released off board!)
5361 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5362 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5363 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5365 lastX = x; lastY = y;
5367 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5368 if(endPV < 0) return;
5369 if(y < margin) step = 1; else
5370 if(y > h - margin) step = -1;
5371 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5372 currentMove += step;
5373 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5374 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5375 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5376 DrawPosition(FALSE, boards[currentMove]);
5380 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5381 // All positions will have equal probability, but the current method will not provide a unique
5382 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5388 int piecesLeft[(int)BlackPawn];
5389 int seed, nrOfShuffles;
5391 void GetPositionNumber()
5392 { // sets global variable seed
5395 seed = appData.defaultFrcPosition;
5396 if(seed < 0) { // randomize based on time for negative FRC position numbers
5397 for(i=0; i<50; i++) seed += random();
5398 seed = random() ^ random() >> 8 ^ random() << 8;
5399 if(seed<0) seed = -seed;
5403 int put(Board board, int pieceType, int rank, int n, int shade)
5404 // put the piece on the (n-1)-th empty squares of the given shade
5408 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5409 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5410 board[rank][i] = (ChessSquare) pieceType;
5411 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5413 piecesLeft[pieceType]--;
5421 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5422 // calculate where the next piece goes, (any empty square), and put it there
5426 i = seed % squaresLeft[shade];
5427 nrOfShuffles *= squaresLeft[shade];
5428 seed /= squaresLeft[shade];
5429 put(board, pieceType, rank, i, shade);
5432 void AddTwoPieces(Board board, int pieceType, int rank)
5433 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5435 int i, n=squaresLeft[ANY], j=n-1, k;
5437 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5438 i = seed % k; // pick one
5441 while(i >= j) i -= j--;
5442 j = n - 1 - j; i += j;
5443 put(board, pieceType, rank, j, ANY);
5444 put(board, pieceType, rank, i, ANY);
5447 void SetUpShuffle(Board board, int number)
5451 GetPositionNumber(); nrOfShuffles = 1;
5453 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5454 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5455 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5457 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5459 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5460 p = (int) board[0][i];
5461 if(p < (int) BlackPawn) piecesLeft[p] ++;
5462 board[0][i] = EmptySquare;
5465 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5466 // shuffles restricted to allow normal castling put KRR first
5467 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5468 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5469 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5470 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5471 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5472 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5473 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5474 put(board, WhiteRook, 0, 0, ANY);
5475 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5478 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5479 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5480 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5481 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5482 while(piecesLeft[p] >= 2) {
5483 AddOnePiece(board, p, 0, LITE);
5484 AddOnePiece(board, p, 0, DARK);
5486 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5489 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5490 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5491 // but we leave King and Rooks for last, to possibly obey FRC restriction
5492 if(p == (int)WhiteRook) continue;
5493 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5494 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5497 // now everything is placed, except perhaps King (Unicorn) and Rooks
5499 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5500 // Last King gets castling rights
5501 while(piecesLeft[(int)WhiteUnicorn]) {
5502 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5503 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5506 while(piecesLeft[(int)WhiteKing]) {
5507 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5508 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5513 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5514 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5517 // Only Rooks can be left; simply place them all
5518 while(piecesLeft[(int)WhiteRook]) {
5519 i = put(board, WhiteRook, 0, 0, ANY);
5520 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5523 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5525 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5528 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5529 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5532 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5535 int SetCharTable( char *table, const char * map )
5536 /* [HGM] moved here from winboard.c because of its general usefulness */
5537 /* Basically a safe strcpy that uses the last character as King */
5539 int result = FALSE; int NrPieces;
5541 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5542 && NrPieces >= 12 && !(NrPieces&1)) {
5543 int i; /* [HGM] Accept even length from 12 to 34 */
5545 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5546 for( i=0; i<NrPieces/2-1; i++ ) {
5548 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5550 table[(int) WhiteKing] = map[NrPieces/2-1];
5551 table[(int) BlackKing] = map[NrPieces-1];
5559 void Prelude(Board board)
5560 { // [HGM] superchess: random selection of exo-pieces
5561 int i, j, k; ChessSquare p;
5562 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5564 GetPositionNumber(); // use FRC position number
5566 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5567 SetCharTable(pieceToChar, appData.pieceToCharTable);
5568 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5569 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5572 j = seed%4; seed /= 4;
5573 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5574 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5575 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5576 j = seed%3 + (seed%3 >= j); seed /= 3;
5577 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5578 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5579 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5580 j = seed%3; seed /= 3;
5581 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5582 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5583 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5584 j = seed%2 + (seed%2 >= j); seed /= 2;
5585 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5586 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5587 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5588 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5589 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5590 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5591 put(board, exoPieces[0], 0, 0, ANY);
5592 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5596 InitPosition(redraw)
5599 ChessSquare (* pieces)[BOARD_FILES];
5600 int i, j, pawnRow, overrule,
5601 oldx = gameInfo.boardWidth,
5602 oldy = gameInfo.boardHeight,
5603 oldh = gameInfo.holdingsWidth;
5606 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5608 /* [AS] Initialize pv info list [HGM] and game status */
5610 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5611 pvInfoList[i].depth = 0;
5612 boards[i][EP_STATUS] = EP_NONE;
5613 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5616 initialRulePlies = 0; /* 50-move counter start */
5618 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5619 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5623 /* [HGM] logic here is completely changed. In stead of full positions */
5624 /* the initialized data only consist of the two backranks. The switch */
5625 /* selects which one we will use, which is than copied to the Board */
5626 /* initialPosition, which for the rest is initialized by Pawns and */
5627 /* empty squares. This initial position is then copied to boards[0], */
5628 /* possibly after shuffling, so that it remains available. */
5630 gameInfo.holdingsWidth = 0; /* default board sizes */
5631 gameInfo.boardWidth = 8;
5632 gameInfo.boardHeight = 8;
5633 gameInfo.holdingsSize = 0;
5634 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5635 for(i=0; i<BOARD_FILES-2; i++)
5636 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5637 initialPosition[EP_STATUS] = EP_NONE;
5638 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5639 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5640 SetCharTable(pieceNickName, appData.pieceNickNames);
5641 else SetCharTable(pieceNickName, "............");
5644 switch (gameInfo.variant) {
5645 case VariantFischeRandom:
5646 shuffleOpenings = TRUE;
5649 case VariantShatranj:
5650 pieces = ShatranjArray;
5651 nrCastlingRights = 0;
5652 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5655 pieces = makrukArray;
5656 nrCastlingRights = 0;
5657 startedFromSetupPosition = TRUE;
5658 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5660 case VariantTwoKings:
5661 pieces = twoKingsArray;
5663 case VariantCapaRandom:
5664 shuffleOpenings = TRUE;
5665 case VariantCapablanca:
5666 pieces = CapablancaArray;
5667 gameInfo.boardWidth = 10;
5668 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5671 pieces = GothicArray;
5672 gameInfo.boardWidth = 10;
5673 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5676 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5677 gameInfo.holdingsSize = 7;
5680 pieces = JanusArray;
5681 gameInfo.boardWidth = 10;
5682 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5683 nrCastlingRights = 6;
5684 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5685 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5686 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5687 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5688 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5689 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5692 pieces = FalconArray;
5693 gameInfo.boardWidth = 10;
5694 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5696 case VariantXiangqi:
5697 pieces = XiangqiArray;
5698 gameInfo.boardWidth = 9;
5699 gameInfo.boardHeight = 10;
5700 nrCastlingRights = 0;
5701 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5704 pieces = ShogiArray;
5705 gameInfo.boardWidth = 9;
5706 gameInfo.boardHeight = 9;
5707 gameInfo.holdingsSize = 7;
5708 nrCastlingRights = 0;
5709 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5711 case VariantCourier:
5712 pieces = CourierArray;
5713 gameInfo.boardWidth = 12;
5714 nrCastlingRights = 0;
5715 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5717 case VariantKnightmate:
5718 pieces = KnightmateArray;
5719 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5721 case VariantSpartan:
5722 pieces = SpartanArray;
5723 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5726 pieces = fairyArray;
5727 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5730 pieces = GreatArray;
5731 gameInfo.boardWidth = 10;
5732 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5733 gameInfo.holdingsSize = 8;
5737 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5738 gameInfo.holdingsSize = 8;
5739 startedFromSetupPosition = TRUE;
5741 case VariantCrazyhouse:
5742 case VariantBughouse:
5744 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5745 gameInfo.holdingsSize = 5;
5747 case VariantWildCastle:
5749 /* !!?shuffle with kings guaranteed to be on d or e file */
5750 shuffleOpenings = 1;
5752 case VariantNoCastle:
5754 nrCastlingRights = 0;
5755 /* !!?unconstrained back-rank shuffle */
5756 shuffleOpenings = 1;
5761 if(appData.NrFiles >= 0) {
5762 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5763 gameInfo.boardWidth = appData.NrFiles;
5765 if(appData.NrRanks >= 0) {
5766 gameInfo.boardHeight = appData.NrRanks;
5768 if(appData.holdingsSize >= 0) {
5769 i = appData.holdingsSize;
5770 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5771 gameInfo.holdingsSize = i;
5773 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5774 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5775 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5777 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5778 if(pawnRow < 1) pawnRow = 1;
5779 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5781 /* User pieceToChar list overrules defaults */
5782 if(appData.pieceToCharTable != NULL)
5783 SetCharTable(pieceToChar, appData.pieceToCharTable);
5785 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5787 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5788 s = (ChessSquare) 0; /* account holding counts in guard band */
5789 for( i=0; i<BOARD_HEIGHT; i++ )
5790 initialPosition[i][j] = s;
5792 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5793 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5794 initialPosition[pawnRow][j] = WhitePawn;
5795 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5796 if(gameInfo.variant == VariantXiangqi) {
5798 initialPosition[pawnRow][j] =
5799 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5800 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5801 initialPosition[2][j] = WhiteCannon;
5802 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5806 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5808 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5811 initialPosition[1][j] = WhiteBishop;
5812 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5814 initialPosition[1][j] = WhiteRook;
5815 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5818 if( nrCastlingRights == -1) {
5819 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5820 /* This sets default castling rights from none to normal corners */
5821 /* Variants with other castling rights must set them themselves above */
5822 nrCastlingRights = 6;
5824 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5825 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5826 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5827 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5828 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5829 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5832 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5833 if(gameInfo.variant == VariantGreat) { // promotion commoners
5834 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5835 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5836 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5837 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5839 if( gameInfo.variant == VariantSChess ) {
5840 initialPosition[1][0] = BlackMarshall;
5841 initialPosition[2][0] = BlackAngel;
5842 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5843 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5844 initialPosition[1][1] = initialPosition[2][1] =
5845 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5847 if (appData.debugMode) {
5848 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5850 if(shuffleOpenings) {
5851 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5852 startedFromSetupPosition = TRUE;
5854 if(startedFromPositionFile) {
5855 /* [HGM] loadPos: use PositionFile for every new game */
5856 CopyBoard(initialPosition, filePosition);
5857 for(i=0; i<nrCastlingRights; i++)
5858 initialRights[i] = filePosition[CASTLING][i];
5859 startedFromSetupPosition = TRUE;
5862 CopyBoard(boards[0], initialPosition);
5864 if(oldx != gameInfo.boardWidth ||
5865 oldy != gameInfo.boardHeight ||
5866 oldv != gameInfo.variant ||
5867 oldh != gameInfo.holdingsWidth
5869 InitDrawingSizes(-2 ,0);
5871 oldv = gameInfo.variant;
5873 DrawPosition(TRUE, boards[currentMove]);
5877 SendBoard(cps, moveNum)
5878 ChessProgramState *cps;
5881 char message[MSG_SIZ];
5883 if (cps->useSetboard) {
5884 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5885 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5886 SendToProgram(message, cps);
5892 /* Kludge to set black to move, avoiding the troublesome and now
5893 * deprecated "black" command.
5895 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5896 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5898 SendToProgram("edit\n", cps);
5899 SendToProgram("#\n", cps);
5900 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5901 bp = &boards[moveNum][i][BOARD_LEFT];
5902 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5903 if ((int) *bp < (int) BlackPawn) {
5904 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5906 if(message[0] == '+' || message[0] == '~') {
5907 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5908 PieceToChar((ChessSquare)(DEMOTED *bp)),
5911 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5912 message[1] = BOARD_RGHT - 1 - j + '1';
5913 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5915 SendToProgram(message, cps);
5920 SendToProgram("c\n", cps);
5921 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5922 bp = &boards[moveNum][i][BOARD_LEFT];
5923 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5924 if (((int) *bp != (int) EmptySquare)
5925 && ((int) *bp >= (int) BlackPawn)) {
5926 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5928 if(message[0] == '+' || message[0] == '~') {
5929 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5930 PieceToChar((ChessSquare)(DEMOTED *bp)),
5933 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5934 message[1] = BOARD_RGHT - 1 - j + '1';
5935 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5937 SendToProgram(message, cps);
5942 SendToProgram(".\n", cps);
5944 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5948 DefaultPromoChoice(int white)
5951 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5952 result = WhiteFerz; // no choice
5953 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5954 result= WhiteKing; // in Suicide Q is the last thing we want
5955 else if(gameInfo.variant == VariantSpartan)
5956 result = white ? WhiteQueen : WhiteAngel;
5957 else result = WhiteQueen;
5958 if(!white) result = WHITE_TO_BLACK result;
5962 static int autoQueen; // [HGM] oneclick
5965 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5967 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5968 /* [HGM] add Shogi promotions */
5969 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5974 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5975 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5977 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5978 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5981 piece = boards[currentMove][fromY][fromX];
5982 if(gameInfo.variant == VariantShogi) {
5983 promotionZoneSize = BOARD_HEIGHT/3;
5984 highestPromotingPiece = (int)WhiteFerz;
5985 } else if(gameInfo.variant == VariantMakruk) {
5986 promotionZoneSize = 3;
5989 // Treat Lance as Pawn when it is not representing Amazon
5990 if(gameInfo.variant != VariantSuper) {
5991 if(piece == WhiteLance) piece = WhitePawn; else
5992 if(piece == BlackLance) piece = BlackPawn;
5995 // next weed out all moves that do not touch the promotion zone at all
5996 if((int)piece >= BlackPawn) {
5997 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5999 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6001 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6002 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6005 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6007 // weed out mandatory Shogi promotions
6008 if(gameInfo.variant == VariantShogi) {
6009 if(piece >= BlackPawn) {
6010 if(toY == 0 && piece == BlackPawn ||
6011 toY == 0 && piece == BlackQueen ||
6012 toY <= 1 && piece == BlackKnight) {
6017 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6018 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6019 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6026 // weed out obviously illegal Pawn moves
6027 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6028 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6029 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6030 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6031 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6032 // note we are not allowed to test for valid (non-)capture, due to premove
6035 // we either have a choice what to promote to, or (in Shogi) whether to promote
6036 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6037 *promoChoice = PieceToChar(BlackFerz); // no choice
6040 // no sense asking what we must promote to if it is going to explode...
6041 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6042 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6045 // give caller the default choice even if we will not make it
6046 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6047 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6048 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6049 && gameInfo.variant != VariantShogi
6050 && gameInfo.variant != VariantSuper) return FALSE;
6051 if(autoQueen) return FALSE; // predetermined
6053 // suppress promotion popup on illegal moves that are not premoves
6054 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6055 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6056 if(appData.testLegality && !premove) {
6057 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6058 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6059 if(moveType != WhitePromotion && moveType != BlackPromotion)
6067 InPalace(row, column)
6069 { /* [HGM] for Xiangqi */
6070 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6071 column < (BOARD_WIDTH + 4)/2 &&
6072 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6077 PieceForSquare (x, y)
6081 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6084 return boards[currentMove][y][x];
6088 OKToStartUserMove(x, y)
6091 ChessSquare from_piece;
6094 if (matchMode) return FALSE;
6095 if (gameMode == EditPosition) return TRUE;
6097 if (x >= 0 && y >= 0)
6098 from_piece = boards[currentMove][y][x];
6100 from_piece = EmptySquare;
6102 if (from_piece == EmptySquare) return FALSE;
6104 white_piece = (int)from_piece >= (int)WhitePawn &&
6105 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6108 case PlayFromGameFile:
6110 case TwoMachinesPlay:
6118 case MachinePlaysWhite:
6119 case IcsPlayingBlack:
6120 if (appData.zippyPlay) return FALSE;
6122 DisplayMoveError(_("You are playing Black"));
6127 case MachinePlaysBlack:
6128 case IcsPlayingWhite:
6129 if (appData.zippyPlay) return FALSE;
6131 DisplayMoveError(_("You are playing White"));
6137 if (!white_piece && WhiteOnMove(currentMove)) {
6138 DisplayMoveError(_("It is White's turn"));
6141 if (white_piece && !WhiteOnMove(currentMove)) {
6142 DisplayMoveError(_("It is Black's turn"));
6145 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6146 /* Editing correspondence game history */
6147 /* Could disallow this or prompt for confirmation */
6152 case BeginningOfGame:
6153 if (appData.icsActive) return FALSE;
6154 if (!appData.noChessProgram) {
6156 DisplayMoveError(_("You are playing White"));
6163 if (!white_piece && WhiteOnMove(currentMove)) {
6164 DisplayMoveError(_("It is White's turn"));
6167 if (white_piece && !WhiteOnMove(currentMove)) {
6168 DisplayMoveError(_("It is Black's turn"));
6177 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6178 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6179 && gameMode != AnalyzeFile && gameMode != Training) {
6180 DisplayMoveError(_("Displayed position is not current"));
6187 OnlyMove(int *x, int *y, Boolean captures) {
6188 DisambiguateClosure cl;
6189 if (appData.zippyPlay) return FALSE;
6191 case MachinePlaysBlack:
6192 case IcsPlayingWhite:
6193 case BeginningOfGame:
6194 if(!WhiteOnMove(currentMove)) return FALSE;
6196 case MachinePlaysWhite:
6197 case IcsPlayingBlack:
6198 if(WhiteOnMove(currentMove)) return FALSE;
6205 cl.pieceIn = EmptySquare;
6210 cl.promoCharIn = NULLCHAR;
6211 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6212 if( cl.kind == NormalMove ||
6213 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6214 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6215 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6222 if(cl.kind != ImpossibleMove) return FALSE;
6223 cl.pieceIn = EmptySquare;
6228 cl.promoCharIn = NULLCHAR;
6229 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6230 if( cl.kind == NormalMove ||
6231 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6232 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6233 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6238 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6244 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6245 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6246 int lastLoadGameUseList = FALSE;
6247 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6248 ChessMove lastLoadGameStart = EndOfFile;
6251 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6252 int fromX, fromY, toX, toY;
6256 ChessSquare pdown, pup;
6258 /* Check if the user is playing in turn. This is complicated because we
6259 let the user "pick up" a piece before it is his turn. So the piece he
6260 tried to pick up may have been captured by the time he puts it down!
6261 Therefore we use the color the user is supposed to be playing in this
6262 test, not the color of the piece that is currently on the starting
6263 square---except in EditGame mode, where the user is playing both
6264 sides; fortunately there the capture race can't happen. (It can
6265 now happen in IcsExamining mode, but that's just too bad. The user
6266 will get a somewhat confusing message in that case.)
6270 case PlayFromGameFile:
6272 case TwoMachinesPlay:
6276 /* We switched into a game mode where moves are not accepted,
6277 perhaps while the mouse button was down. */
6280 case MachinePlaysWhite:
6281 /* User is moving for Black */
6282 if (WhiteOnMove(currentMove)) {
6283 DisplayMoveError(_("It is White's turn"));
6288 case MachinePlaysBlack:
6289 /* User is moving for White */
6290 if (!WhiteOnMove(currentMove)) {
6291 DisplayMoveError(_("It is Black's turn"));
6298 case BeginningOfGame:
6301 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6302 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6303 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6304 /* User is moving for Black */
6305 if (WhiteOnMove(currentMove)) {
6306 DisplayMoveError(_("It is White's turn"));
6310 /* User is moving for White */
6311 if (!WhiteOnMove(currentMove)) {
6312 DisplayMoveError(_("It is Black's turn"));
6318 case IcsPlayingBlack:
6319 /* User is moving for Black */
6320 if (WhiteOnMove(currentMove)) {
6321 if (!appData.premove) {
6322 DisplayMoveError(_("It is White's turn"));
6323 } else if (toX >= 0 && toY >= 0) {
6326 premoveFromX = fromX;
6327 premoveFromY = fromY;
6328 premovePromoChar = promoChar;
6330 if (appData.debugMode)
6331 fprintf(debugFP, "Got premove: fromX %d,"
6332 "fromY %d, toX %d, toY %d\n",
6333 fromX, fromY, toX, toY);
6339 case IcsPlayingWhite:
6340 /* User is moving for White */
6341 if (!WhiteOnMove(currentMove)) {
6342 if (!appData.premove) {
6343 DisplayMoveError(_("It is Black's turn"));
6344 } else if (toX >= 0 && toY >= 0) {
6347 premoveFromX = fromX;
6348 premoveFromY = fromY;
6349 premovePromoChar = promoChar;
6351 if (appData.debugMode)
6352 fprintf(debugFP, "Got premove: fromX %d,"
6353 "fromY %d, toX %d, toY %d\n",
6354 fromX, fromY, toX, toY);
6364 /* EditPosition, empty square, or different color piece;
6365 click-click move is possible */
6366 if (toX == -2 || toY == -2) {
6367 boards[0][fromY][fromX] = EmptySquare;
6368 DrawPosition(FALSE, boards[currentMove]);
6370 } else if (toX >= 0 && toY >= 0) {
6371 boards[0][toY][toX] = boards[0][fromY][fromX];
6372 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6373 if(boards[0][fromY][0] != EmptySquare) {
6374 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6375 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6378 if(fromX == BOARD_RGHT+1) {
6379 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6380 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6381 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6384 boards[0][fromY][fromX] = EmptySquare;
6385 DrawPosition(FALSE, boards[currentMove]);
6391 if(toX < 0 || toY < 0) return;
6392 pdown = boards[currentMove][fromY][fromX];
6393 pup = boards[currentMove][toY][toX];
6395 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6396 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6397 if( pup != EmptySquare ) return;
6398 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6399 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6400 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6401 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6402 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6403 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6404 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6408 /* [HGM] always test for legality, to get promotion info */
6409 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6410 fromY, fromX, toY, toX, promoChar);
6411 /* [HGM] but possibly ignore an IllegalMove result */
6412 if (appData.testLegality) {
6413 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6414 DisplayMoveError(_("Illegal move"));
6419 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6422 /* Common tail of UserMoveEvent and DropMenuEvent */
6424 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6426 int fromX, fromY, toX, toY;
6427 /*char*/int promoChar;
6431 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6432 // [HGM] superchess: suppress promotions to non-available piece
6433 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6434 if(WhiteOnMove(currentMove)) {
6435 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6437 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6441 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6442 move type in caller when we know the move is a legal promotion */
6443 if(moveType == NormalMove && promoChar)
6444 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6446 /* [HGM] <popupFix> The following if has been moved here from
6447 UserMoveEvent(). Because it seemed to belong here (why not allow
6448 piece drops in training games?), and because it can only be
6449 performed after it is known to what we promote. */
6450 if (gameMode == Training) {
6451 /* compare the move played on the board to the next move in the
6452 * game. If they match, display the move and the opponent's response.
6453 * If they don't match, display an error message.
6457 CopyBoard(testBoard, boards[currentMove]);
6458 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6460 if (CompareBoards(testBoard, boards[currentMove+1])) {
6461 ForwardInner(currentMove+1);
6463 /* Autoplay the opponent's response.
6464 * if appData.animate was TRUE when Training mode was entered,
6465 * the response will be animated.
6467 saveAnimate = appData.animate;
6468 appData.animate = animateTraining;
6469 ForwardInner(currentMove+1);
6470 appData.animate = saveAnimate;
6472 /* check for the end of the game */
6473 if (currentMove >= forwardMostMove) {
6474 gameMode = PlayFromGameFile;
6476 SetTrainingModeOff();
6477 DisplayInformation(_("End of game"));
6480 DisplayError(_("Incorrect move"), 0);
6485 /* Ok, now we know that the move is good, so we can kill
6486 the previous line in Analysis Mode */
6487 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6488 && currentMove < forwardMostMove) {
6489 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6490 else forwardMostMove = currentMove;
6493 /* If we need the chess program but it's dead, restart it */
6494 ResurrectChessProgram();
6496 /* A user move restarts a paused game*/
6500 thinkOutput[0] = NULLCHAR;
6502 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6504 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6505 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6509 if (gameMode == BeginningOfGame) {
6510 if (appData.noChessProgram) {
6511 gameMode = EditGame;
6515 gameMode = MachinePlaysBlack;
6518 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6520 if (first.sendName) {
6521 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6522 SendToProgram(buf, &first);
6529 /* Relay move to ICS or chess engine */
6530 if (appData.icsActive) {
6531 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6532 gameMode == IcsExamining) {
6533 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6534 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6536 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6538 // also send plain move, in case ICS does not understand atomic claims
6539 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6543 if (first.sendTime && (gameMode == BeginningOfGame ||
6544 gameMode == MachinePlaysWhite ||
6545 gameMode == MachinePlaysBlack)) {
6546 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6548 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6549 // [HGM] book: if program might be playing, let it use book
6550 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6551 first.maybeThinking = TRUE;
6552 } else SendMoveToProgram(forwardMostMove-1, &first);
6553 if (currentMove == cmailOldMove + 1) {
6554 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6558 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6562 if(appData.testLegality)
6563 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6569 if (WhiteOnMove(currentMove)) {
6570 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6572 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6576 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6581 case MachinePlaysBlack:
6582 case MachinePlaysWhite:
6583 /* disable certain menu options while machine is thinking */
6584 SetMachineThinkingEnables();
6591 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6592 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6594 if(bookHit) { // [HGM] book: simulate book reply
6595 static char bookMove[MSG_SIZ]; // a bit generous?
6597 programStats.nodes = programStats.depth = programStats.time =
6598 programStats.score = programStats.got_only_move = 0;
6599 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6601 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6602 strcat(bookMove, bookHit);
6603 HandleMachineMove(bookMove, &first);
6609 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6616 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6617 Markers *m = (Markers *) closure;
6618 if(rf == fromY && ff == fromX)
6619 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6620 || kind == WhiteCapturesEnPassant
6621 || kind == BlackCapturesEnPassant);
6622 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6626 MarkTargetSquares(int clear)
6629 if(!appData.markers || !appData.highlightDragging ||
6630 !appData.testLegality || gameMode == EditPosition) return;
6632 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6635 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6636 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6637 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6639 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6642 DrawPosition(TRUE, NULL);
6646 Explode(Board board, int fromX, int fromY, int toX, int toY)
6648 if(gameInfo.variant == VariantAtomic &&
6649 (board[toY][toX] != EmptySquare || // capture?
6650 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6651 board[fromY][fromX] == BlackPawn )
6653 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6659 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6661 int CanPromote(ChessSquare piece, int y)
6663 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6664 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6665 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6666 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6667 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6668 gameInfo.variant == VariantMakruk) return FALSE;
6669 return (piece == BlackPawn && y == 1 ||
6670 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6671 piece == BlackLance && y == 1 ||
6672 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6675 void LeftClick(ClickType clickType, int xPix, int yPix)
6678 Boolean saveAnimate;
6679 static int second = 0, promotionChoice = 0, clearFlag = 0;
6680 char promoChoice = NULLCHAR;
6683 if(appData.seekGraph && appData.icsActive && loggedOn &&
6684 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6685 SeekGraphClick(clickType, xPix, yPix, 0);
6689 if (clickType == Press) ErrorPopDown();
6690 MarkTargetSquares(1);
6692 x = EventToSquare(xPix, BOARD_WIDTH);
6693 y = EventToSquare(yPix, BOARD_HEIGHT);
6694 if (!flipView && y >= 0) {
6695 y = BOARD_HEIGHT - 1 - y;
6697 if (flipView && x >= 0) {
6698 x = BOARD_WIDTH - 1 - x;
6701 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6702 defaultPromoChoice = promoSweep;
6703 promoSweep = EmptySquare; // terminate sweep
6704 promoDefaultAltered = TRUE;
6705 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6708 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6709 if(clickType == Release) return; // ignore upclick of click-click destination
6710 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6711 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6712 if(gameInfo.holdingsWidth &&
6713 (WhiteOnMove(currentMove)
6714 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6715 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6716 // click in right holdings, for determining promotion piece
6717 ChessSquare p = boards[currentMove][y][x];
6718 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6719 if(p != EmptySquare) {
6720 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6725 DrawPosition(FALSE, boards[currentMove]);
6729 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6730 if(clickType == Press
6731 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6732 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6733 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6736 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6737 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6739 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6740 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6741 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6742 defaultPromoChoice = DefaultPromoChoice(side);
6745 autoQueen = appData.alwaysPromoteToQueen;
6749 gatingPiece = EmptySquare;
6750 if (clickType != Press) {
6751 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6752 DragPieceEnd(xPix, yPix); dragging = 0;
6753 DrawPosition(FALSE, NULL);
6757 fromX = x; fromY = y;
6758 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6759 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6760 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6762 if (OKToStartUserMove(fromX, fromY)) {
6764 MarkTargetSquares(0);
6765 DragPieceBegin(xPix, yPix); dragging = 1;
6766 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6767 promoSweep = defaultPromoChoice;
6768 selectFlag = 0; lastX = xPix; lastY = yPix;
6769 Sweep(0); // Pawn that is going to promote: preview promotion piece
6770 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6772 if (appData.highlightDragging) {
6773 SetHighlights(fromX, fromY, -1, -1);
6775 } else fromX = fromY = -1;
6781 if (clickType == Press && gameMode != EditPosition) {
6786 // ignore off-board to clicks
6787 if(y < 0 || x < 0) return;
6789 /* Check if clicking again on the same color piece */
6790 fromP = boards[currentMove][fromY][fromX];
6791 toP = boards[currentMove][y][x];
6792 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6793 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6794 WhitePawn <= toP && toP <= WhiteKing &&
6795 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6796 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6797 (BlackPawn <= fromP && fromP <= BlackKing &&
6798 BlackPawn <= toP && toP <= BlackKing &&
6799 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6800 !(fromP == BlackKing && toP == BlackRook && frc))) {
6801 /* Clicked again on same color piece -- changed his mind */
6802 second = (x == fromX && y == fromY);
6803 promoDefaultAltered = FALSE;
6804 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6805 if (appData.highlightDragging) {
6806 SetHighlights(x, y, -1, -1);
6810 if (OKToStartUserMove(x, y)) {
6811 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6812 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6813 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6814 gatingPiece = boards[currentMove][fromY][fromX];
6815 else gatingPiece = EmptySquare;
6817 fromY = y; dragging = 1;
6818 MarkTargetSquares(0);
6819 DragPieceBegin(xPix, yPix);
6820 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6821 promoSweep = defaultPromoChoice;
6822 selectFlag = 0; lastX = xPix; lastY = yPix;
6823 Sweep(0); // Pawn that is going to promote: preview promotion piece
6827 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6830 // ignore clicks on holdings
6831 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6834 if (clickType == Release && x == fromX && y == fromY) {
6835 DragPieceEnd(xPix, yPix); dragging = 0;
6837 // a deferred attempt to click-click move an empty square on top of a piece
6838 boards[currentMove][y][x] = EmptySquare;
6840 DrawPosition(FALSE, boards[currentMove]);
6841 fromX = fromY = -1; clearFlag = 0;
6844 if (appData.animateDragging) {
6845 /* Undo animation damage if any */
6846 DrawPosition(FALSE, NULL);
6849 /* Second up/down in same square; just abort move */
6852 gatingPiece = EmptySquare;
6855 ClearPremoveHighlights();
6857 /* First upclick in same square; start click-click mode */
6858 SetHighlights(x, y, -1, -1);
6865 /* we now have a different from- and (possibly off-board) to-square */
6866 /* Completed move */
6869 saveAnimate = appData.animate;
6870 if (clickType == Press) {
6871 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6872 // must be Edit Position mode with empty-square selected
6873 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6874 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6877 /* Finish clickclick move */
6878 if (appData.animate || appData.highlightLastMove) {
6879 SetHighlights(fromX, fromY, toX, toY);
6884 /* Finish drag move */
6885 if (appData.highlightLastMove) {
6886 SetHighlights(fromX, fromY, toX, toY);
6890 DragPieceEnd(xPix, yPix); dragging = 0;
6891 /* Don't animate move and drag both */
6892 appData.animate = FALSE;
6895 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6896 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6897 ChessSquare piece = boards[currentMove][fromY][fromX];
6898 if(gameMode == EditPosition && piece != EmptySquare &&
6899 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6902 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6903 n = PieceToNumber(piece - (int)BlackPawn);
6904 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6905 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6906 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6908 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6909 n = PieceToNumber(piece);
6910 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6911 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6912 boards[currentMove][n][BOARD_WIDTH-2]++;
6914 boards[currentMove][fromY][fromX] = EmptySquare;
6918 DrawPosition(TRUE, boards[currentMove]);
6922 // off-board moves should not be highlighted
6923 if(x < 0 || y < 0) ClearHighlights();
6925 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6927 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6928 SetHighlights(fromX, fromY, toX, toY);
6929 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6930 // [HGM] super: promotion to captured piece selected from holdings
6931 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6932 promotionChoice = TRUE;
6933 // kludge follows to temporarily execute move on display, without promoting yet
6934 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6935 boards[currentMove][toY][toX] = p;
6936 DrawPosition(FALSE, boards[currentMove]);
6937 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6938 boards[currentMove][toY][toX] = q;
6939 DisplayMessage("Click in holdings to choose piece", "");
6944 int oldMove = currentMove;
6945 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6946 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6947 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6948 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6949 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6950 DrawPosition(TRUE, boards[currentMove]);
6953 appData.animate = saveAnimate;
6954 if (appData.animate || appData.animateDragging) {
6955 /* Undo animation damage if needed */
6956 DrawPosition(FALSE, NULL);
6960 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6961 { // front-end-free part taken out of PieceMenuPopup
6962 int whichMenu; int xSqr, ySqr;
6964 if(seekGraphUp) { // [HGM] seekgraph
6965 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6966 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6970 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6971 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6972 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6973 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6974 if(action == Press) {
6975 originalFlip = flipView;
6976 flipView = !flipView; // temporarily flip board to see game from partners perspective
6977 DrawPosition(TRUE, partnerBoard);
6978 DisplayMessage(partnerStatus, "");
6980 } else if(action == Release) {
6981 flipView = originalFlip;
6982 DrawPosition(TRUE, boards[currentMove]);
6988 xSqr = EventToSquare(x, BOARD_WIDTH);
6989 ySqr = EventToSquare(y, BOARD_HEIGHT);
6990 if (action == Release) {
6991 if(pieceSweep != EmptySquare) {
6992 EditPositionMenuEvent(pieceSweep, toX, toY);
6993 pieceSweep = EmptySquare;
6994 } else UnLoadPV(); // [HGM] pv
6996 if (action != Press) return -2; // return code to be ignored
6999 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
7001 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
7002 if (xSqr < 0 || ySqr < 0) return -1;
7003 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7004 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7005 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7006 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7010 if(!appData.icsEngineAnalyze) return -1;
7011 case IcsPlayingWhite:
7012 case IcsPlayingBlack:
7013 if(!appData.zippyPlay) goto noZip;
7016 case MachinePlaysWhite:
7017 case MachinePlaysBlack:
7018 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7019 if (!appData.dropMenu) {
7021 return 2; // flag front-end to grab mouse events
7023 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7024 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7027 if (xSqr < 0 || ySqr < 0) return -1;
7028 if (!appData.dropMenu || appData.testLegality &&
7029 gameInfo.variant != VariantBughouse &&
7030 gameInfo.variant != VariantCrazyhouse) return -1;
7031 whichMenu = 1; // drop menu
7037 if (((*fromX = xSqr) < 0) ||
7038 ((*fromY = ySqr) < 0)) {
7039 *fromX = *fromY = -1;
7043 *fromX = BOARD_WIDTH - 1 - *fromX;
7045 *fromY = BOARD_HEIGHT - 1 - *fromY;
7050 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7052 // char * hint = lastHint;
7053 FrontEndProgramStats stats;
7055 stats.which = cps == &first ? 0 : 1;
7056 stats.depth = cpstats->depth;
7057 stats.nodes = cpstats->nodes;
7058 stats.score = cpstats->score;
7059 stats.time = cpstats->time;
7060 stats.pv = cpstats->movelist;
7061 stats.hint = lastHint;
7062 stats.an_move_index = 0;
7063 stats.an_move_count = 0;
7065 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7066 stats.hint = cpstats->move_name;
7067 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7068 stats.an_move_count = cpstats->nr_moves;
7071 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
7073 SetProgramStats( &stats );
7076 #define MAXPLAYERS 500
7079 TourneyStandings(int display)
7081 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7082 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7083 char result, *p, *names[MAXPLAYERS];
7085 names[0] = p = strdup(appData.participants);
7086 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7088 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7090 while(result = appData.results[nr]) {
7091 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7092 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7093 wScore = bScore = 0;
7095 case '+': wScore = 2; break;
7096 case '-': bScore = 2; break;
7097 case '=': wScore = bScore = 1; break;
7099 case '*': return strdup("busy"); // tourney not finished
7107 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7108 for(w=0; w<nPlayers; w++) {
7110 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7111 ranking[w] = b; points[w] = bScore; score[b] = -2;
7113 p = malloc(nPlayers*34+1);
7114 for(w=0; w<nPlayers && w<display; w++)
7115 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7121 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7122 { // count all piece types
7124 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7125 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7126 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7129 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7130 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7131 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7132 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7133 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7134 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7139 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7141 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7142 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7144 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7145 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7146 if(myPawns == 2 && nMine == 3) // KPP
7147 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7148 if(myPawns == 1 && nMine == 2) // KP
7149 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7150 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7151 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7152 if(myPawns) return FALSE;
7153 if(pCnt[WhiteRook+side])
7154 return pCnt[BlackRook-side] ||
7155 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7156 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7157 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7158 if(pCnt[WhiteCannon+side]) {
7159 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7160 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7162 if(pCnt[WhiteKnight+side])
7163 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7168 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7170 VariantClass v = gameInfo.variant;
7172 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7173 if(v == VariantShatranj) return TRUE; // always winnable through baring
7174 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7175 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7177 if(v == VariantXiangqi) {
7178 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7180 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7181 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7182 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7183 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7184 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7185 if(stale) // we have at least one last-rank P plus perhaps C
7186 return majors // KPKX
7187 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7189 return pCnt[WhiteFerz+side] // KCAK
7190 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7191 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7192 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7194 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7195 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7197 if(nMine == 1) return FALSE; // bare King
7198 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
7199 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7200 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7201 // by now we have King + 1 piece (or multiple Bishops on the same color)
7202 if(pCnt[WhiteKnight+side])
7203 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7204 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7205 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7207 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7208 if(pCnt[WhiteAlfil+side])
7209 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7210 if(pCnt[WhiteWazir+side])
7211 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7218 Adjudicate(ChessProgramState *cps)
7219 { // [HGM] some adjudications useful with buggy engines
7220 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7221 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7222 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7223 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7224 int k, count = 0; static int bare = 1;
7225 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7226 Boolean canAdjudicate = !appData.icsActive;
7228 // most tests only when we understand the game, i.e. legality-checking on
7229 if( appData.testLegality )
7230 { /* [HGM] Some more adjudications for obstinate engines */
7231 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7232 static int moveCount = 6;
7234 char *reason = NULL;
7236 /* Count what is on board. */
7237 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7239 /* Some material-based adjudications that have to be made before stalemate test */
7240 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7241 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7242 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7243 if(canAdjudicate && appData.checkMates) {
7245 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7246 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7247 "Xboard adjudication: King destroyed", GE_XBOARD );
7252 /* Bare King in Shatranj (loses) or Losers (wins) */
7253 if( nrW == 1 || nrB == 1) {
7254 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7255 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7256 if(canAdjudicate && appData.checkMates) {
7258 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7259 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7260 "Xboard adjudication: Bare king", GE_XBOARD );
7264 if( gameInfo.variant == VariantShatranj && --bare < 0)
7266 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7267 if(canAdjudicate && appData.checkMates) {
7268 /* but only adjudicate if adjudication enabled */
7270 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7271 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7272 "Xboard adjudication: Bare king", GE_XBOARD );
7279 // don't wait for engine to announce game end if we can judge ourselves
7280 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7282 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7283 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7284 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7285 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7288 reason = "Xboard adjudication: 3rd check";
7289 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7299 reason = "Xboard adjudication: Stalemate";
7300 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7301 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7302 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7303 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7304 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7305 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7306 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7307 EP_CHECKMATE : EP_WINS);
7308 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7309 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7313 reason = "Xboard adjudication: Checkmate";
7314 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7318 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7320 result = GameIsDrawn; break;
7322 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7324 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7328 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7330 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7331 GameEnds( result, reason, GE_XBOARD );
7335 /* Next absolutely insufficient mating material. */
7336 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7337 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7338 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7340 /* always flag draws, for judging claims */
7341 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7343 if(canAdjudicate && appData.materialDraws) {
7344 /* but only adjudicate them if adjudication enabled */
7345 if(engineOpponent) {
7346 SendToProgram("force\n", engineOpponent); // suppress reply
7347 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7349 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7354 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7355 if(gameInfo.variant == VariantXiangqi ?
7356 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7358 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7359 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7360 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7361 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7363 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7364 { /* if the first 3 moves do not show a tactical win, declare draw */
7365 if(engineOpponent) {
7366 SendToProgram("force\n", engineOpponent); // suppress reply
7367 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7369 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7372 } else moveCount = 6;
7374 if (appData.debugMode) { int i;
7375 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7376 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7377 appData.drawRepeats);
7378 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7379 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7383 // Repetition draws and 50-move rule can be applied independently of legality testing
7385 /* Check for rep-draws */
7387 for(k = forwardMostMove-2;
7388 k>=backwardMostMove && k>=forwardMostMove-100 &&
7389 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7390 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7393 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7394 /* compare castling rights */
7395 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7396 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7397 rights++; /* King lost rights, while rook still had them */
7398 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7399 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7400 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7401 rights++; /* but at least one rook lost them */
7403 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7404 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7406 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7407 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7408 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7411 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7412 && appData.drawRepeats > 1) {
7413 /* adjudicate after user-specified nr of repeats */
7414 int result = GameIsDrawn;
7415 char *details = "XBoard adjudication: repetition draw";
7416 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7417 // [HGM] xiangqi: check for forbidden perpetuals
7418 int m, ourPerpetual = 1, hisPerpetual = 1;
7419 for(m=forwardMostMove; m>k; m-=2) {
7420 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7421 ourPerpetual = 0; // the current mover did not always check
7422 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7423 hisPerpetual = 0; // the opponent did not always check
7425 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7426 ourPerpetual, hisPerpetual);
7427 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7428 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7429 details = "Xboard adjudication: perpetual checking";
7431 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7432 break; // (or we would have caught him before). Abort repetition-checking loop.
7434 // Now check for perpetual chases
7435 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7436 hisPerpetual = PerpetualChase(k, forwardMostMove);
7437 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7438 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7439 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7440 details = "Xboard adjudication: perpetual chasing";
7442 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7443 break; // Abort repetition-checking loop.
7445 // if neither of us is checking or chasing all the time, or both are, it is draw
7447 if(engineOpponent) {
7448 SendToProgram("force\n", engineOpponent); // suppress reply
7449 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7451 GameEnds( result, details, GE_XBOARD );
7454 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7455 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7459 /* Now we test for 50-move draws. Determine ply count */
7460 count = forwardMostMove;
7461 /* look for last irreversble move */
7462 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7464 /* if we hit starting position, add initial plies */
7465 if( count == backwardMostMove )
7466 count -= initialRulePlies;
7467 count = forwardMostMove - count;
7468 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7469 // adjust reversible move counter for checks in Xiangqi
7470 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7471 if(i < backwardMostMove) i = backwardMostMove;
7472 while(i <= forwardMostMove) {
7473 lastCheck = inCheck; // check evasion does not count
7474 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7475 if(inCheck || lastCheck) count--; // check does not count
7480 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7481 /* this is used to judge if draw claims are legal */
7482 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7483 if(engineOpponent) {
7484 SendToProgram("force\n", engineOpponent); // suppress reply
7485 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7487 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7491 /* if draw offer is pending, treat it as a draw claim
7492 * when draw condition present, to allow engines a way to
7493 * claim draws before making their move to avoid a race
7494 * condition occurring after their move
7496 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7498 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7499 p = "Draw claim: 50-move rule";
7500 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7501 p = "Draw claim: 3-fold repetition";
7502 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7503 p = "Draw claim: insufficient mating material";
7504 if( p != NULL && canAdjudicate) {
7505 if(engineOpponent) {
7506 SendToProgram("force\n", engineOpponent); // suppress reply
7507 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7509 GameEnds( GameIsDrawn, p, GE_XBOARD );
7514 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7515 if(engineOpponent) {
7516 SendToProgram("force\n", engineOpponent); // suppress reply
7517 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7519 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7525 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7526 { // [HGM] book: this routine intercepts moves to simulate book replies
7527 char *bookHit = NULL;
7529 //first determine if the incoming move brings opponent into his book
7530 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7531 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7532 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7533 if(bookHit != NULL && !cps->bookSuspend) {
7534 // make sure opponent is not going to reply after receiving move to book position
7535 SendToProgram("force\n", cps);
7536 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7538 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7539 // now arrange restart after book miss
7541 // after a book hit we never send 'go', and the code after the call to this routine
7542 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7544 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7545 SendToProgram(buf, cps);
7546 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7547 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7548 SendToProgram("go\n", cps);
7549 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7550 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7551 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7552 SendToProgram("go\n", cps);
7553 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7555 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7559 ChessProgramState *savedState;
7560 void DeferredBookMove(void)
7562 if(savedState->lastPing != savedState->lastPong)
7563 ScheduleDelayedEvent(DeferredBookMove, 10);
7565 HandleMachineMove(savedMessage, savedState);
7569 HandleMachineMove(message, cps)
7571 ChessProgramState *cps;
7573 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7574 char realname[MSG_SIZ];
7575 int fromX, fromY, toX, toY;
7584 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7586 * Kludge to ignore BEL characters
7588 while (*message == '\007') message++;
7591 * [HGM] engine debug message: ignore lines starting with '#' character
7593 if(cps->debug && *message == '#') return;
7596 * Look for book output
7598 if (cps == &first && bookRequested) {
7599 if (message[0] == '\t' || message[0] == ' ') {
7600 /* Part of the book output is here; append it */
7601 strcat(bookOutput, message);
7602 strcat(bookOutput, " \n");
7604 } else if (bookOutput[0] != NULLCHAR) {
7605 /* All of book output has arrived; display it */
7606 char *p = bookOutput;
7607 while (*p != NULLCHAR) {
7608 if (*p == '\t') *p = ' ';
7611 DisplayInformation(bookOutput);
7612 bookRequested = FALSE;
7613 /* Fall through to parse the current output */
7618 * Look for machine move.
7620 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7621 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7623 /* This method is only useful on engines that support ping */
7624 if (cps->lastPing != cps->lastPong) {
7625 if (gameMode == BeginningOfGame) {
7626 /* Extra move from before last new; ignore */
7627 if (appData.debugMode) {
7628 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7631 if (appData.debugMode) {
7632 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7633 cps->which, gameMode);
7636 SendToProgram("undo\n", cps);
7642 case BeginningOfGame:
7643 /* Extra move from before last reset; ignore */
7644 if (appData.debugMode) {
7645 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7652 /* Extra move after we tried to stop. The mode test is
7653 not a reliable way of detecting this problem, but it's
7654 the best we can do on engines that don't support ping.
7656 if (appData.debugMode) {
7657 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7658 cps->which, gameMode);
7660 SendToProgram("undo\n", cps);
7663 case MachinePlaysWhite:
7664 case IcsPlayingWhite:
7665 machineWhite = TRUE;
7668 case MachinePlaysBlack:
7669 case IcsPlayingBlack:
7670 machineWhite = FALSE;
7673 case TwoMachinesPlay:
7674 machineWhite = (cps->twoMachinesColor[0] == 'w');
7677 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7678 if (appData.debugMode) {
7680 "Ignoring move out of turn by %s, gameMode %d"
7681 ", forwardMost %d\n",
7682 cps->which, gameMode, forwardMostMove);
7687 if (appData.debugMode) { int f = forwardMostMove;
7688 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7689 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7690 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7692 if(cps->alphaRank) AlphaRank(machineMove, 4);
7693 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7694 &fromX, &fromY, &toX, &toY, &promoChar)) {
7695 /* Machine move could not be parsed; ignore it. */
7696 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7697 machineMove, _(cps->which));
7698 DisplayError(buf1, 0);
7699 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7700 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7701 if (gameMode == TwoMachinesPlay) {
7702 GameEnds(machineWhite ? BlackWins : WhiteWins,
7708 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7709 /* So we have to redo legality test with true e.p. status here, */
7710 /* to make sure an illegal e.p. capture does not slip through, */
7711 /* to cause a forfeit on a justified illegal-move complaint */
7712 /* of the opponent. */
7713 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7715 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7716 fromY, fromX, toY, toX, promoChar);
7717 if (appData.debugMode) {
7719 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7720 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7721 fprintf(debugFP, "castling rights\n");
7723 if(moveType == IllegalMove) {
7724 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7725 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7726 GameEnds(machineWhite ? BlackWins : WhiteWins,
7729 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7730 /* [HGM] Kludge to handle engines that send FRC-style castling
7731 when they shouldn't (like TSCP-Gothic) */
7733 case WhiteASideCastleFR:
7734 case BlackASideCastleFR:
7736 currentMoveString[2]++;
7738 case WhiteHSideCastleFR:
7739 case BlackHSideCastleFR:
7741 currentMoveString[2]--;
7743 default: ; // nothing to do, but suppresses warning of pedantic compilers
7746 hintRequested = FALSE;
7747 lastHint[0] = NULLCHAR;
7748 bookRequested = FALSE;
7749 /* Program may be pondering now */
7750 cps->maybeThinking = TRUE;
7751 if (cps->sendTime == 2) cps->sendTime = 1;
7752 if (cps->offeredDraw) cps->offeredDraw--;
7754 /* [AS] Save move info*/
7755 pvInfoList[ forwardMostMove ].score = programStats.score;
7756 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7757 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7759 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7761 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7762 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7765 while( count < adjudicateLossPlies ) {
7766 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7769 score = -score; /* Flip score for winning side */
7772 if( score > adjudicateLossThreshold ) {
7779 if( count >= adjudicateLossPlies ) {
7780 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7782 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7783 "Xboard adjudication",
7790 if(Adjudicate(cps)) {
7791 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7792 return; // [HGM] adjudicate: for all automatic game ends
7796 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7798 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7799 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7801 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7803 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7805 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7806 char buf[3*MSG_SIZ];
7808 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7809 programStats.score / 100.,
7811 programStats.time / 100.,
7812 (unsigned int)programStats.nodes,
7813 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7814 programStats.movelist);
7816 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7821 /* [AS] Clear stats for next move */
7822 ClearProgramStats();
7823 thinkOutput[0] = NULLCHAR;
7824 hiddenThinkOutputState = 0;
7827 if (gameMode == TwoMachinesPlay) {
7828 /* [HGM] relaying draw offers moved to after reception of move */
7829 /* and interpreting offer as claim if it brings draw condition */
7830 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7831 SendToProgram("draw\n", cps->other);
7833 if (cps->other->sendTime) {
7834 SendTimeRemaining(cps->other,
7835 cps->other->twoMachinesColor[0] == 'w');
7837 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7838 if (firstMove && !bookHit) {
7840 if (cps->other->useColors) {
7841 SendToProgram(cps->other->twoMachinesColor, cps->other);
7843 SendToProgram("go\n", cps->other);
7845 cps->other->maybeThinking = TRUE;
7848 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7850 if (!pausing && appData.ringBellAfterMoves) {
7855 * Reenable menu items that were disabled while
7856 * machine was thinking
7858 if (gameMode != TwoMachinesPlay)
7859 SetUserThinkingEnables();
7861 // [HGM] book: after book hit opponent has received move and is now in force mode
7862 // force the book reply into it, and then fake that it outputted this move by jumping
7863 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7865 static char bookMove[MSG_SIZ]; // a bit generous?
7867 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7868 strcat(bookMove, bookHit);
7871 programStats.nodes = programStats.depth = programStats.time =
7872 programStats.score = programStats.got_only_move = 0;
7873 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7875 if(cps->lastPing != cps->lastPong) {
7876 savedMessage = message; // args for deferred call
7878 ScheduleDelayedEvent(DeferredBookMove, 10);
7887 /* Set special modes for chess engines. Later something general
7888 * could be added here; for now there is just one kludge feature,
7889 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7890 * when "xboard" is given as an interactive command.
7892 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7893 cps->useSigint = FALSE;
7894 cps->useSigterm = FALSE;
7896 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7897 ParseFeatures(message+8, cps);
7898 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7901 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7902 int dummy, s=6; char buf[MSG_SIZ];
7903 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7904 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7905 ParseFEN(boards[0], &dummy, message+s);
7906 DrawPosition(TRUE, boards[0]);
7907 startedFromSetupPosition = TRUE;
7910 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7911 * want this, I was asked to put it in, and obliged.
7913 if (!strncmp(message, "setboard ", 9)) {
7914 Board initial_position;
7916 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7918 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7919 DisplayError(_("Bad FEN received from engine"), 0);
7923 CopyBoard(boards[0], initial_position);
7924 initialRulePlies = FENrulePlies;
7925 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7926 else gameMode = MachinePlaysBlack;
7927 DrawPosition(FALSE, boards[currentMove]);
7933 * Look for communication commands
7935 if (!strncmp(message, "telluser ", 9)) {
7936 if(message[9] == '\\' && message[10] == '\\')
7937 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7938 DisplayNote(message + 9);
7941 if (!strncmp(message, "tellusererror ", 14)) {
7943 if(message[14] == '\\' && message[15] == '\\')
7944 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7945 DisplayError(message + 14, 0);
7948 if (!strncmp(message, "tellopponent ", 13)) {
7949 if (appData.icsActive) {
7951 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7955 DisplayNote(message + 13);
7959 if (!strncmp(message, "tellothers ", 11)) {
7960 if (appData.icsActive) {
7962 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7968 if (!strncmp(message, "tellall ", 8)) {
7969 if (appData.icsActive) {
7971 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7975 DisplayNote(message + 8);
7979 if (strncmp(message, "warning", 7) == 0) {
7980 /* Undocumented feature, use tellusererror in new code */
7981 DisplayError(message, 0);
7984 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7985 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7986 strcat(realname, " query");
7987 AskQuestion(realname, buf2, buf1, cps->pr);
7990 /* Commands from the engine directly to ICS. We don't allow these to be
7991 * sent until we are logged on. Crafty kibitzes have been known to
7992 * interfere with the login process.
7995 if (!strncmp(message, "tellics ", 8)) {
7996 SendToICS(message + 8);
8000 if (!strncmp(message, "tellicsnoalias ", 15)) {
8001 SendToICS(ics_prefix);
8002 SendToICS(message + 15);
8006 /* The following are for backward compatibility only */
8007 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8008 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8009 SendToICS(ics_prefix);
8015 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8019 * If the move is illegal, cancel it and redraw the board.
8020 * Also deal with other error cases. Matching is rather loose
8021 * here to accommodate engines written before the spec.
8023 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8024 strncmp(message, "Error", 5) == 0) {
8025 if (StrStr(message, "name") ||
8026 StrStr(message, "rating") || StrStr(message, "?") ||
8027 StrStr(message, "result") || StrStr(message, "board") ||
8028 StrStr(message, "bk") || StrStr(message, "computer") ||
8029 StrStr(message, "variant") || StrStr(message, "hint") ||
8030 StrStr(message, "random") || StrStr(message, "depth") ||
8031 StrStr(message, "accepted")) {
8034 if (StrStr(message, "protover")) {
8035 /* Program is responding to input, so it's apparently done
8036 initializing, and this error message indicates it is
8037 protocol version 1. So we don't need to wait any longer
8038 for it to initialize and send feature commands. */
8039 FeatureDone(cps, 1);
8040 cps->protocolVersion = 1;
8043 cps->maybeThinking = FALSE;
8045 if (StrStr(message, "draw")) {
8046 /* Program doesn't have "draw" command */
8047 cps->sendDrawOffers = 0;
8050 if (cps->sendTime != 1 &&
8051 (StrStr(message, "time") || StrStr(message, "otim"))) {
8052 /* Program apparently doesn't have "time" or "otim" command */
8056 if (StrStr(message, "analyze")) {
8057 cps->analysisSupport = FALSE;
8058 cps->analyzing = FALSE;
8060 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8061 DisplayError(buf2, 0);
8064 if (StrStr(message, "(no matching move)st")) {
8065 /* Special kludge for GNU Chess 4 only */
8066 cps->stKludge = TRUE;
8067 SendTimeControl(cps, movesPerSession, timeControl,
8068 timeIncrement, appData.searchDepth,
8072 if (StrStr(message, "(no matching move)sd")) {
8073 /* Special kludge for GNU Chess 4 only */
8074 cps->sdKludge = TRUE;
8075 SendTimeControl(cps, movesPerSession, timeControl,
8076 timeIncrement, appData.searchDepth,
8080 if (!StrStr(message, "llegal")) {
8083 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8084 gameMode == IcsIdle) return;
8085 if (forwardMostMove <= backwardMostMove) return;
8086 if (pausing) PauseEvent();
8087 if(appData.forceIllegal) {
8088 // [HGM] illegal: machine refused move; force position after move into it
8089 SendToProgram("force\n", cps);
8090 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8091 // we have a real problem now, as SendBoard will use the a2a3 kludge
8092 // when black is to move, while there might be nothing on a2 or black
8093 // might already have the move. So send the board as if white has the move.
8094 // But first we must change the stm of the engine, as it refused the last move
8095 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8096 if(WhiteOnMove(forwardMostMove)) {
8097 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8098 SendBoard(cps, forwardMostMove); // kludgeless board
8100 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8101 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8102 SendBoard(cps, forwardMostMove+1); // kludgeless board
8104 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8105 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8106 gameMode == TwoMachinesPlay)
8107 SendToProgram("go\n", cps);
8110 if (gameMode == PlayFromGameFile) {
8111 /* Stop reading this game file */
8112 gameMode = EditGame;
8115 /* [HGM] illegal-move claim should forfeit game when Xboard */
8116 /* only passes fully legal moves */
8117 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8118 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8119 "False illegal-move claim", GE_XBOARD );
8120 return; // do not take back move we tested as valid
8122 currentMove = forwardMostMove-1;
8123 DisplayMove(currentMove-1); /* before DisplayMoveError */
8124 SwitchClocks(forwardMostMove-1); // [HGM] race
8125 DisplayBothClocks();
8126 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8127 parseList[currentMove], _(cps->which));
8128 DisplayMoveError(buf1);
8129 DrawPosition(FALSE, boards[currentMove]);
8132 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8133 /* Program has a broken "time" command that
8134 outputs a string not ending in newline.
8140 * If chess program startup fails, exit with an error message.
8141 * Attempts to recover here are futile.
8143 if ((StrStr(message, "unknown host") != NULL)
8144 || (StrStr(message, "No remote directory") != NULL)
8145 || (StrStr(message, "not found") != NULL)
8146 || (StrStr(message, "No such file") != NULL)
8147 || (StrStr(message, "can't alloc") != NULL)
8148 || (StrStr(message, "Permission denied") != NULL)) {
8150 cps->maybeThinking = FALSE;
8151 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8152 _(cps->which), cps->program, cps->host, message);
8153 RemoveInputSource(cps->isr);
8154 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8155 if(cps == &first) appData.noChessProgram = TRUE;
8156 DisplayError(buf1, 0);
8162 * Look for hint output
8164 if (sscanf(message, "Hint: %s", buf1) == 1) {
8165 if (cps == &first && hintRequested) {
8166 hintRequested = FALSE;
8167 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8168 &fromX, &fromY, &toX, &toY, &promoChar)) {
8169 (void) CoordsToAlgebraic(boards[forwardMostMove],
8170 PosFlags(forwardMostMove),
8171 fromY, fromX, toY, toX, promoChar, buf1);
8172 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8173 DisplayInformation(buf2);
8175 /* Hint move could not be parsed!? */
8176 snprintf(buf2, sizeof(buf2),
8177 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8178 buf1, _(cps->which));
8179 DisplayError(buf2, 0);
8182 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8188 * Ignore other messages if game is not in progress
8190 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8191 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8194 * look for win, lose, draw, or draw offer
8196 if (strncmp(message, "1-0", 3) == 0) {
8197 char *p, *q, *r = "";
8198 p = strchr(message, '{');
8206 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8208 } else if (strncmp(message, "0-1", 3) == 0) {
8209 char *p, *q, *r = "";
8210 p = strchr(message, '{');
8218 /* Kludge for Arasan 4.1 bug */
8219 if (strcmp(r, "Black resigns") == 0) {
8220 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8223 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8225 } else if (strncmp(message, "1/2", 3) == 0) {
8226 char *p, *q, *r = "";
8227 p = strchr(message, '{');
8236 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8239 } else if (strncmp(message, "White resign", 12) == 0) {
8240 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8242 } else if (strncmp(message, "Black resign", 12) == 0) {
8243 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8245 } else if (strncmp(message, "White matches", 13) == 0 ||
8246 strncmp(message, "Black matches", 13) == 0 ) {
8247 /* [HGM] ignore GNUShogi noises */
8249 } else if (strncmp(message, "White", 5) == 0 &&
8250 message[5] != '(' &&
8251 StrStr(message, "Black") == NULL) {
8252 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8254 } else if (strncmp(message, "Black", 5) == 0 &&
8255 message[5] != '(') {
8256 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8258 } else if (strcmp(message, "resign") == 0 ||
8259 strcmp(message, "computer resigns") == 0) {
8261 case MachinePlaysBlack:
8262 case IcsPlayingBlack:
8263 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8265 case MachinePlaysWhite:
8266 case IcsPlayingWhite:
8267 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8269 case TwoMachinesPlay:
8270 if (cps->twoMachinesColor[0] == 'w')
8271 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8273 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8280 } else if (strncmp(message, "opponent mates", 14) == 0) {
8282 case MachinePlaysBlack:
8283 case IcsPlayingBlack:
8284 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8286 case MachinePlaysWhite:
8287 case IcsPlayingWhite:
8288 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8290 case TwoMachinesPlay:
8291 if (cps->twoMachinesColor[0] == 'w')
8292 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8294 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8301 } else if (strncmp(message, "computer mates", 14) == 0) {
8303 case MachinePlaysBlack:
8304 case IcsPlayingBlack:
8305 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8307 case MachinePlaysWhite:
8308 case IcsPlayingWhite:
8309 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8311 case TwoMachinesPlay:
8312 if (cps->twoMachinesColor[0] == 'w')
8313 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8315 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8322 } else if (strncmp(message, "checkmate", 9) == 0) {
8323 if (WhiteOnMove(forwardMostMove)) {
8324 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8326 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8329 } else if (strstr(message, "Draw") != NULL ||
8330 strstr(message, "game is a draw") != NULL) {
8331 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8333 } else if (strstr(message, "offer") != NULL &&
8334 strstr(message, "draw") != NULL) {
8336 if (appData.zippyPlay && first.initDone) {
8337 /* Relay offer to ICS */
8338 SendToICS(ics_prefix);
8339 SendToICS("draw\n");
8342 cps->offeredDraw = 2; /* valid until this engine moves twice */
8343 if (gameMode == TwoMachinesPlay) {
8344 if (cps->other->offeredDraw) {
8345 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8346 /* [HGM] in two-machine mode we delay relaying draw offer */
8347 /* until after we also have move, to see if it is really claim */
8349 } else if (gameMode == MachinePlaysWhite ||
8350 gameMode == MachinePlaysBlack) {
8351 if (userOfferedDraw) {
8352 DisplayInformation(_("Machine accepts your draw offer"));
8353 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8355 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8362 * Look for thinking output
8364 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8365 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8367 int plylev, mvleft, mvtot, curscore, time;
8368 char mvname[MOVE_LEN];
8372 int prefixHint = FALSE;
8373 mvname[0] = NULLCHAR;
8376 case MachinePlaysBlack:
8377 case IcsPlayingBlack:
8378 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8380 case MachinePlaysWhite:
8381 case IcsPlayingWhite:
8382 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8387 case IcsObserving: /* [DM] icsEngineAnalyze */
8388 if (!appData.icsEngineAnalyze) ignore = TRUE;
8390 case TwoMachinesPlay:
8391 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8401 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8403 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8404 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8406 if (plyext != ' ' && plyext != '\t') {
8410 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8411 if( cps->scoreIsAbsolute &&
8412 ( gameMode == MachinePlaysBlack ||
8413 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8414 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8415 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8416 !WhiteOnMove(currentMove)
8419 curscore = -curscore;
8423 tempStats.depth = plylev;
8424 tempStats.nodes = nodes;
8425 tempStats.time = time;
8426 tempStats.score = curscore;
8427 tempStats.got_only_move = 0;
8429 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8432 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8433 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8434 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8435 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8436 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8437 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8438 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8439 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8442 /* Buffer overflow protection */
8443 if (buf1[0] != NULLCHAR) {
8444 if (strlen(buf1) >= sizeof(tempStats.movelist)
8445 && appData.debugMode) {
8447 "PV is too long; using the first %u bytes.\n",
8448 (unsigned) sizeof(tempStats.movelist) - 1);
8451 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8453 sprintf(tempStats.movelist, " no PV\n");
8456 if (tempStats.seen_stat) {
8457 tempStats.ok_to_send = 1;
8460 if (strchr(tempStats.movelist, '(') != NULL) {
8461 tempStats.line_is_book = 1;
8462 tempStats.nr_moves = 0;
8463 tempStats.moves_left = 0;
8465 tempStats.line_is_book = 0;
8468 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8469 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8471 SendProgramStatsToFrontend( cps, &tempStats );
8474 [AS] Protect the thinkOutput buffer from overflow... this
8475 is only useful if buf1 hasn't overflowed first!
8477 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8479 (gameMode == TwoMachinesPlay ?
8480 ToUpper(cps->twoMachinesColor[0]) : ' '),
8481 ((double) curscore) / 100.0,
8482 prefixHint ? lastHint : "",
8483 prefixHint ? " " : "" );
8485 if( buf1[0] != NULLCHAR ) {
8486 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8488 if( strlen(buf1) > max_len ) {
8489 if( appData.debugMode) {
8490 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8492 buf1[max_len+1] = '\0';
8495 strcat( thinkOutput, buf1 );
8498 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8499 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8500 DisplayMove(currentMove - 1);
8504 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8505 /* crafty (9.25+) says "(only move) <move>"
8506 * if there is only 1 legal move
8508 sscanf(p, "(only move) %s", buf1);
8509 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8510 sprintf(programStats.movelist, "%s (only move)", buf1);
8511 programStats.depth = 1;
8512 programStats.nr_moves = 1;
8513 programStats.moves_left = 1;
8514 programStats.nodes = 1;
8515 programStats.time = 1;
8516 programStats.got_only_move = 1;
8518 /* Not really, but we also use this member to
8519 mean "line isn't going to change" (Crafty
8520 isn't searching, so stats won't change) */
8521 programStats.line_is_book = 1;
8523 SendProgramStatsToFrontend( cps, &programStats );
8525 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8526 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8527 DisplayMove(currentMove - 1);
8530 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8531 &time, &nodes, &plylev, &mvleft,
8532 &mvtot, mvname) >= 5) {
8533 /* The stat01: line is from Crafty (9.29+) in response
8534 to the "." command */
8535 programStats.seen_stat = 1;
8536 cps->maybeThinking = TRUE;
8538 if (programStats.got_only_move || !appData.periodicUpdates)
8541 programStats.depth = plylev;
8542 programStats.time = time;
8543 programStats.nodes = nodes;
8544 programStats.moves_left = mvleft;
8545 programStats.nr_moves = mvtot;
8546 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8547 programStats.ok_to_send = 1;
8548 programStats.movelist[0] = '\0';
8550 SendProgramStatsToFrontend( cps, &programStats );
8554 } else if (strncmp(message,"++",2) == 0) {
8555 /* Crafty 9.29+ outputs this */
8556 programStats.got_fail = 2;
8559 } else if (strncmp(message,"--",2) == 0) {
8560 /* Crafty 9.29+ outputs this */
8561 programStats.got_fail = 1;
8564 } else if (thinkOutput[0] != NULLCHAR &&
8565 strncmp(message, " ", 4) == 0) {
8566 unsigned message_len;
8569 while (*p && *p == ' ') p++;
8571 message_len = strlen( p );
8573 /* [AS] Avoid buffer overflow */
8574 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8575 strcat(thinkOutput, " ");
8576 strcat(thinkOutput, p);
8579 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8580 strcat(programStats.movelist, " ");
8581 strcat(programStats.movelist, p);
8584 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8585 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8586 DisplayMove(currentMove - 1);
8594 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8595 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8597 ChessProgramStats cpstats;
8599 if (plyext != ' ' && plyext != '\t') {
8603 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8604 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8605 curscore = -curscore;
8608 cpstats.depth = plylev;
8609 cpstats.nodes = nodes;
8610 cpstats.time = time;
8611 cpstats.score = curscore;
8612 cpstats.got_only_move = 0;
8613 cpstats.movelist[0] = '\0';
8615 if (buf1[0] != NULLCHAR) {
8616 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8619 cpstats.ok_to_send = 0;
8620 cpstats.line_is_book = 0;
8621 cpstats.nr_moves = 0;
8622 cpstats.moves_left = 0;
8624 SendProgramStatsToFrontend( cps, &cpstats );
8631 /* Parse a game score from the character string "game", and
8632 record it as the history of the current game. The game
8633 score is NOT assumed to start from the standard position.
8634 The display is not updated in any way.
8637 ParseGameHistory(game)
8641 int fromX, fromY, toX, toY, boardIndex;
8646 if (appData.debugMode)
8647 fprintf(debugFP, "Parsing game history: %s\n", game);
8649 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8650 gameInfo.site = StrSave(appData.icsHost);
8651 gameInfo.date = PGNDate();
8652 gameInfo.round = StrSave("-");
8654 /* Parse out names of players */
8655 while (*game == ' ') game++;
8657 while (*game != ' ') *p++ = *game++;
8659 gameInfo.white = StrSave(buf);
8660 while (*game == ' ') game++;
8662 while (*game != ' ' && *game != '\n') *p++ = *game++;
8664 gameInfo.black = StrSave(buf);
8667 boardIndex = blackPlaysFirst ? 1 : 0;
8670 yyboardindex = boardIndex;
8671 moveType = (ChessMove) Myylex();
8673 case IllegalMove: /* maybe suicide chess, etc. */
8674 if (appData.debugMode) {
8675 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8676 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8677 setbuf(debugFP, NULL);
8679 case WhitePromotion:
8680 case BlackPromotion:
8681 case WhiteNonPromotion:
8682 case BlackNonPromotion:
8684 case WhiteCapturesEnPassant:
8685 case BlackCapturesEnPassant:
8686 case WhiteKingSideCastle:
8687 case WhiteQueenSideCastle:
8688 case BlackKingSideCastle:
8689 case BlackQueenSideCastle:
8690 case WhiteKingSideCastleWild:
8691 case WhiteQueenSideCastleWild:
8692 case BlackKingSideCastleWild:
8693 case BlackQueenSideCastleWild:
8695 case WhiteHSideCastleFR:
8696 case WhiteASideCastleFR:
8697 case BlackHSideCastleFR:
8698 case BlackASideCastleFR:
8700 fromX = currentMoveString[0] - AAA;
8701 fromY = currentMoveString[1] - ONE;
8702 toX = currentMoveString[2] - AAA;
8703 toY = currentMoveString[3] - ONE;
8704 promoChar = currentMoveString[4];
8708 fromX = moveType == WhiteDrop ?
8709 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8710 (int) CharToPiece(ToLower(currentMoveString[0]));
8712 toX = currentMoveString[2] - AAA;
8713 toY = currentMoveString[3] - ONE;
8714 promoChar = NULLCHAR;
8718 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8719 if (appData.debugMode) {
8720 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8721 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8722 setbuf(debugFP, NULL);
8724 DisplayError(buf, 0);
8726 case ImpossibleMove:
8728 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8729 if (appData.debugMode) {
8730 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8731 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8732 setbuf(debugFP, NULL);
8734 DisplayError(buf, 0);
8737 if (boardIndex < backwardMostMove) {
8738 /* Oops, gap. How did that happen? */
8739 DisplayError(_("Gap in move list"), 0);
8742 backwardMostMove = blackPlaysFirst ? 1 : 0;
8743 if (boardIndex > forwardMostMove) {
8744 forwardMostMove = boardIndex;
8748 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8749 strcat(parseList[boardIndex-1], " ");
8750 strcat(parseList[boardIndex-1], yy_text);
8762 case GameUnfinished:
8763 if (gameMode == IcsExamining) {
8764 if (boardIndex < backwardMostMove) {
8765 /* Oops, gap. How did that happen? */
8768 backwardMostMove = blackPlaysFirst ? 1 : 0;
8771 gameInfo.result = moveType;
8772 p = strchr(yy_text, '{');
8773 if (p == NULL) p = strchr(yy_text, '(');
8776 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8778 q = strchr(p, *p == '{' ? '}' : ')');
8779 if (q != NULL) *q = NULLCHAR;
8782 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8783 gameInfo.resultDetails = StrSave(p);
8786 if (boardIndex >= forwardMostMove &&
8787 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8788 backwardMostMove = blackPlaysFirst ? 1 : 0;
8791 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8792 fromY, fromX, toY, toX, promoChar,
8793 parseList[boardIndex]);
8794 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8795 /* currentMoveString is set as a side-effect of yylex */
8796 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8797 strcat(moveList[boardIndex], "\n");
8799 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8800 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8806 if(gameInfo.variant != VariantShogi)
8807 strcat(parseList[boardIndex - 1], "+");
8811 strcat(parseList[boardIndex - 1], "#");
8818 /* Apply a move to the given board */
8820 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8821 int fromX, fromY, toX, toY;
8825 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8826 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8828 /* [HGM] compute & store e.p. status and castling rights for new position */
8829 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8831 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8832 oldEP = (signed char)board[EP_STATUS];
8833 board[EP_STATUS] = EP_NONE;
8835 if( board[toY][toX] != EmptySquare )
8836 board[EP_STATUS] = EP_CAPTURE;
8838 if (fromY == DROP_RANK) {
8840 piece = board[toY][toX] = (ChessSquare) fromX;
8844 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8845 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8846 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8848 if( board[fromY][fromX] == WhitePawn ) {
8849 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8850 board[EP_STATUS] = EP_PAWN_MOVE;
8852 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8853 gameInfo.variant != VariantBerolina || toX < fromX)
8854 board[EP_STATUS] = toX | berolina;
8855 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8856 gameInfo.variant != VariantBerolina || toX > fromX)
8857 board[EP_STATUS] = toX;
8860 if( board[fromY][fromX] == BlackPawn ) {
8861 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8862 board[EP_STATUS] = EP_PAWN_MOVE;
8863 if( toY-fromY== -2) {
8864 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8865 gameInfo.variant != VariantBerolina || toX < fromX)
8866 board[EP_STATUS] = toX | berolina;
8867 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8868 gameInfo.variant != VariantBerolina || toX > fromX)
8869 board[EP_STATUS] = toX;
8873 for(i=0; i<nrCastlingRights; i++) {
8874 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8875 board[CASTLING][i] == toX && castlingRank[i] == toY
8876 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8879 if (fromX == toX && fromY == toY) return;
8881 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8882 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8883 if(gameInfo.variant == VariantKnightmate)
8884 king += (int) WhiteUnicorn - (int) WhiteKing;
8886 /* Code added by Tord: */
8887 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8888 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8889 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8890 board[fromY][fromX] = EmptySquare;
8891 board[toY][toX] = EmptySquare;
8892 if((toX > fromX) != (piece == WhiteRook)) {
8893 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8895 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8897 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8898 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8899 board[fromY][fromX] = EmptySquare;
8900 board[toY][toX] = EmptySquare;
8901 if((toX > fromX) != (piece == BlackRook)) {
8902 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8904 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8906 /* End of code added by Tord */
8908 } else if (board[fromY][fromX] == king
8909 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8910 && toY == fromY && toX > fromX+1) {
8911 board[fromY][fromX] = EmptySquare;
8912 board[toY][toX] = king;
8913 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8914 board[fromY][BOARD_RGHT-1] = EmptySquare;
8915 } else if (board[fromY][fromX] == king
8916 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8917 && toY == fromY && toX < fromX-1) {
8918 board[fromY][fromX] = EmptySquare;
8919 board[toY][toX] = king;
8920 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8921 board[fromY][BOARD_LEFT] = EmptySquare;
8922 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8923 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8924 && toY >= BOARD_HEIGHT-promoRank
8926 /* white pawn promotion */
8927 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8928 if (board[toY][toX] == EmptySquare) {
8929 board[toY][toX] = WhiteQueen;
8931 if(gameInfo.variant==VariantBughouse ||
8932 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8933 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8934 board[fromY][fromX] = EmptySquare;
8935 } else if ((fromY == BOARD_HEIGHT-4)
8937 && gameInfo.variant != VariantXiangqi
8938 && gameInfo.variant != VariantBerolina
8939 && (board[fromY][fromX] == WhitePawn)
8940 && (board[toY][toX] == EmptySquare)) {
8941 board[fromY][fromX] = EmptySquare;
8942 board[toY][toX] = WhitePawn;
8943 captured = board[toY - 1][toX];
8944 board[toY - 1][toX] = EmptySquare;
8945 } else if ((fromY == BOARD_HEIGHT-4)
8947 && gameInfo.variant == VariantBerolina
8948 && (board[fromY][fromX] == WhitePawn)
8949 && (board[toY][toX] == EmptySquare)) {
8950 board[fromY][fromX] = EmptySquare;
8951 board[toY][toX] = WhitePawn;
8952 if(oldEP & EP_BEROLIN_A) {
8953 captured = board[fromY][fromX-1];
8954 board[fromY][fromX-1] = EmptySquare;
8955 }else{ captured = board[fromY][fromX+1];
8956 board[fromY][fromX+1] = EmptySquare;
8958 } else if (board[fromY][fromX] == king
8959 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8960 && toY == fromY && toX > fromX+1) {
8961 board[fromY][fromX] = EmptySquare;
8962 board[toY][toX] = king;
8963 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8964 board[fromY][BOARD_RGHT-1] = EmptySquare;
8965 } else if (board[fromY][fromX] == king
8966 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8967 && toY == fromY && toX < fromX-1) {
8968 board[fromY][fromX] = EmptySquare;
8969 board[toY][toX] = king;
8970 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8971 board[fromY][BOARD_LEFT] = EmptySquare;
8972 } else if (fromY == 7 && fromX == 3
8973 && board[fromY][fromX] == BlackKing
8974 && toY == 7 && toX == 5) {
8975 board[fromY][fromX] = EmptySquare;
8976 board[toY][toX] = BlackKing;
8977 board[fromY][7] = EmptySquare;
8978 board[toY][4] = BlackRook;
8979 } else if (fromY == 7 && fromX == 3
8980 && board[fromY][fromX] == BlackKing
8981 && toY == 7 && toX == 1) {
8982 board[fromY][fromX] = EmptySquare;
8983 board[toY][toX] = BlackKing;
8984 board[fromY][0] = EmptySquare;
8985 board[toY][2] = BlackRook;
8986 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8987 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8990 /* black pawn promotion */
8991 board[toY][toX] = CharToPiece(ToLower(promoChar));
8992 if (board[toY][toX] == EmptySquare) {
8993 board[toY][toX] = BlackQueen;
8995 if(gameInfo.variant==VariantBughouse ||
8996 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8997 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8998 board[fromY][fromX] = EmptySquare;
8999 } else if ((fromY == 3)
9001 && gameInfo.variant != VariantXiangqi
9002 && gameInfo.variant != VariantBerolina
9003 && (board[fromY][fromX] == BlackPawn)
9004 && (board[toY][toX] == EmptySquare)) {
9005 board[fromY][fromX] = EmptySquare;
9006 board[toY][toX] = BlackPawn;
9007 captured = board[toY + 1][toX];
9008 board[toY + 1][toX] = EmptySquare;
9009 } else if ((fromY == 3)
9011 && gameInfo.variant == VariantBerolina
9012 && (board[fromY][fromX] == BlackPawn)
9013 && (board[toY][toX] == EmptySquare)) {
9014 board[fromY][fromX] = EmptySquare;
9015 board[toY][toX] = BlackPawn;
9016 if(oldEP & EP_BEROLIN_A) {
9017 captured = board[fromY][fromX-1];
9018 board[fromY][fromX-1] = EmptySquare;
9019 }else{ captured = board[fromY][fromX+1];
9020 board[fromY][fromX+1] = EmptySquare;
9023 board[toY][toX] = board[fromY][fromX];
9024 board[fromY][fromX] = EmptySquare;
9028 if (gameInfo.holdingsWidth != 0) {
9030 /* !!A lot more code needs to be written to support holdings */
9031 /* [HGM] OK, so I have written it. Holdings are stored in the */
9032 /* penultimate board files, so they are automaticlly stored */
9033 /* in the game history. */
9034 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9035 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9036 /* Delete from holdings, by decreasing count */
9037 /* and erasing image if necessary */
9038 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9039 if(p < (int) BlackPawn) { /* white drop */
9040 p -= (int)WhitePawn;
9041 p = PieceToNumber((ChessSquare)p);
9042 if(p >= gameInfo.holdingsSize) p = 0;
9043 if(--board[p][BOARD_WIDTH-2] <= 0)
9044 board[p][BOARD_WIDTH-1] = EmptySquare;
9045 if((int)board[p][BOARD_WIDTH-2] < 0)
9046 board[p][BOARD_WIDTH-2] = 0;
9047 } else { /* black drop */
9048 p -= (int)BlackPawn;
9049 p = PieceToNumber((ChessSquare)p);
9050 if(p >= gameInfo.holdingsSize) p = 0;
9051 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9052 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9053 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9054 board[BOARD_HEIGHT-1-p][1] = 0;
9057 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9058 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9059 /* [HGM] holdings: Add to holdings, if holdings exist */
9060 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9061 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9062 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9065 if (p >= (int) BlackPawn) {
9066 p -= (int)BlackPawn;
9067 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9068 /* in Shogi restore piece to its original first */
9069 captured = (ChessSquare) (DEMOTED captured);
9072 p = PieceToNumber((ChessSquare)p);
9073 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9074 board[p][BOARD_WIDTH-2]++;
9075 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9077 p -= (int)WhitePawn;
9078 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9079 captured = (ChessSquare) (DEMOTED captured);
9082 p = PieceToNumber((ChessSquare)p);
9083 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9084 board[BOARD_HEIGHT-1-p][1]++;
9085 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9088 } else if (gameInfo.variant == VariantAtomic) {
9089 if (captured != EmptySquare) {
9091 for (y = toY-1; y <= toY+1; y++) {
9092 for (x = toX-1; x <= toX+1; x++) {
9093 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9094 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9095 board[y][x] = EmptySquare;
9099 board[toY][toX] = EmptySquare;
9102 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9103 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9105 if(promoChar == '+') {
9106 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9107 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9108 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9109 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9111 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9112 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9113 // [HGM] superchess: take promotion piece out of holdings
9114 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9115 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9116 if(!--board[k][BOARD_WIDTH-2])
9117 board[k][BOARD_WIDTH-1] = EmptySquare;
9119 if(!--board[BOARD_HEIGHT-1-k][1])
9120 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9126 /* Updates forwardMostMove */
9128 MakeMove(fromX, fromY, toX, toY, promoChar)
9129 int fromX, fromY, toX, toY;
9132 // forwardMostMove++; // [HGM] bare: moved downstream
9134 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9135 int timeLeft; static int lastLoadFlag=0; int king, piece;
9136 piece = boards[forwardMostMove][fromY][fromX];
9137 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9138 if(gameInfo.variant == VariantKnightmate)
9139 king += (int) WhiteUnicorn - (int) WhiteKing;
9140 if(forwardMostMove == 0) {
9142 fprintf(serverMoves, "%s;", second.tidy);
9143 fprintf(serverMoves, "%s;", first.tidy);
9144 if(!blackPlaysFirst)
9145 fprintf(serverMoves, "%s;", second.tidy);
9146 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9147 lastLoadFlag = loadFlag;
9149 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9150 // print castling suffix
9151 if( toY == fromY && piece == king ) {
9153 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9155 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9158 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9159 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9160 boards[forwardMostMove][toY][toX] == EmptySquare
9161 && fromX != toX && fromY != toY)
9162 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9164 if(promoChar != NULLCHAR)
9165 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9167 fprintf(serverMoves, "/%d/%d",
9168 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9169 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9170 else timeLeft = blackTimeRemaining/1000;
9171 fprintf(serverMoves, "/%d", timeLeft);
9173 fflush(serverMoves);
9176 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9177 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9181 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9182 if (commentList[forwardMostMove+1] != NULL) {
9183 free(commentList[forwardMostMove+1]);
9184 commentList[forwardMostMove+1] = NULL;
9186 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9187 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9188 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9189 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9190 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9191 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9192 gameInfo.result = GameUnfinished;
9193 if (gameInfo.resultDetails != NULL) {
9194 free(gameInfo.resultDetails);
9195 gameInfo.resultDetails = NULL;
9197 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9198 moveList[forwardMostMove - 1]);
9199 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9200 PosFlags(forwardMostMove - 1),
9201 fromY, fromX, toY, toX, promoChar,
9202 parseList[forwardMostMove - 1]);
9203 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9209 if(gameInfo.variant != VariantShogi)
9210 strcat(parseList[forwardMostMove - 1], "+");
9214 strcat(parseList[forwardMostMove - 1], "#");
9217 if (appData.debugMode) {
9218 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9223 /* Updates currentMove if not pausing */
9225 ShowMove(fromX, fromY, toX, toY)
9227 int instant = (gameMode == PlayFromGameFile) ?
9228 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9229 if(appData.noGUI) return;
9230 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9232 if (forwardMostMove == currentMove + 1) {
9233 AnimateMove(boards[forwardMostMove - 1],
9234 fromX, fromY, toX, toY);
9236 if (appData.highlightLastMove) {
9237 SetHighlights(fromX, fromY, toX, toY);
9240 currentMove = forwardMostMove;
9243 if (instant) return;
9245 DisplayMove(currentMove - 1);
9246 DrawPosition(FALSE, boards[currentMove]);
9247 DisplayBothClocks();
9248 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9251 void SendEgtPath(ChessProgramState *cps)
9252 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9253 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9255 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9258 char c, *q = name+1, *r, *s;
9260 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9261 while(*p && *p != ',') *q++ = *p++;
9263 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9264 strcmp(name, ",nalimov:") == 0 ) {
9265 // take nalimov path from the menu-changeable option first, if it is defined
9266 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9267 SendToProgram(buf,cps); // send egtbpath command for nalimov
9269 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9270 (s = StrStr(appData.egtFormats, name)) != NULL) {
9271 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9272 s = r = StrStr(s, ":") + 1; // beginning of path info
9273 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9274 c = *r; *r = 0; // temporarily null-terminate path info
9275 *--q = 0; // strip of trailig ':' from name
9276 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9278 SendToProgram(buf,cps); // send egtbpath command for this format
9280 if(*p == ',') p++; // read away comma to position for next format name
9285 InitChessProgram(cps, setup)
9286 ChessProgramState *cps;
9287 int setup; /* [HGM] needed to setup FRC opening position */
9289 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9290 if (appData.noChessProgram) return;
9291 hintRequested = FALSE;
9292 bookRequested = FALSE;
9294 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9295 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9296 if(cps->memSize) { /* [HGM] memory */
9297 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9298 SendToProgram(buf, cps);
9300 SendEgtPath(cps); /* [HGM] EGT */
9301 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9302 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9303 SendToProgram(buf, cps);
9306 SendToProgram(cps->initString, cps);
9307 if (gameInfo.variant != VariantNormal &&
9308 gameInfo.variant != VariantLoadable
9309 /* [HGM] also send variant if board size non-standard */
9310 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9312 char *v = VariantName(gameInfo.variant);
9313 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9314 /* [HGM] in protocol 1 we have to assume all variants valid */
9315 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9316 DisplayFatalError(buf, 0, 1);
9320 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9321 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9322 if( gameInfo.variant == VariantXiangqi )
9323 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9324 if( gameInfo.variant == VariantShogi )
9325 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9326 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9327 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9328 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9329 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9330 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9331 if( gameInfo.variant == VariantCourier )
9332 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9333 if( gameInfo.variant == VariantSuper )
9334 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9335 if( gameInfo.variant == VariantGreat )
9336 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9337 if( gameInfo.variant == VariantSChess )
9338 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9341 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9342 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9343 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9344 if(StrStr(cps->variants, b) == NULL) {
9345 // specific sized variant not known, check if general sizing allowed
9346 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9347 if(StrStr(cps->variants, "boardsize") == NULL) {
9348 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9349 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9350 DisplayFatalError(buf, 0, 1);
9353 /* [HGM] here we really should compare with the maximum supported board size */
9356 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9357 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9358 SendToProgram(buf, cps);
9360 currentlyInitializedVariant = gameInfo.variant;
9362 /* [HGM] send opening position in FRC to first engine */
9364 SendToProgram("force\n", cps);
9366 /* engine is now in force mode! Set flag to wake it up after first move. */
9367 setboardSpoiledMachineBlack = 1;
9371 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9372 SendToProgram(buf, cps);
9374 cps->maybeThinking = FALSE;
9375 cps->offeredDraw = 0;
9376 if (!appData.icsActive) {
9377 SendTimeControl(cps, movesPerSession, timeControl,
9378 timeIncrement, appData.searchDepth,
9381 if (appData.showThinking
9382 // [HGM] thinking: four options require thinking output to be sent
9383 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9385 SendToProgram("post\n", cps);
9387 SendToProgram("hard\n", cps);
9388 if (!appData.ponderNextMove) {
9389 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9390 it without being sure what state we are in first. "hard"
9391 is not a toggle, so that one is OK.
9393 SendToProgram("easy\n", cps);
9396 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9397 SendToProgram(buf, cps);
9399 cps->initDone = TRUE;
9404 StartChessProgram(cps)
9405 ChessProgramState *cps;
9410 if (appData.noChessProgram) return;
9411 cps->initDone = FALSE;
9413 if (strcmp(cps->host, "localhost") == 0) {
9414 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9415 } else if (*appData.remoteShell == NULLCHAR) {
9416 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9418 if (*appData.remoteUser == NULLCHAR) {
9419 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9422 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9423 cps->host, appData.remoteUser, cps->program);
9425 err = StartChildProcess(buf, "", &cps->pr);
9429 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9430 DisplayFatalError(buf, err, 1);
9436 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9437 if (cps->protocolVersion > 1) {
9438 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9439 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9440 cps->comboCnt = 0; // and values of combo boxes
9441 SendToProgram(buf, cps);
9443 SendToProgram("xboard\n", cps);
9448 TwoMachinesEventIfReady P((void))
9450 static int curMess = 0;
9451 if (first.lastPing != first.lastPong) {
9452 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9453 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9456 if (second.lastPing != second.lastPong) {
9457 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9458 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9461 DisplayMessage("", ""); curMess = 0;
9467 CreateTourney(char *name)
9470 if(name[0] == NULLCHAR) return 0;
9471 f = fopen(appData.tourneyFile, "r");
9472 if(f) { // file exists
9473 ParseArgsFromFile(f); // parse it
9475 f = fopen(appData.tourneyFile, "w");
9476 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9477 // create a file with tournament description
9478 fprintf(f, "-participants {%s}\n", appData.participants);
9479 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9480 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9481 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9482 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9483 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9484 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9485 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9486 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9487 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9488 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9489 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9491 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9493 fprintf(f, "-mps %d\n", appData.movesPerSession);
9494 fprintf(f, "-tc %s\n", appData.timeControl);
9495 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9497 fprintf(f, "-results \"\"\n");
9501 appData.noChessProgram = FALSE;
9502 appData.clockMode = TRUE;
9507 #define MAXENGINES 1000
9508 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9510 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9512 char buf[MSG_SIZ], *p, *q;
9516 while(*p && *p != '\n') *q++ = *p++;
9518 if(engineList[i]) free(engineList[i]);
9519 engineList[i] = strdup(buf);
9521 TidyProgramName(engineList[i], "localhost", buf);
9522 if(engineMnemonic[i]) free(engineMnemonic[i]);
9523 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9525 sscanf(q + 8, "%s", buf + strlen(buf));
9528 engineMnemonic[i] = strdup(buf);
9530 if(i > MAXENGINES - 2) break;
9532 engineList[i] = NULL;
9535 // following implemented as macro to avoid type limitations
9536 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9538 void SwapEngines(int n)
9539 { // swap settings for first engine and other engine (so far only some selected options)
9544 SWAP(chessProgram, p)
9546 SWAP(hasOwnBookUCI, h)
9547 SWAP(protocolVersion, h)
9549 SWAP(scoreIsAbsolute, h)
9556 SetPlayer(int player)
9557 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9559 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9560 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9561 "-firstNeedsNoncompliantFEN false -firstNPS -1 -fn \"\"";
9562 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9563 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9564 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9566 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9567 ParseArgsFromString(resetOptions);
9568 ParseArgsFromString(buf);
9574 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9575 { // determine players from game number
9576 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9578 if(appData.tourneyType == 0) {
9579 roundsPerCycle = (nPlayers - 1) | 1;
9580 pairingsPerRound = nPlayers / 2;
9581 } else if(appData.tourneyType > 0) {
9582 roundsPerCycle = nPlayers - appData.tourneyType;
9583 pairingsPerRound = appData.tourneyType;
9585 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9586 gamesPerCycle = gamesPerRound * roundsPerCycle;
9587 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9588 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9589 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9590 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9591 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9592 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9594 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9595 if(appData.roundSync) *syncInterval = gamesPerRound;
9597 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9599 if(appData.tourneyType == 0) {
9600 if(curPairing == (nPlayers-1)/2 ) {
9601 *whitePlayer = curRound;
9602 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9604 *whitePlayer = curRound - pairingsPerRound + curPairing;
9605 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9606 *blackPlayer = curRound + pairingsPerRound - curPairing;
9607 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9609 } else if(appData.tourneyType > 0) {
9610 *whitePlayer = curPairing;
9611 *blackPlayer = curRound + appData.tourneyType;
9614 // take care of white/black alternation per round.
9615 // For cycles and games this is already taken care of by default, derived from matchGame!
9616 return curRound & 1;
9620 NextTourneyGame(int nr, int *swapColors)
9621 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9623 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9625 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9626 tf = fopen(appData.tourneyFile, "r");
9627 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9628 ParseArgsFromFile(tf); fclose(tf);
9629 InitTimeControls(); // TC might be altered from tourney file
9631 p = appData.participants;
9632 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9633 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9636 p = q = appData.results;
9637 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9638 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9639 DisplayMessage(_("Waiting for other game(s)"),"");
9640 waitingForGame = TRUE;
9641 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9644 waitingForGame = FALSE;
9647 if(first.pr != NoProc) return 1; // engines already loaded
9649 // redefine engines, engine dir, etc.
9650 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9651 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9653 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9654 SwapEngines(1); // and make that valid for second engine by swapping
9655 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9656 InitEngine(&second, 1);
9657 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9663 { // performs game initialization that does not invoke engines, and then tries to start the game
9664 int firstWhite, swapColors = 0;
9665 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9666 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9667 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9668 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9669 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9670 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9671 Reset(FALSE, first.pr != NoProc);
9672 appData.noChessProgram = FALSE;
9673 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9677 void UserAdjudicationEvent( int result )
9679 ChessMove gameResult = GameIsDrawn;
9682 gameResult = WhiteWins;
9684 else if( result < 0 ) {
9685 gameResult = BlackWins;
9688 if( gameMode == TwoMachinesPlay ) {
9689 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9694 // [HGM] save: calculate checksum of game to make games easily identifiable
9695 int StringCheckSum(char *s)
9698 if(s==NULL) return 0;
9699 while(*s) i = i*259 + *s++;
9706 for(i=backwardMostMove; i<forwardMostMove; i++) {
9707 sum += pvInfoList[i].depth;
9708 sum += StringCheckSum(parseList[i]);
9709 sum += StringCheckSum(commentList[i]);
9712 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9713 return sum + StringCheckSum(commentList[i]);
9714 } // end of save patch
9717 GameEnds(result, resultDetails, whosays)
9719 char *resultDetails;
9722 GameMode nextGameMode;
9724 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9726 if(endingGame) return; /* [HGM] crash: forbid recursion */
9728 if(twoBoards) { // [HGM] dual: switch back to one board
9729 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9730 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9732 if (appData.debugMode) {
9733 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9734 result, resultDetails ? resultDetails : "(null)", whosays);
9737 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9739 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9740 /* If we are playing on ICS, the server decides when the
9741 game is over, but the engine can offer to draw, claim
9745 if (appData.zippyPlay && first.initDone) {
9746 if (result == GameIsDrawn) {
9747 /* In case draw still needs to be claimed */
9748 SendToICS(ics_prefix);
9749 SendToICS("draw\n");
9750 } else if (StrCaseStr(resultDetails, "resign")) {
9751 SendToICS(ics_prefix);
9752 SendToICS("resign\n");
9756 endingGame = 0; /* [HGM] crash */
9760 /* If we're loading the game from a file, stop */
9761 if (whosays == GE_FILE) {
9762 (void) StopLoadGameTimer();
9766 /* Cancel draw offers */
9767 first.offeredDraw = second.offeredDraw = 0;
9769 /* If this is an ICS game, only ICS can really say it's done;
9770 if not, anyone can. */
9771 isIcsGame = (gameMode == IcsPlayingWhite ||
9772 gameMode == IcsPlayingBlack ||
9773 gameMode == IcsObserving ||
9774 gameMode == IcsExamining);
9776 if (!isIcsGame || whosays == GE_ICS) {
9777 /* OK -- not an ICS game, or ICS said it was done */
9779 if (!isIcsGame && !appData.noChessProgram)
9780 SetUserThinkingEnables();
9782 /* [HGM] if a machine claims the game end we verify this claim */
9783 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9784 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9786 ChessMove trueResult = (ChessMove) -1;
9788 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9789 first.twoMachinesColor[0] :
9790 second.twoMachinesColor[0] ;
9792 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9793 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9794 /* [HGM] verify: engine mate claims accepted if they were flagged */
9795 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9797 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9798 /* [HGM] verify: engine mate claims accepted if they were flagged */
9799 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9801 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9802 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9805 // now verify win claims, but not in drop games, as we don't understand those yet
9806 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9807 || gameInfo.variant == VariantGreat) &&
9808 (result == WhiteWins && claimer == 'w' ||
9809 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9810 if (appData.debugMode) {
9811 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9812 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9814 if(result != trueResult) {
9815 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9816 result = claimer == 'w' ? BlackWins : WhiteWins;
9817 resultDetails = buf;
9820 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9821 && (forwardMostMove <= backwardMostMove ||
9822 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9823 (claimer=='b')==(forwardMostMove&1))
9825 /* [HGM] verify: draws that were not flagged are false claims */
9826 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9827 result = claimer == 'w' ? BlackWins : WhiteWins;
9828 resultDetails = buf;
9830 /* (Claiming a loss is accepted no questions asked!) */
9832 /* [HGM] bare: don't allow bare King to win */
9833 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9834 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9835 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9836 && result != GameIsDrawn)
9837 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9838 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9839 int p = (signed char)boards[forwardMostMove][i][j] - color;
9840 if(p >= 0 && p <= (int)WhiteKing) k++;
9842 if (appData.debugMode) {
9843 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9844 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9847 result = GameIsDrawn;
9848 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9849 resultDetails = buf;
9855 if(serverMoves != NULL && !loadFlag) { char c = '=';
9856 if(result==WhiteWins) c = '+';
9857 if(result==BlackWins) c = '-';
9858 if(resultDetails != NULL)
9859 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9861 if (resultDetails != NULL) {
9862 gameInfo.result = result;
9863 gameInfo.resultDetails = StrSave(resultDetails);
9865 /* display last move only if game was not loaded from file */
9866 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9867 DisplayMove(currentMove - 1);
9869 if (forwardMostMove != 0) {
9870 if (gameMode != PlayFromGameFile && gameMode != EditGame
9871 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9873 if (*appData.saveGameFile != NULLCHAR) {
9874 SaveGameToFile(appData.saveGameFile, TRUE);
9875 } else if (appData.autoSaveGames) {
9878 if (*appData.savePositionFile != NULLCHAR) {
9879 SavePositionToFile(appData.savePositionFile);
9884 /* Tell program how game ended in case it is learning */
9885 /* [HGM] Moved this to after saving the PGN, just in case */
9886 /* engine died and we got here through time loss. In that */
9887 /* case we will get a fatal error writing the pipe, which */
9888 /* would otherwise lose us the PGN. */
9889 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9890 /* output during GameEnds should never be fatal anymore */
9891 if (gameMode == MachinePlaysWhite ||
9892 gameMode == MachinePlaysBlack ||
9893 gameMode == TwoMachinesPlay ||
9894 gameMode == IcsPlayingWhite ||
9895 gameMode == IcsPlayingBlack ||
9896 gameMode == BeginningOfGame) {
9898 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9900 if (first.pr != NoProc) {
9901 SendToProgram(buf, &first);
9903 if (second.pr != NoProc &&
9904 gameMode == TwoMachinesPlay) {
9905 SendToProgram(buf, &second);
9910 if (appData.icsActive) {
9911 if (appData.quietPlay &&
9912 (gameMode == IcsPlayingWhite ||
9913 gameMode == IcsPlayingBlack)) {
9914 SendToICS(ics_prefix);
9915 SendToICS("set shout 1\n");
9917 nextGameMode = IcsIdle;
9918 ics_user_moved = FALSE;
9919 /* clean up premove. It's ugly when the game has ended and the
9920 * premove highlights are still on the board.
9924 ClearPremoveHighlights();
9925 DrawPosition(FALSE, boards[currentMove]);
9927 if (whosays == GE_ICS) {
9930 if (gameMode == IcsPlayingWhite)
9932 else if(gameMode == IcsPlayingBlack)
9936 if (gameMode == IcsPlayingBlack)
9938 else if(gameMode == IcsPlayingWhite)
9945 PlayIcsUnfinishedSound();
9948 } else if (gameMode == EditGame ||
9949 gameMode == PlayFromGameFile ||
9950 gameMode == AnalyzeMode ||
9951 gameMode == AnalyzeFile) {
9952 nextGameMode = gameMode;
9954 nextGameMode = EndOfGame;
9959 nextGameMode = gameMode;
9962 if (appData.noChessProgram) {
9963 gameMode = nextGameMode;
9965 endingGame = 0; /* [HGM] crash */
9970 /* Put first chess program into idle state */
9971 if (first.pr != NoProc &&
9972 (gameMode == MachinePlaysWhite ||
9973 gameMode == MachinePlaysBlack ||
9974 gameMode == TwoMachinesPlay ||
9975 gameMode == IcsPlayingWhite ||
9976 gameMode == IcsPlayingBlack ||
9977 gameMode == BeginningOfGame)) {
9978 SendToProgram("force\n", &first);
9979 if (first.usePing) {
9981 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9982 SendToProgram(buf, &first);
9985 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9986 /* Kill off first chess program */
9987 if (first.isr != NULL)
9988 RemoveInputSource(first.isr);
9991 if (first.pr != NoProc) {
9993 DoSleep( appData.delayBeforeQuit );
9994 SendToProgram("quit\n", &first);
9995 DoSleep( appData.delayAfterQuit );
9996 DestroyChildProcess(first.pr, first.useSigterm);
10000 if (second.reuse) {
10001 /* Put second chess program into idle state */
10002 if (second.pr != NoProc &&
10003 gameMode == TwoMachinesPlay) {
10004 SendToProgram("force\n", &second);
10005 if (second.usePing) {
10007 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10008 SendToProgram(buf, &second);
10011 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10012 /* Kill off second chess program */
10013 if (second.isr != NULL)
10014 RemoveInputSource(second.isr);
10017 if (second.pr != NoProc) {
10018 DoSleep( appData.delayBeforeQuit );
10019 SendToProgram("quit\n", &second);
10020 DoSleep( appData.delayAfterQuit );
10021 DestroyChildProcess(second.pr, second.useSigterm);
10023 second.pr = NoProc;
10026 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10027 char resChar = '=';
10031 if (first.twoMachinesColor[0] == 'w') {
10034 second.matchWins++;
10039 if (first.twoMachinesColor[0] == 'b') {
10042 second.matchWins++;
10045 case GameUnfinished:
10051 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10052 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10053 ReserveGame(nextGame, resChar); // sets nextGame
10054 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10055 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10057 if (nextGame <= appData.matchGames && !abortMatch) {
10058 gameMode = nextGameMode;
10059 matchGame = nextGame; // this will be overruled in tourney mode!
10060 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10061 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10062 endingGame = 0; /* [HGM] crash */
10065 gameMode = nextGameMode;
10066 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10067 first.tidy, second.tidy,
10068 first.matchWins, second.matchWins,
10069 appData.matchGames - (first.matchWins + second.matchWins));
10070 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10071 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10072 first.twoMachinesColor = "black\n";
10073 second.twoMachinesColor = "white\n";
10075 first.twoMachinesColor = "white\n";
10076 second.twoMachinesColor = "black\n";
10080 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10081 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10083 gameMode = nextGameMode;
10085 endingGame = 0; /* [HGM] crash */
10086 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10087 if(matchMode == TRUE) { // match through command line: exit with or without popup
10089 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10091 } else DisplayFatalError(buf, 0, 0);
10092 } else { // match through menu; just stop, with or without popup
10093 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10095 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10096 } else DisplayNote(buf);
10098 if(ranking) free(ranking);
10102 /* Assumes program was just initialized (initString sent).
10103 Leaves program in force mode. */
10105 FeedMovesToProgram(cps, upto)
10106 ChessProgramState *cps;
10111 if (appData.debugMode)
10112 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10113 startedFromSetupPosition ? "position and " : "",
10114 backwardMostMove, upto, cps->which);
10115 if(currentlyInitializedVariant != gameInfo.variant) {
10117 // [HGM] variantswitch: make engine aware of new variant
10118 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10119 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10120 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10121 SendToProgram(buf, cps);
10122 currentlyInitializedVariant = gameInfo.variant;
10124 SendToProgram("force\n", cps);
10125 if (startedFromSetupPosition) {
10126 SendBoard(cps, backwardMostMove);
10127 if (appData.debugMode) {
10128 fprintf(debugFP, "feedMoves\n");
10131 for (i = backwardMostMove; i < upto; i++) {
10132 SendMoveToProgram(i, cps);
10138 ResurrectChessProgram()
10140 /* The chess program may have exited.
10141 If so, restart it and feed it all the moves made so far. */
10142 static int doInit = 0;
10144 if (appData.noChessProgram) return 1;
10146 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10147 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10148 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10149 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10151 if (first.pr != NoProc) return 1;
10152 StartChessProgram(&first);
10154 InitChessProgram(&first, FALSE);
10155 FeedMovesToProgram(&first, currentMove);
10157 if (!first.sendTime) {
10158 /* can't tell gnuchess what its clock should read,
10159 so we bow to its notion. */
10161 timeRemaining[0][currentMove] = whiteTimeRemaining;
10162 timeRemaining[1][currentMove] = blackTimeRemaining;
10165 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10166 appData.icsEngineAnalyze) && first.analysisSupport) {
10167 SendToProgram("analyze\n", &first);
10168 first.analyzing = TRUE;
10174 * Button procedures
10177 Reset(redraw, init)
10182 if (appData.debugMode) {
10183 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10184 redraw, init, gameMode);
10186 CleanupTail(); // [HGM] vari: delete any stored variations
10187 pausing = pauseExamInvalid = FALSE;
10188 startedFromSetupPosition = blackPlaysFirst = FALSE;
10190 whiteFlag = blackFlag = FALSE;
10191 userOfferedDraw = FALSE;
10192 hintRequested = bookRequested = FALSE;
10193 first.maybeThinking = FALSE;
10194 second.maybeThinking = FALSE;
10195 first.bookSuspend = FALSE; // [HGM] book
10196 second.bookSuspend = FALSE;
10197 thinkOutput[0] = NULLCHAR;
10198 lastHint[0] = NULLCHAR;
10199 ClearGameInfo(&gameInfo);
10200 gameInfo.variant = StringToVariant(appData.variant);
10201 ics_user_moved = ics_clock_paused = FALSE;
10202 ics_getting_history = H_FALSE;
10204 white_holding[0] = black_holding[0] = NULLCHAR;
10205 ClearProgramStats();
10206 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10210 flipView = appData.flipView;
10211 ClearPremoveHighlights();
10212 gotPremove = FALSE;
10213 alarmSounded = FALSE;
10215 GameEnds(EndOfFile, NULL, GE_PLAYER);
10216 if(appData.serverMovesName != NULL) {
10217 /* [HGM] prepare to make moves file for broadcasting */
10218 clock_t t = clock();
10219 if(serverMoves != NULL) fclose(serverMoves);
10220 serverMoves = fopen(appData.serverMovesName, "r");
10221 if(serverMoves != NULL) {
10222 fclose(serverMoves);
10223 /* delay 15 sec before overwriting, so all clients can see end */
10224 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10226 serverMoves = fopen(appData.serverMovesName, "w");
10230 gameMode = BeginningOfGame;
10232 if(appData.icsActive) gameInfo.variant = VariantNormal;
10233 currentMove = forwardMostMove = backwardMostMove = 0;
10234 InitPosition(redraw);
10235 for (i = 0; i < MAX_MOVES; i++) {
10236 if (commentList[i] != NULL) {
10237 free(commentList[i]);
10238 commentList[i] = NULL;
10242 timeRemaining[0][0] = whiteTimeRemaining;
10243 timeRemaining[1][0] = blackTimeRemaining;
10245 if (first.pr == NULL) {
10246 StartChessProgram(&first);
10249 InitChessProgram(&first, startedFromSetupPosition);
10252 DisplayMessage("", "");
10253 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10254 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10261 if (!AutoPlayOneMove())
10263 if (matchMode || appData.timeDelay == 0)
10265 if (appData.timeDelay < 0)
10267 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10276 int fromX, fromY, toX, toY;
10278 if (appData.debugMode) {
10279 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10282 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10285 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10286 pvInfoList[currentMove].depth = programStats.depth;
10287 pvInfoList[currentMove].score = programStats.score;
10288 pvInfoList[currentMove].time = 0;
10289 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10292 if (currentMove >= forwardMostMove) {
10293 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10294 gameMode = EditGame;
10297 /* [AS] Clear current move marker at the end of a game */
10298 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10303 toX = moveList[currentMove][2] - AAA;
10304 toY = moveList[currentMove][3] - ONE;
10306 if (moveList[currentMove][1] == '@') {
10307 if (appData.highlightLastMove) {
10308 SetHighlights(-1, -1, toX, toY);
10311 fromX = moveList[currentMove][0] - AAA;
10312 fromY = moveList[currentMove][1] - ONE;
10314 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10316 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10318 if (appData.highlightLastMove) {
10319 SetHighlights(fromX, fromY, toX, toY);
10322 DisplayMove(currentMove);
10323 SendMoveToProgram(currentMove++, &first);
10324 DisplayBothClocks();
10325 DrawPosition(FALSE, boards[currentMove]);
10326 // [HGM] PV info: always display, routine tests if empty
10327 DisplayComment(currentMove - 1, commentList[currentMove]);
10333 LoadGameOneMove(readAhead)
10334 ChessMove readAhead;
10336 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10337 char promoChar = NULLCHAR;
10338 ChessMove moveType;
10339 char move[MSG_SIZ];
10342 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10343 gameMode != AnalyzeMode && gameMode != Training) {
10348 yyboardindex = forwardMostMove;
10349 if (readAhead != EndOfFile) {
10350 moveType = readAhead;
10352 if (gameFileFP == NULL)
10354 moveType = (ChessMove) Myylex();
10358 switch (moveType) {
10360 if (appData.debugMode)
10361 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10364 /* append the comment but don't display it */
10365 AppendComment(currentMove, p, FALSE);
10368 case WhiteCapturesEnPassant:
10369 case BlackCapturesEnPassant:
10370 case WhitePromotion:
10371 case BlackPromotion:
10372 case WhiteNonPromotion:
10373 case BlackNonPromotion:
10375 case WhiteKingSideCastle:
10376 case WhiteQueenSideCastle:
10377 case BlackKingSideCastle:
10378 case BlackQueenSideCastle:
10379 case WhiteKingSideCastleWild:
10380 case WhiteQueenSideCastleWild:
10381 case BlackKingSideCastleWild:
10382 case BlackQueenSideCastleWild:
10384 case WhiteHSideCastleFR:
10385 case WhiteASideCastleFR:
10386 case BlackHSideCastleFR:
10387 case BlackASideCastleFR:
10389 if (appData.debugMode)
10390 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10391 fromX = currentMoveString[0] - AAA;
10392 fromY = currentMoveString[1] - ONE;
10393 toX = currentMoveString[2] - AAA;
10394 toY = currentMoveString[3] - ONE;
10395 promoChar = currentMoveString[4];
10400 if (appData.debugMode)
10401 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10402 fromX = moveType == WhiteDrop ?
10403 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10404 (int) CharToPiece(ToLower(currentMoveString[0]));
10406 toX = currentMoveString[2] - AAA;
10407 toY = currentMoveString[3] - ONE;
10413 case GameUnfinished:
10414 if (appData.debugMode)
10415 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10416 p = strchr(yy_text, '{');
10417 if (p == NULL) p = strchr(yy_text, '(');
10420 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10422 q = strchr(p, *p == '{' ? '}' : ')');
10423 if (q != NULL) *q = NULLCHAR;
10426 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10427 GameEnds(moveType, p, GE_FILE);
10429 if (cmailMsgLoaded) {
10431 flipView = WhiteOnMove(currentMove);
10432 if (moveType == GameUnfinished) flipView = !flipView;
10433 if (appData.debugMode)
10434 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10439 if (appData.debugMode)
10440 fprintf(debugFP, "Parser hit end of file\n");
10441 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10447 if (WhiteOnMove(currentMove)) {
10448 GameEnds(BlackWins, "Black mates", GE_FILE);
10450 GameEnds(WhiteWins, "White mates", GE_FILE);
10454 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10460 case MoveNumberOne:
10461 if (lastLoadGameStart == GNUChessGame) {
10462 /* GNUChessGames have numbers, but they aren't move numbers */
10463 if (appData.debugMode)
10464 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10465 yy_text, (int) moveType);
10466 return LoadGameOneMove(EndOfFile); /* tail recursion */
10468 /* else fall thru */
10473 /* Reached start of next game in file */
10474 if (appData.debugMode)
10475 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10476 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10482 if (WhiteOnMove(currentMove)) {
10483 GameEnds(BlackWins, "Black mates", GE_FILE);
10485 GameEnds(WhiteWins, "White mates", GE_FILE);
10489 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10495 case PositionDiagram: /* should not happen; ignore */
10496 case ElapsedTime: /* ignore */
10497 case NAG: /* ignore */
10498 if (appData.debugMode)
10499 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10500 yy_text, (int) moveType);
10501 return LoadGameOneMove(EndOfFile); /* tail recursion */
10504 if (appData.testLegality) {
10505 if (appData.debugMode)
10506 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10507 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10508 (forwardMostMove / 2) + 1,
10509 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10510 DisplayError(move, 0);
10513 if (appData.debugMode)
10514 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10515 yy_text, currentMoveString);
10516 fromX = currentMoveString[0] - AAA;
10517 fromY = currentMoveString[1] - ONE;
10518 toX = currentMoveString[2] - AAA;
10519 toY = currentMoveString[3] - ONE;
10520 promoChar = currentMoveString[4];
10524 case AmbiguousMove:
10525 if (appData.debugMode)
10526 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10527 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10528 (forwardMostMove / 2) + 1,
10529 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10530 DisplayError(move, 0);
10535 case ImpossibleMove:
10536 if (appData.debugMode)
10537 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10538 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10539 (forwardMostMove / 2) + 1,
10540 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10541 DisplayError(move, 0);
10547 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10548 DrawPosition(FALSE, boards[currentMove]);
10549 DisplayBothClocks();
10550 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10551 DisplayComment(currentMove - 1, commentList[currentMove]);
10553 (void) StopLoadGameTimer();
10555 cmailOldMove = forwardMostMove;
10558 /* currentMoveString is set as a side-effect of yylex */
10560 thinkOutput[0] = NULLCHAR;
10561 MakeMove(fromX, fromY, toX, toY, promoChar);
10562 currentMove = forwardMostMove;
10567 /* Load the nth game from the given file */
10569 LoadGameFromFile(filename, n, title, useList)
10573 /*Boolean*/ int useList;
10578 if (strcmp(filename, "-") == 0) {
10582 f = fopen(filename, "rb");
10584 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10585 DisplayError(buf, errno);
10589 if (fseek(f, 0, 0) == -1) {
10590 /* f is not seekable; probably a pipe */
10593 if (useList && n == 0) {
10594 int error = GameListBuild(f);
10596 DisplayError(_("Cannot build game list"), error);
10597 } else if (!ListEmpty(&gameList) &&
10598 ((ListGame *) gameList.tailPred)->number > 1) {
10599 GameListPopUp(f, title);
10606 return LoadGame(f, n, title, FALSE);
10611 MakeRegisteredMove()
10613 int fromX, fromY, toX, toY;
10615 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10616 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10619 if (appData.debugMode)
10620 fprintf(debugFP, "Restoring %s for game %d\n",
10621 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10623 thinkOutput[0] = NULLCHAR;
10624 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10625 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10626 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10627 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10628 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10629 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10630 MakeMove(fromX, fromY, toX, toY, promoChar);
10631 ShowMove(fromX, fromY, toX, toY);
10633 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10640 if (WhiteOnMove(currentMove)) {
10641 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10643 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10648 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10655 if (WhiteOnMove(currentMove)) {
10656 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10658 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10663 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10674 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10676 CmailLoadGame(f, gameNumber, title, useList)
10684 if (gameNumber > nCmailGames) {
10685 DisplayError(_("No more games in this message"), 0);
10688 if (f == lastLoadGameFP) {
10689 int offset = gameNumber - lastLoadGameNumber;
10691 cmailMsg[0] = NULLCHAR;
10692 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10693 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10694 nCmailMovesRegistered--;
10696 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10697 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10698 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10701 if (! RegisterMove()) return FALSE;
10705 retVal = LoadGame(f, gameNumber, title, useList);
10707 /* Make move registered during previous look at this game, if any */
10708 MakeRegisteredMove();
10710 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10711 commentList[currentMove]
10712 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10713 DisplayComment(currentMove - 1, commentList[currentMove]);
10719 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10724 int gameNumber = lastLoadGameNumber + offset;
10725 if (lastLoadGameFP == NULL) {
10726 DisplayError(_("No game has been loaded yet"), 0);
10729 if (gameNumber <= 0) {
10730 DisplayError(_("Can't back up any further"), 0);
10733 if (cmailMsgLoaded) {
10734 return CmailLoadGame(lastLoadGameFP, gameNumber,
10735 lastLoadGameTitle, lastLoadGameUseList);
10737 return LoadGame(lastLoadGameFP, gameNumber,
10738 lastLoadGameTitle, lastLoadGameUseList);
10744 /* Load the nth game from open file f */
10746 LoadGame(f, gameNumber, title, useList)
10754 int gn = gameNumber;
10755 ListGame *lg = NULL;
10756 int numPGNTags = 0;
10758 GameMode oldGameMode;
10759 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10761 if (appData.debugMode)
10762 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10764 if (gameMode == Training )
10765 SetTrainingModeOff();
10767 oldGameMode = gameMode;
10768 if (gameMode != BeginningOfGame) {
10769 Reset(FALSE, TRUE);
10773 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10774 fclose(lastLoadGameFP);
10778 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10781 fseek(f, lg->offset, 0);
10782 GameListHighlight(gameNumber);
10786 DisplayError(_("Game number out of range"), 0);
10791 if (fseek(f, 0, 0) == -1) {
10792 if (f == lastLoadGameFP ?
10793 gameNumber == lastLoadGameNumber + 1 :
10797 DisplayError(_("Can't seek on game file"), 0);
10802 lastLoadGameFP = f;
10803 lastLoadGameNumber = gameNumber;
10804 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10805 lastLoadGameUseList = useList;
10809 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10810 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10811 lg->gameInfo.black);
10813 } else if (*title != NULLCHAR) {
10814 if (gameNumber > 1) {
10815 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10818 DisplayTitle(title);
10822 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10823 gameMode = PlayFromGameFile;
10827 currentMove = forwardMostMove = backwardMostMove = 0;
10828 CopyBoard(boards[0], initialPosition);
10832 * Skip the first gn-1 games in the file.
10833 * Also skip over anything that precedes an identifiable
10834 * start of game marker, to avoid being confused by
10835 * garbage at the start of the file. Currently
10836 * recognized start of game markers are the move number "1",
10837 * the pattern "gnuchess .* game", the pattern
10838 * "^[#;%] [^ ]* game file", and a PGN tag block.
10839 * A game that starts with one of the latter two patterns
10840 * will also have a move number 1, possibly
10841 * following a position diagram.
10842 * 5-4-02: Let's try being more lenient and allowing a game to
10843 * start with an unnumbered move. Does that break anything?
10845 cm = lastLoadGameStart = EndOfFile;
10847 yyboardindex = forwardMostMove;
10848 cm = (ChessMove) Myylex();
10851 if (cmailMsgLoaded) {
10852 nCmailGames = CMAIL_MAX_GAMES - gn;
10855 DisplayError(_("Game not found in file"), 0);
10862 lastLoadGameStart = cm;
10865 case MoveNumberOne:
10866 switch (lastLoadGameStart) {
10871 case MoveNumberOne:
10873 gn--; /* count this game */
10874 lastLoadGameStart = cm;
10883 switch (lastLoadGameStart) {
10886 case MoveNumberOne:
10888 gn--; /* count this game */
10889 lastLoadGameStart = cm;
10892 lastLoadGameStart = cm; /* game counted already */
10900 yyboardindex = forwardMostMove;
10901 cm = (ChessMove) Myylex();
10902 } while (cm == PGNTag || cm == Comment);
10909 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10910 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10911 != CMAIL_OLD_RESULT) {
10913 cmailResult[ CMAIL_MAX_GAMES
10914 - gn - 1] = CMAIL_OLD_RESULT;
10920 /* Only a NormalMove can be at the start of a game
10921 * without a position diagram. */
10922 if (lastLoadGameStart == EndOfFile ) {
10924 lastLoadGameStart = MoveNumberOne;
10933 if (appData.debugMode)
10934 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10936 if (cm == XBoardGame) {
10937 /* Skip any header junk before position diagram and/or move 1 */
10939 yyboardindex = forwardMostMove;
10940 cm = (ChessMove) Myylex();
10942 if (cm == EndOfFile ||
10943 cm == GNUChessGame || cm == XBoardGame) {
10944 /* Empty game; pretend end-of-file and handle later */
10949 if (cm == MoveNumberOne || cm == PositionDiagram ||
10950 cm == PGNTag || cm == Comment)
10953 } else if (cm == GNUChessGame) {
10954 if (gameInfo.event != NULL) {
10955 free(gameInfo.event);
10957 gameInfo.event = StrSave(yy_text);
10960 startedFromSetupPosition = FALSE;
10961 while (cm == PGNTag) {
10962 if (appData.debugMode)
10963 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10964 err = ParsePGNTag(yy_text, &gameInfo);
10965 if (!err) numPGNTags++;
10967 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10968 if(gameInfo.variant != oldVariant) {
10969 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10970 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10971 InitPosition(TRUE);
10972 oldVariant = gameInfo.variant;
10973 if (appData.debugMode)
10974 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10978 if (gameInfo.fen != NULL) {
10979 Board initial_position;
10980 startedFromSetupPosition = TRUE;
10981 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10983 DisplayError(_("Bad FEN position in file"), 0);
10986 CopyBoard(boards[0], initial_position);
10987 if (blackPlaysFirst) {
10988 currentMove = forwardMostMove = backwardMostMove = 1;
10989 CopyBoard(boards[1], initial_position);
10990 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10991 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10992 timeRemaining[0][1] = whiteTimeRemaining;
10993 timeRemaining[1][1] = blackTimeRemaining;
10994 if (commentList[0] != NULL) {
10995 commentList[1] = commentList[0];
10996 commentList[0] = NULL;
10999 currentMove = forwardMostMove = backwardMostMove = 0;
11001 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11003 initialRulePlies = FENrulePlies;
11004 for( i=0; i< nrCastlingRights; i++ )
11005 initialRights[i] = initial_position[CASTLING][i];
11007 yyboardindex = forwardMostMove;
11008 free(gameInfo.fen);
11009 gameInfo.fen = NULL;
11012 yyboardindex = forwardMostMove;
11013 cm = (ChessMove) Myylex();
11015 /* Handle comments interspersed among the tags */
11016 while (cm == Comment) {
11018 if (appData.debugMode)
11019 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11021 AppendComment(currentMove, p, FALSE);
11022 yyboardindex = forwardMostMove;
11023 cm = (ChessMove) Myylex();
11027 /* don't rely on existence of Event tag since if game was
11028 * pasted from clipboard the Event tag may not exist
11030 if (numPGNTags > 0){
11032 if (gameInfo.variant == VariantNormal) {
11033 VariantClass v = StringToVariant(gameInfo.event);
11034 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11035 if(v < VariantShogi) gameInfo.variant = v;
11038 if( appData.autoDisplayTags ) {
11039 tags = PGNTags(&gameInfo);
11040 TagsPopUp(tags, CmailMsg());
11045 /* Make something up, but don't display it now */
11050 if (cm == PositionDiagram) {
11053 Board initial_position;
11055 if (appData.debugMode)
11056 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11058 if (!startedFromSetupPosition) {
11060 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11061 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11072 initial_position[i][j++] = CharToPiece(*p);
11075 while (*p == ' ' || *p == '\t' ||
11076 *p == '\n' || *p == '\r') p++;
11078 if (strncmp(p, "black", strlen("black"))==0)
11079 blackPlaysFirst = TRUE;
11081 blackPlaysFirst = FALSE;
11082 startedFromSetupPosition = TRUE;
11084 CopyBoard(boards[0], initial_position);
11085 if (blackPlaysFirst) {
11086 currentMove = forwardMostMove = backwardMostMove = 1;
11087 CopyBoard(boards[1], initial_position);
11088 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11089 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11090 timeRemaining[0][1] = whiteTimeRemaining;
11091 timeRemaining[1][1] = blackTimeRemaining;
11092 if (commentList[0] != NULL) {
11093 commentList[1] = commentList[0];
11094 commentList[0] = NULL;
11097 currentMove = forwardMostMove = backwardMostMove = 0;
11100 yyboardindex = forwardMostMove;
11101 cm = (ChessMove) Myylex();
11104 if (first.pr == NoProc) {
11105 StartChessProgram(&first);
11107 InitChessProgram(&first, FALSE);
11108 SendToProgram("force\n", &first);
11109 if (startedFromSetupPosition) {
11110 SendBoard(&first, forwardMostMove);
11111 if (appData.debugMode) {
11112 fprintf(debugFP, "Load Game\n");
11114 DisplayBothClocks();
11117 /* [HGM] server: flag to write setup moves in broadcast file as one */
11118 loadFlag = appData.suppressLoadMoves;
11120 while (cm == Comment) {
11122 if (appData.debugMode)
11123 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11125 AppendComment(currentMove, p, FALSE);
11126 yyboardindex = forwardMostMove;
11127 cm = (ChessMove) Myylex();
11130 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11131 cm == WhiteWins || cm == BlackWins ||
11132 cm == GameIsDrawn || cm == GameUnfinished) {
11133 DisplayMessage("", _("No moves in game"));
11134 if (cmailMsgLoaded) {
11135 if (appData.debugMode)
11136 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11140 DrawPosition(FALSE, boards[currentMove]);
11141 DisplayBothClocks();
11142 gameMode = EditGame;
11149 // [HGM] PV info: routine tests if comment empty
11150 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11151 DisplayComment(currentMove - 1, commentList[currentMove]);
11153 if (!matchMode && appData.timeDelay != 0)
11154 DrawPosition(FALSE, boards[currentMove]);
11156 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11157 programStats.ok_to_send = 1;
11160 /* if the first token after the PGN tags is a move
11161 * and not move number 1, retrieve it from the parser
11163 if (cm != MoveNumberOne)
11164 LoadGameOneMove(cm);
11166 /* load the remaining moves from the file */
11167 while (LoadGameOneMove(EndOfFile)) {
11168 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11169 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11172 /* rewind to the start of the game */
11173 currentMove = backwardMostMove;
11175 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11177 if (oldGameMode == AnalyzeFile ||
11178 oldGameMode == AnalyzeMode) {
11179 AnalyzeFileEvent();
11182 if (matchMode || appData.timeDelay == 0) {
11184 gameMode = EditGame;
11186 } else if (appData.timeDelay > 0) {
11187 AutoPlayGameLoop();
11190 if (appData.debugMode)
11191 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11193 loadFlag = 0; /* [HGM] true game starts */
11197 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11199 ReloadPosition(offset)
11202 int positionNumber = lastLoadPositionNumber + offset;
11203 if (lastLoadPositionFP == NULL) {
11204 DisplayError(_("No position has been loaded yet"), 0);
11207 if (positionNumber <= 0) {
11208 DisplayError(_("Can't back up any further"), 0);
11211 return LoadPosition(lastLoadPositionFP, positionNumber,
11212 lastLoadPositionTitle);
11215 /* Load the nth position from the given file */
11217 LoadPositionFromFile(filename, n, title)
11225 if (strcmp(filename, "-") == 0) {
11226 return LoadPosition(stdin, n, "stdin");
11228 f = fopen(filename, "rb");
11230 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11231 DisplayError(buf, errno);
11234 return LoadPosition(f, n, title);
11239 /* Load the nth position from the given open file, and close it */
11241 LoadPosition(f, positionNumber, title)
11243 int positionNumber;
11246 char *p, line[MSG_SIZ];
11247 Board initial_position;
11248 int i, j, fenMode, pn;
11250 if (gameMode == Training )
11251 SetTrainingModeOff();
11253 if (gameMode != BeginningOfGame) {
11254 Reset(FALSE, TRUE);
11256 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11257 fclose(lastLoadPositionFP);
11259 if (positionNumber == 0) positionNumber = 1;
11260 lastLoadPositionFP = f;
11261 lastLoadPositionNumber = positionNumber;
11262 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11263 if (first.pr == NoProc) {
11264 StartChessProgram(&first);
11265 InitChessProgram(&first, FALSE);
11267 pn = positionNumber;
11268 if (positionNumber < 0) {
11269 /* Negative position number means to seek to that byte offset */
11270 if (fseek(f, -positionNumber, 0) == -1) {
11271 DisplayError(_("Can't seek on position file"), 0);
11276 if (fseek(f, 0, 0) == -1) {
11277 if (f == lastLoadPositionFP ?
11278 positionNumber == lastLoadPositionNumber + 1 :
11279 positionNumber == 1) {
11282 DisplayError(_("Can't seek on position file"), 0);
11287 /* See if this file is FEN or old-style xboard */
11288 if (fgets(line, MSG_SIZ, f) == NULL) {
11289 DisplayError(_("Position not found in file"), 0);
11292 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11293 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11296 if (fenMode || line[0] == '#') pn--;
11298 /* skip positions before number pn */
11299 if (fgets(line, MSG_SIZ, f) == NULL) {
11301 DisplayError(_("Position not found in file"), 0);
11304 if (fenMode || line[0] == '#') pn--;
11309 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11310 DisplayError(_("Bad FEN position in file"), 0);
11314 (void) fgets(line, MSG_SIZ, f);
11315 (void) fgets(line, MSG_SIZ, f);
11317 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11318 (void) fgets(line, MSG_SIZ, f);
11319 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11322 initial_position[i][j++] = CharToPiece(*p);
11326 blackPlaysFirst = FALSE;
11328 (void) fgets(line, MSG_SIZ, f);
11329 if (strncmp(line, "black", strlen("black"))==0)
11330 blackPlaysFirst = TRUE;
11333 startedFromSetupPosition = TRUE;
11335 SendToProgram("force\n", &first);
11336 CopyBoard(boards[0], initial_position);
11337 if (blackPlaysFirst) {
11338 currentMove = forwardMostMove = backwardMostMove = 1;
11339 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11340 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11341 CopyBoard(boards[1], initial_position);
11342 DisplayMessage("", _("Black to play"));
11344 currentMove = forwardMostMove = backwardMostMove = 0;
11345 DisplayMessage("", _("White to play"));
11347 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11348 SendBoard(&first, forwardMostMove);
11349 if (appData.debugMode) {
11351 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11352 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11353 fprintf(debugFP, "Load Position\n");
11356 if (positionNumber > 1) {
11357 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11358 DisplayTitle(line);
11360 DisplayTitle(title);
11362 gameMode = EditGame;
11365 timeRemaining[0][1] = whiteTimeRemaining;
11366 timeRemaining[1][1] = blackTimeRemaining;
11367 DrawPosition(FALSE, boards[currentMove]);
11374 CopyPlayerNameIntoFileName(dest, src)
11377 while (*src != NULLCHAR && *src != ',') {
11382 *(*dest)++ = *src++;
11387 char *DefaultFileName(ext)
11390 static char def[MSG_SIZ];
11393 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11395 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11397 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11399 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11406 /* Save the current game to the given file */
11408 SaveGameToFile(filename, append)
11416 if (strcmp(filename, "-") == 0) {
11417 return SaveGame(stdout, 0, NULL);
11419 f = fopen(filename, append ? "a" : "w");
11421 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11422 DisplayError(buf, errno);
11425 safeStrCpy(buf, lastMsg, MSG_SIZ);
11426 DisplayMessage(_("Waiting for access to save file"), "");
11427 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11428 DisplayMessage(_("Saving game"), "");
11429 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11430 result = SaveGame(f, 0, NULL);
11431 DisplayMessage(buf, "");
11441 static char buf[MSG_SIZ];
11444 p = strchr(str, ' ');
11445 if (p == NULL) return str;
11446 strncpy(buf, str, p - str);
11447 buf[p - str] = NULLCHAR;
11451 #define PGN_MAX_LINE 75
11453 #define PGN_SIDE_WHITE 0
11454 #define PGN_SIDE_BLACK 1
11457 static int FindFirstMoveOutOfBook( int side )
11461 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11462 int index = backwardMostMove;
11463 int has_book_hit = 0;
11465 if( (index % 2) != side ) {
11469 while( index < forwardMostMove ) {
11470 /* Check to see if engine is in book */
11471 int depth = pvInfoList[index].depth;
11472 int score = pvInfoList[index].score;
11478 else if( score == 0 && depth == 63 ) {
11479 in_book = 1; /* Zappa */
11481 else if( score == 2 && depth == 99 ) {
11482 in_book = 1; /* Abrok */
11485 has_book_hit += in_book;
11501 void GetOutOfBookInfo( char * buf )
11505 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11507 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11508 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11512 if( oob[0] >= 0 || oob[1] >= 0 ) {
11513 for( i=0; i<2; i++ ) {
11517 if( i > 0 && oob[0] >= 0 ) {
11518 strcat( buf, " " );
11521 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11522 sprintf( buf+strlen(buf), "%s%.2f",
11523 pvInfoList[idx].score >= 0 ? "+" : "",
11524 pvInfoList[idx].score / 100.0 );
11530 /* Save game in PGN style and close the file */
11535 int i, offset, linelen, newblock;
11539 int movelen, numlen, blank;
11540 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11542 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11544 tm = time((time_t *) NULL);
11546 PrintPGNTags(f, &gameInfo);
11548 if (backwardMostMove > 0 || startedFromSetupPosition) {
11549 char *fen = PositionToFEN(backwardMostMove, NULL);
11550 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11551 fprintf(f, "\n{--------------\n");
11552 PrintPosition(f, backwardMostMove);
11553 fprintf(f, "--------------}\n");
11557 /* [AS] Out of book annotation */
11558 if( appData.saveOutOfBookInfo ) {
11561 GetOutOfBookInfo( buf );
11563 if( buf[0] != '\0' ) {
11564 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11571 i = backwardMostMove;
11575 while (i < forwardMostMove) {
11576 /* Print comments preceding this move */
11577 if (commentList[i] != NULL) {
11578 if (linelen > 0) fprintf(f, "\n");
11579 fprintf(f, "%s", commentList[i]);
11584 /* Format move number */
11586 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11589 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11591 numtext[0] = NULLCHAR;
11593 numlen = strlen(numtext);
11596 /* Print move number */
11597 blank = linelen > 0 && numlen > 0;
11598 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11607 fprintf(f, "%s", numtext);
11611 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11612 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11615 blank = linelen > 0 && movelen > 0;
11616 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11625 fprintf(f, "%s", move_buffer);
11626 linelen += movelen;
11628 /* [AS] Add PV info if present */
11629 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11630 /* [HGM] add time */
11631 char buf[MSG_SIZ]; int seconds;
11633 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11639 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11642 seconds = (seconds + 4)/10; // round to full seconds
11644 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11646 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11649 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11650 pvInfoList[i].score >= 0 ? "+" : "",
11651 pvInfoList[i].score / 100.0,
11652 pvInfoList[i].depth,
11655 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11657 /* Print score/depth */
11658 blank = linelen > 0 && movelen > 0;
11659 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11668 fprintf(f, "%s", move_buffer);
11669 linelen += movelen;
11675 /* Start a new line */
11676 if (linelen > 0) fprintf(f, "\n");
11678 /* Print comments after last move */
11679 if (commentList[i] != NULL) {
11680 fprintf(f, "%s\n", commentList[i]);
11684 if (gameInfo.resultDetails != NULL &&
11685 gameInfo.resultDetails[0] != NULLCHAR) {
11686 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11687 PGNResult(gameInfo.result));
11689 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11693 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11697 /* Save game in old style and close the file */
11699 SaveGameOldStyle(f)
11705 tm = time((time_t *) NULL);
11707 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11710 if (backwardMostMove > 0 || startedFromSetupPosition) {
11711 fprintf(f, "\n[--------------\n");
11712 PrintPosition(f, backwardMostMove);
11713 fprintf(f, "--------------]\n");
11718 i = backwardMostMove;
11719 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11721 while (i < forwardMostMove) {
11722 if (commentList[i] != NULL) {
11723 fprintf(f, "[%s]\n", commentList[i]);
11726 if ((i % 2) == 1) {
11727 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11730 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11732 if (commentList[i] != NULL) {
11736 if (i >= forwardMostMove) {
11740 fprintf(f, "%s\n", parseList[i]);
11745 if (commentList[i] != NULL) {
11746 fprintf(f, "[%s]\n", commentList[i]);
11749 /* This isn't really the old style, but it's close enough */
11750 if (gameInfo.resultDetails != NULL &&
11751 gameInfo.resultDetails[0] != NULLCHAR) {
11752 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11753 gameInfo.resultDetails);
11755 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11762 /* Save the current game to open file f and close the file */
11764 SaveGame(f, dummy, dummy2)
11769 if (gameMode == EditPosition) EditPositionDone(TRUE);
11770 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11771 if (appData.oldSaveStyle)
11772 return SaveGameOldStyle(f);
11774 return SaveGamePGN(f);
11777 /* Save the current position to the given file */
11779 SavePositionToFile(filename)
11785 if (strcmp(filename, "-") == 0) {
11786 return SavePosition(stdout, 0, NULL);
11788 f = fopen(filename, "a");
11790 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11791 DisplayError(buf, errno);
11794 safeStrCpy(buf, lastMsg, MSG_SIZ);
11795 DisplayMessage(_("Waiting for access to save file"), "");
11796 flock(fileno(f), LOCK_EX); // [HGM] lock
11797 DisplayMessage(_("Saving position"), "");
11798 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11799 SavePosition(f, 0, NULL);
11800 DisplayMessage(buf, "");
11806 /* Save the current position to the given open file and close the file */
11808 SavePosition(f, dummy, dummy2)
11816 if (gameMode == EditPosition) EditPositionDone(TRUE);
11817 if (appData.oldSaveStyle) {
11818 tm = time((time_t *) NULL);
11820 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11822 fprintf(f, "[--------------\n");
11823 PrintPosition(f, currentMove);
11824 fprintf(f, "--------------]\n");
11826 fen = PositionToFEN(currentMove, NULL);
11827 fprintf(f, "%s\n", fen);
11835 ReloadCmailMsgEvent(unregister)
11839 static char *inFilename = NULL;
11840 static char *outFilename;
11842 struct stat inbuf, outbuf;
11845 /* Any registered moves are unregistered if unregister is set, */
11846 /* i.e. invoked by the signal handler */
11848 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11849 cmailMoveRegistered[i] = FALSE;
11850 if (cmailCommentList[i] != NULL) {
11851 free(cmailCommentList[i]);
11852 cmailCommentList[i] = NULL;
11855 nCmailMovesRegistered = 0;
11858 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11859 cmailResult[i] = CMAIL_NOT_RESULT;
11863 if (inFilename == NULL) {
11864 /* Because the filenames are static they only get malloced once */
11865 /* and they never get freed */
11866 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11867 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11869 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11870 sprintf(outFilename, "%s.out", appData.cmailGameName);
11873 status = stat(outFilename, &outbuf);
11875 cmailMailedMove = FALSE;
11877 status = stat(inFilename, &inbuf);
11878 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11881 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11882 counts the games, notes how each one terminated, etc.
11884 It would be nice to remove this kludge and instead gather all
11885 the information while building the game list. (And to keep it
11886 in the game list nodes instead of having a bunch of fixed-size
11887 parallel arrays.) Note this will require getting each game's
11888 termination from the PGN tags, as the game list builder does
11889 not process the game moves. --mann
11891 cmailMsgLoaded = TRUE;
11892 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11894 /* Load first game in the file or popup game menu */
11895 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11897 #endif /* !WIN32 */
11905 char string[MSG_SIZ];
11907 if ( cmailMailedMove
11908 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11909 return TRUE; /* Allow free viewing */
11912 /* Unregister move to ensure that we don't leave RegisterMove */
11913 /* with the move registered when the conditions for registering no */
11915 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11916 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11917 nCmailMovesRegistered --;
11919 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11921 free(cmailCommentList[lastLoadGameNumber - 1]);
11922 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11926 if (cmailOldMove == -1) {
11927 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11931 if (currentMove > cmailOldMove + 1) {
11932 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11936 if (currentMove < cmailOldMove) {
11937 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11941 if (forwardMostMove > currentMove) {
11942 /* Silently truncate extra moves */
11946 if ( (currentMove == cmailOldMove + 1)
11947 || ( (currentMove == cmailOldMove)
11948 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11949 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11950 if (gameInfo.result != GameUnfinished) {
11951 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11954 if (commentList[currentMove] != NULL) {
11955 cmailCommentList[lastLoadGameNumber - 1]
11956 = StrSave(commentList[currentMove]);
11958 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11960 if (appData.debugMode)
11961 fprintf(debugFP, "Saving %s for game %d\n",
11962 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11964 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11966 f = fopen(string, "w");
11967 if (appData.oldSaveStyle) {
11968 SaveGameOldStyle(f); /* also closes the file */
11970 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11971 f = fopen(string, "w");
11972 SavePosition(f, 0, NULL); /* also closes the file */
11974 fprintf(f, "{--------------\n");
11975 PrintPosition(f, currentMove);
11976 fprintf(f, "--------------}\n\n");
11978 SaveGame(f, 0, NULL); /* also closes the file*/
11981 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11982 nCmailMovesRegistered ++;
11983 } else if (nCmailGames == 1) {
11984 DisplayError(_("You have not made a move yet"), 0);
11995 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11996 FILE *commandOutput;
11997 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11998 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12004 if (! cmailMsgLoaded) {
12005 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12009 if (nCmailGames == nCmailResults) {
12010 DisplayError(_("No unfinished games"), 0);
12014 #if CMAIL_PROHIBIT_REMAIL
12015 if (cmailMailedMove) {
12016 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);
12017 DisplayError(msg, 0);
12022 if (! (cmailMailedMove || RegisterMove())) return;
12024 if ( cmailMailedMove
12025 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12026 snprintf(string, MSG_SIZ, partCommandString,
12027 appData.debugMode ? " -v" : "", appData.cmailGameName);
12028 commandOutput = popen(string, "r");
12030 if (commandOutput == NULL) {
12031 DisplayError(_("Failed to invoke cmail"), 0);
12033 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12034 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12036 if (nBuffers > 1) {
12037 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12038 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12039 nBytes = MSG_SIZ - 1;
12041 (void) memcpy(msg, buffer, nBytes);
12043 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12045 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12046 cmailMailedMove = TRUE; /* Prevent >1 moves */
12049 for (i = 0; i < nCmailGames; i ++) {
12050 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12055 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12057 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12059 appData.cmailGameName,
12061 LoadGameFromFile(buffer, 1, buffer, FALSE);
12062 cmailMsgLoaded = FALSE;
12066 DisplayInformation(msg);
12067 pclose(commandOutput);
12070 if ((*cmailMsg) != '\0') {
12071 DisplayInformation(cmailMsg);
12076 #endif /* !WIN32 */
12085 int prependComma = 0;
12087 char string[MSG_SIZ]; /* Space for game-list */
12090 if (!cmailMsgLoaded) return "";
12092 if (cmailMailedMove) {
12093 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12095 /* Create a list of games left */
12096 snprintf(string, MSG_SIZ, "[");
12097 for (i = 0; i < nCmailGames; i ++) {
12098 if (! ( cmailMoveRegistered[i]
12099 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12100 if (prependComma) {
12101 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12103 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12107 strcat(string, number);
12110 strcat(string, "]");
12112 if (nCmailMovesRegistered + nCmailResults == 0) {
12113 switch (nCmailGames) {
12115 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12119 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12123 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12128 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12130 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12135 if (nCmailResults == nCmailGames) {
12136 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12138 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12143 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12155 if (gameMode == Training)
12156 SetTrainingModeOff();
12159 cmailMsgLoaded = FALSE;
12160 if (appData.icsActive) {
12161 SendToICS(ics_prefix);
12162 SendToICS("refresh\n");
12172 /* Give up on clean exit */
12176 /* Keep trying for clean exit */
12180 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12182 if (telnetISR != NULL) {
12183 RemoveInputSource(telnetISR);
12185 if (icsPR != NoProc) {
12186 DestroyChildProcess(icsPR, TRUE);
12189 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12190 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12192 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12193 /* make sure this other one finishes before killing it! */
12194 if(endingGame) { int count = 0;
12195 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12196 while(endingGame && count++ < 10) DoSleep(1);
12197 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12200 /* Kill off chess programs */
12201 if (first.pr != NoProc) {
12204 DoSleep( appData.delayBeforeQuit );
12205 SendToProgram("quit\n", &first);
12206 DoSleep( appData.delayAfterQuit );
12207 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12209 if (second.pr != NoProc) {
12210 DoSleep( appData.delayBeforeQuit );
12211 SendToProgram("quit\n", &second);
12212 DoSleep( appData.delayAfterQuit );
12213 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12215 if (first.isr != NULL) {
12216 RemoveInputSource(first.isr);
12218 if (second.isr != NULL) {
12219 RemoveInputSource(second.isr);
12222 ShutDownFrontEnd();
12229 if (appData.debugMode)
12230 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12234 if (gameMode == MachinePlaysWhite ||
12235 gameMode == MachinePlaysBlack) {
12238 DisplayBothClocks();
12240 if (gameMode == PlayFromGameFile) {
12241 if (appData.timeDelay >= 0)
12242 AutoPlayGameLoop();
12243 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12244 Reset(FALSE, TRUE);
12245 SendToICS(ics_prefix);
12246 SendToICS("refresh\n");
12247 } else if (currentMove < forwardMostMove) {
12248 ForwardInner(forwardMostMove);
12250 pauseExamInvalid = FALSE;
12252 switch (gameMode) {
12256 pauseExamForwardMostMove = forwardMostMove;
12257 pauseExamInvalid = FALSE;
12260 case IcsPlayingWhite:
12261 case IcsPlayingBlack:
12265 case PlayFromGameFile:
12266 (void) StopLoadGameTimer();
12270 case BeginningOfGame:
12271 if (appData.icsActive) return;
12272 /* else fall through */
12273 case MachinePlaysWhite:
12274 case MachinePlaysBlack:
12275 case TwoMachinesPlay:
12276 if (forwardMostMove == 0)
12277 return; /* don't pause if no one has moved */
12278 if ((gameMode == MachinePlaysWhite &&
12279 !WhiteOnMove(forwardMostMove)) ||
12280 (gameMode == MachinePlaysBlack &&
12281 WhiteOnMove(forwardMostMove))) {
12294 char title[MSG_SIZ];
12296 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12297 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12299 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12300 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12301 parseList[currentMove - 1]);
12304 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12311 char *tags = PGNTags(&gameInfo);
12312 EditTagsPopUp(tags, NULL);
12319 if (appData.noChessProgram || gameMode == AnalyzeMode)
12322 if (gameMode != AnalyzeFile) {
12323 if (!appData.icsEngineAnalyze) {
12325 if (gameMode != EditGame) return;
12327 ResurrectChessProgram();
12328 SendToProgram("analyze\n", &first);
12329 first.analyzing = TRUE;
12330 /*first.maybeThinking = TRUE;*/
12331 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12332 EngineOutputPopUp();
12334 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12339 StartAnalysisClock();
12340 GetTimeMark(&lastNodeCountTime);
12347 if (appData.noChessProgram || gameMode == AnalyzeFile)
12350 if (gameMode != AnalyzeMode) {
12352 if (gameMode != EditGame) return;
12353 ResurrectChessProgram();
12354 SendToProgram("analyze\n", &first);
12355 first.analyzing = TRUE;
12356 /*first.maybeThinking = TRUE;*/
12357 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12358 EngineOutputPopUp();
12360 gameMode = AnalyzeFile;
12365 StartAnalysisClock();
12366 GetTimeMark(&lastNodeCountTime);
12371 MachineWhiteEvent()
12374 char *bookHit = NULL;
12376 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12380 if (gameMode == PlayFromGameFile ||
12381 gameMode == TwoMachinesPlay ||
12382 gameMode == Training ||
12383 gameMode == AnalyzeMode ||
12384 gameMode == EndOfGame)
12387 if (gameMode == EditPosition)
12388 EditPositionDone(TRUE);
12390 if (!WhiteOnMove(currentMove)) {
12391 DisplayError(_("It is not White's turn"), 0);
12395 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12398 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12399 gameMode == AnalyzeFile)
12402 ResurrectChessProgram(); /* in case it isn't running */
12403 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12404 gameMode = MachinePlaysWhite;
12407 gameMode = MachinePlaysWhite;
12411 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12413 if (first.sendName) {
12414 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12415 SendToProgram(buf, &first);
12417 if (first.sendTime) {
12418 if (first.useColors) {
12419 SendToProgram("black\n", &first); /*gnu kludge*/
12421 SendTimeRemaining(&first, TRUE);
12423 if (first.useColors) {
12424 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12426 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12427 SetMachineThinkingEnables();
12428 first.maybeThinking = TRUE;
12432 if (appData.autoFlipView && !flipView) {
12433 flipView = !flipView;
12434 DrawPosition(FALSE, NULL);
12435 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12438 if(bookHit) { // [HGM] book: simulate book reply
12439 static char bookMove[MSG_SIZ]; // a bit generous?
12441 programStats.nodes = programStats.depth = programStats.time =
12442 programStats.score = programStats.got_only_move = 0;
12443 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12445 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12446 strcat(bookMove, bookHit);
12447 HandleMachineMove(bookMove, &first);
12452 MachineBlackEvent()
12455 char *bookHit = NULL;
12457 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12461 if (gameMode == PlayFromGameFile ||
12462 gameMode == TwoMachinesPlay ||
12463 gameMode == Training ||
12464 gameMode == AnalyzeMode ||
12465 gameMode == EndOfGame)
12468 if (gameMode == EditPosition)
12469 EditPositionDone(TRUE);
12471 if (WhiteOnMove(currentMove)) {
12472 DisplayError(_("It is not Black's turn"), 0);
12476 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12479 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12480 gameMode == AnalyzeFile)
12483 ResurrectChessProgram(); /* in case it isn't running */
12484 gameMode = MachinePlaysBlack;
12488 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12490 if (first.sendName) {
12491 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12492 SendToProgram(buf, &first);
12494 if (first.sendTime) {
12495 if (first.useColors) {
12496 SendToProgram("white\n", &first); /*gnu kludge*/
12498 SendTimeRemaining(&first, FALSE);
12500 if (first.useColors) {
12501 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12503 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12504 SetMachineThinkingEnables();
12505 first.maybeThinking = TRUE;
12508 if (appData.autoFlipView && flipView) {
12509 flipView = !flipView;
12510 DrawPosition(FALSE, NULL);
12511 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12513 if(bookHit) { // [HGM] book: simulate book reply
12514 static char bookMove[MSG_SIZ]; // a bit generous?
12516 programStats.nodes = programStats.depth = programStats.time =
12517 programStats.score = programStats.got_only_move = 0;
12518 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12520 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12521 strcat(bookMove, bookHit);
12522 HandleMachineMove(bookMove, &first);
12528 DisplayTwoMachinesTitle()
12531 if (appData.matchGames > 0) {
12532 if (first.twoMachinesColor[0] == 'w') {
12533 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12534 gameInfo.white, gameInfo.black,
12535 first.matchWins, second.matchWins,
12536 matchGame - 1 - (first.matchWins + second.matchWins));
12538 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12539 gameInfo.white, gameInfo.black,
12540 second.matchWins, first.matchWins,
12541 matchGame - 1 - (first.matchWins + second.matchWins));
12544 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12550 SettingsMenuIfReady()
12552 if (second.lastPing != second.lastPong) {
12553 DisplayMessage("", _("Waiting for second chess program"));
12554 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12558 DisplayMessage("", "");
12559 SettingsPopUp(&second);
12563 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12566 if (cps->pr == NULL) {
12567 StartChessProgram(cps);
12568 if (cps->protocolVersion == 1) {
12571 /* kludge: allow timeout for initial "feature" command */
12573 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12574 DisplayMessage("", buf);
12575 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12583 TwoMachinesEvent P((void))
12587 ChessProgramState *onmove;
12588 char *bookHit = NULL;
12589 static int stalling = 0;
12593 if (appData.noChessProgram) return;
12595 switch (gameMode) {
12596 case TwoMachinesPlay:
12598 case MachinePlaysWhite:
12599 case MachinePlaysBlack:
12600 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12601 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12605 case BeginningOfGame:
12606 case PlayFromGameFile:
12609 if (gameMode != EditGame) return;
12612 EditPositionDone(TRUE);
12623 // forwardMostMove = currentMove;
12624 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12626 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12628 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12629 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12630 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12634 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12635 SendToProgram("force\n", &second);
12637 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12640 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12641 if(appData.matchPause>10000 || appData.matchPause<10)
12642 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12643 wait = SubtractTimeMarks(&now, &pauseStart);
12644 if(wait < appData.matchPause) {
12645 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12649 DisplayMessage("", "");
12650 if (startedFromSetupPosition) {
12651 SendBoard(&second, backwardMostMove);
12652 if (appData.debugMode) {
12653 fprintf(debugFP, "Two Machines\n");
12656 for (i = backwardMostMove; i < forwardMostMove; i++) {
12657 SendMoveToProgram(i, &second);
12660 gameMode = TwoMachinesPlay;
12664 DisplayTwoMachinesTitle();
12666 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12671 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12672 SendToProgram(first.computerString, &first);
12673 if (first.sendName) {
12674 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12675 SendToProgram(buf, &first);
12677 SendToProgram(second.computerString, &second);
12678 if (second.sendName) {
12679 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12680 SendToProgram(buf, &second);
12684 if (!first.sendTime || !second.sendTime) {
12685 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12686 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12688 if (onmove->sendTime) {
12689 if (onmove->useColors) {
12690 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12692 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12694 if (onmove->useColors) {
12695 SendToProgram(onmove->twoMachinesColor, onmove);
12697 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12698 // SendToProgram("go\n", onmove);
12699 onmove->maybeThinking = TRUE;
12700 SetMachineThinkingEnables();
12704 if(bookHit) { // [HGM] book: simulate book reply
12705 static char bookMove[MSG_SIZ]; // a bit generous?
12707 programStats.nodes = programStats.depth = programStats.time =
12708 programStats.score = programStats.got_only_move = 0;
12709 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12711 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12712 strcat(bookMove, bookHit);
12713 savedMessage = bookMove; // args for deferred call
12714 savedState = onmove;
12715 ScheduleDelayedEvent(DeferredBookMove, 1);
12722 if (gameMode == Training) {
12723 SetTrainingModeOff();
12724 gameMode = PlayFromGameFile;
12725 DisplayMessage("", _("Training mode off"));
12727 gameMode = Training;
12728 animateTraining = appData.animate;
12730 /* make sure we are not already at the end of the game */
12731 if (currentMove < forwardMostMove) {
12732 SetTrainingModeOn();
12733 DisplayMessage("", _("Training mode on"));
12735 gameMode = PlayFromGameFile;
12736 DisplayError(_("Already at end of game"), 0);
12745 if (!appData.icsActive) return;
12746 switch (gameMode) {
12747 case IcsPlayingWhite:
12748 case IcsPlayingBlack:
12751 case BeginningOfGame:
12759 EditPositionDone(TRUE);
12772 gameMode = IcsIdle;
12783 switch (gameMode) {
12785 SetTrainingModeOff();
12787 case MachinePlaysWhite:
12788 case MachinePlaysBlack:
12789 case BeginningOfGame:
12790 SendToProgram("force\n", &first);
12791 SetUserThinkingEnables();
12793 case PlayFromGameFile:
12794 (void) StopLoadGameTimer();
12795 if (gameFileFP != NULL) {
12800 EditPositionDone(TRUE);
12805 SendToProgram("force\n", &first);
12807 case TwoMachinesPlay:
12808 GameEnds(EndOfFile, NULL, GE_PLAYER);
12809 ResurrectChessProgram();
12810 SetUserThinkingEnables();
12813 ResurrectChessProgram();
12815 case IcsPlayingBlack:
12816 case IcsPlayingWhite:
12817 DisplayError(_("Warning: You are still playing a game"), 0);
12820 DisplayError(_("Warning: You are still observing a game"), 0);
12823 DisplayError(_("Warning: You are still examining a game"), 0);
12834 first.offeredDraw = second.offeredDraw = 0;
12836 if (gameMode == PlayFromGameFile) {
12837 whiteTimeRemaining = timeRemaining[0][currentMove];
12838 blackTimeRemaining = timeRemaining[1][currentMove];
12842 if (gameMode == MachinePlaysWhite ||
12843 gameMode == MachinePlaysBlack ||
12844 gameMode == TwoMachinesPlay ||
12845 gameMode == EndOfGame) {
12846 i = forwardMostMove;
12847 while (i > currentMove) {
12848 SendToProgram("undo\n", &first);
12851 whiteTimeRemaining = timeRemaining[0][currentMove];
12852 blackTimeRemaining = timeRemaining[1][currentMove];
12853 DisplayBothClocks();
12854 if (whiteFlag || blackFlag) {
12855 whiteFlag = blackFlag = 0;
12860 gameMode = EditGame;
12867 EditPositionEvent()
12869 if (gameMode == EditPosition) {
12875 if (gameMode != EditGame) return;
12877 gameMode = EditPosition;
12880 if (currentMove > 0)
12881 CopyBoard(boards[0], boards[currentMove]);
12883 blackPlaysFirst = !WhiteOnMove(currentMove);
12885 currentMove = forwardMostMove = backwardMostMove = 0;
12886 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12893 /* [DM] icsEngineAnalyze - possible call from other functions */
12894 if (appData.icsEngineAnalyze) {
12895 appData.icsEngineAnalyze = FALSE;
12897 DisplayMessage("",_("Close ICS engine analyze..."));
12899 if (first.analysisSupport && first.analyzing) {
12900 SendToProgram("exit\n", &first);
12901 first.analyzing = FALSE;
12903 thinkOutput[0] = NULLCHAR;
12907 EditPositionDone(Boolean fakeRights)
12909 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12911 startedFromSetupPosition = TRUE;
12912 InitChessProgram(&first, FALSE);
12913 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12914 boards[0][EP_STATUS] = EP_NONE;
12915 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12916 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12917 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12918 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12919 } else boards[0][CASTLING][2] = NoRights;
12920 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12921 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12922 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12923 } else boards[0][CASTLING][5] = NoRights;
12925 SendToProgram("force\n", &first);
12926 if (blackPlaysFirst) {
12927 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12928 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12929 currentMove = forwardMostMove = backwardMostMove = 1;
12930 CopyBoard(boards[1], boards[0]);
12932 currentMove = forwardMostMove = backwardMostMove = 0;
12934 SendBoard(&first, forwardMostMove);
12935 if (appData.debugMode) {
12936 fprintf(debugFP, "EditPosDone\n");
12939 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12940 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12941 gameMode = EditGame;
12943 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12944 ClearHighlights(); /* [AS] */
12947 /* Pause for `ms' milliseconds */
12948 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12958 } while (SubtractTimeMarks(&m2, &m1) < ms);
12961 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12963 SendMultiLineToICS(buf)
12966 char temp[MSG_SIZ+1], *p;
12973 strncpy(temp, buf, len);
12978 if (*p == '\n' || *p == '\r')
12983 strcat(temp, "\n");
12985 SendToPlayer(temp, strlen(temp));
12989 SetWhiteToPlayEvent()
12991 if (gameMode == EditPosition) {
12992 blackPlaysFirst = FALSE;
12993 DisplayBothClocks(); /* works because currentMove is 0 */
12994 } else if (gameMode == IcsExamining) {
12995 SendToICS(ics_prefix);
12996 SendToICS("tomove white\n");
13001 SetBlackToPlayEvent()
13003 if (gameMode == EditPosition) {
13004 blackPlaysFirst = TRUE;
13005 currentMove = 1; /* kludge */
13006 DisplayBothClocks();
13008 } else if (gameMode == IcsExamining) {
13009 SendToICS(ics_prefix);
13010 SendToICS("tomove black\n");
13015 EditPositionMenuEvent(selection, x, y)
13016 ChessSquare selection;
13020 ChessSquare piece = boards[0][y][x];
13022 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13024 switch (selection) {
13026 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13027 SendToICS(ics_prefix);
13028 SendToICS("bsetup clear\n");
13029 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13030 SendToICS(ics_prefix);
13031 SendToICS("clearboard\n");
13033 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13034 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13035 for (y = 0; y < BOARD_HEIGHT; y++) {
13036 if (gameMode == IcsExamining) {
13037 if (boards[currentMove][y][x] != EmptySquare) {
13038 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13043 boards[0][y][x] = p;
13048 if (gameMode == EditPosition) {
13049 DrawPosition(FALSE, boards[0]);
13054 SetWhiteToPlayEvent();
13058 SetBlackToPlayEvent();
13062 if (gameMode == IcsExamining) {
13063 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13064 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13067 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13068 if(x == BOARD_LEFT-2) {
13069 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13070 boards[0][y][1] = 0;
13072 if(x == BOARD_RGHT+1) {
13073 if(y >= gameInfo.holdingsSize) break;
13074 boards[0][y][BOARD_WIDTH-2] = 0;
13077 boards[0][y][x] = EmptySquare;
13078 DrawPosition(FALSE, boards[0]);
13083 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13084 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13085 selection = (ChessSquare) (PROMOTED piece);
13086 } else if(piece == EmptySquare) selection = WhiteSilver;
13087 else selection = (ChessSquare)((int)piece - 1);
13091 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13092 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13093 selection = (ChessSquare) (DEMOTED piece);
13094 } else if(piece == EmptySquare) selection = BlackSilver;
13095 else selection = (ChessSquare)((int)piece + 1);
13100 if(gameInfo.variant == VariantShatranj ||
13101 gameInfo.variant == VariantXiangqi ||
13102 gameInfo.variant == VariantCourier ||
13103 gameInfo.variant == VariantMakruk )
13104 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13109 if(gameInfo.variant == VariantXiangqi)
13110 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13111 if(gameInfo.variant == VariantKnightmate)
13112 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13115 if (gameMode == IcsExamining) {
13116 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13117 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13118 PieceToChar(selection), AAA + x, ONE + y);
13121 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13123 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13124 n = PieceToNumber(selection - BlackPawn);
13125 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13126 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13127 boards[0][BOARD_HEIGHT-1-n][1]++;
13129 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13130 n = PieceToNumber(selection);
13131 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13132 boards[0][n][BOARD_WIDTH-1] = selection;
13133 boards[0][n][BOARD_WIDTH-2]++;
13136 boards[0][y][x] = selection;
13137 DrawPosition(TRUE, boards[0]);
13145 DropMenuEvent(selection, x, y)
13146 ChessSquare selection;
13149 ChessMove moveType;
13151 switch (gameMode) {
13152 case IcsPlayingWhite:
13153 case MachinePlaysBlack:
13154 if (!WhiteOnMove(currentMove)) {
13155 DisplayMoveError(_("It is Black's turn"));
13158 moveType = WhiteDrop;
13160 case IcsPlayingBlack:
13161 case MachinePlaysWhite:
13162 if (WhiteOnMove(currentMove)) {
13163 DisplayMoveError(_("It is White's turn"));
13166 moveType = BlackDrop;
13169 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13175 if (moveType == BlackDrop && selection < BlackPawn) {
13176 selection = (ChessSquare) ((int) selection
13177 + (int) BlackPawn - (int) WhitePawn);
13179 if (boards[currentMove][y][x] != EmptySquare) {
13180 DisplayMoveError(_("That square is occupied"));
13184 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13190 /* Accept a pending offer of any kind from opponent */
13192 if (appData.icsActive) {
13193 SendToICS(ics_prefix);
13194 SendToICS("accept\n");
13195 } else if (cmailMsgLoaded) {
13196 if (currentMove == cmailOldMove &&
13197 commentList[cmailOldMove] != NULL &&
13198 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13199 "Black offers a draw" : "White offers a draw")) {
13201 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13202 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13204 DisplayError(_("There is no pending offer on this move"), 0);
13205 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13208 /* Not used for offers from chess program */
13215 /* Decline a pending offer of any kind from opponent */
13217 if (appData.icsActive) {
13218 SendToICS(ics_prefix);
13219 SendToICS("decline\n");
13220 } else if (cmailMsgLoaded) {
13221 if (currentMove == cmailOldMove &&
13222 commentList[cmailOldMove] != NULL &&
13223 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13224 "Black offers a draw" : "White offers a draw")) {
13226 AppendComment(cmailOldMove, "Draw declined", TRUE);
13227 DisplayComment(cmailOldMove - 1, "Draw declined");
13230 DisplayError(_("There is no pending offer on this move"), 0);
13233 /* Not used for offers from chess program */
13240 /* Issue ICS rematch command */
13241 if (appData.icsActive) {
13242 SendToICS(ics_prefix);
13243 SendToICS("rematch\n");
13250 /* Call your opponent's flag (claim a win on time) */
13251 if (appData.icsActive) {
13252 SendToICS(ics_prefix);
13253 SendToICS("flag\n");
13255 switch (gameMode) {
13258 case MachinePlaysWhite:
13261 GameEnds(GameIsDrawn, "Both players ran out of time",
13264 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13266 DisplayError(_("Your opponent is not out of time"), 0);
13269 case MachinePlaysBlack:
13272 GameEnds(GameIsDrawn, "Both players ran out of time",
13275 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13277 DisplayError(_("Your opponent is not out of time"), 0);
13285 ClockClick(int which)
13286 { // [HGM] code moved to back-end from winboard.c
13287 if(which) { // black clock
13288 if (gameMode == EditPosition || gameMode == IcsExamining) {
13289 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13290 SetBlackToPlayEvent();
13291 } else if (gameMode == EditGame || shiftKey) {
13292 AdjustClock(which, -1);
13293 } else if (gameMode == IcsPlayingWhite ||
13294 gameMode == MachinePlaysBlack) {
13297 } else { // white clock
13298 if (gameMode == EditPosition || gameMode == IcsExamining) {
13299 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13300 SetWhiteToPlayEvent();
13301 } else if (gameMode == EditGame || shiftKey) {
13302 AdjustClock(which, -1);
13303 } else if (gameMode == IcsPlayingBlack ||
13304 gameMode == MachinePlaysWhite) {
13313 /* Offer draw or accept pending draw offer from opponent */
13315 if (appData.icsActive) {
13316 /* Note: tournament rules require draw offers to be
13317 made after you make your move but before you punch
13318 your clock. Currently ICS doesn't let you do that;
13319 instead, you immediately punch your clock after making
13320 a move, but you can offer a draw at any time. */
13322 SendToICS(ics_prefix);
13323 SendToICS("draw\n");
13324 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13325 } else if (cmailMsgLoaded) {
13326 if (currentMove == cmailOldMove &&
13327 commentList[cmailOldMove] != NULL &&
13328 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13329 "Black offers a draw" : "White offers a draw")) {
13330 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13331 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13332 } else if (currentMove == cmailOldMove + 1) {
13333 char *offer = WhiteOnMove(cmailOldMove) ?
13334 "White offers a draw" : "Black offers a draw";
13335 AppendComment(currentMove, offer, TRUE);
13336 DisplayComment(currentMove - 1, offer);
13337 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13339 DisplayError(_("You must make your move before offering a draw"), 0);
13340 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13342 } else if (first.offeredDraw) {
13343 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13345 if (first.sendDrawOffers) {
13346 SendToProgram("draw\n", &first);
13347 userOfferedDraw = TRUE;
13355 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13357 if (appData.icsActive) {
13358 SendToICS(ics_prefix);
13359 SendToICS("adjourn\n");
13361 /* Currently GNU Chess doesn't offer or accept Adjourns */
13369 /* Offer Abort or accept pending Abort offer from opponent */
13371 if (appData.icsActive) {
13372 SendToICS(ics_prefix);
13373 SendToICS("abort\n");
13375 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13382 /* Resign. You can do this even if it's not your turn. */
13384 if (appData.icsActive) {
13385 SendToICS(ics_prefix);
13386 SendToICS("resign\n");
13388 switch (gameMode) {
13389 case MachinePlaysWhite:
13390 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13392 case MachinePlaysBlack:
13393 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13396 if (cmailMsgLoaded) {
13398 if (WhiteOnMove(cmailOldMove)) {
13399 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13401 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13403 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13414 StopObservingEvent()
13416 /* Stop observing current games */
13417 SendToICS(ics_prefix);
13418 SendToICS("unobserve\n");
13422 StopExaminingEvent()
13424 /* Stop observing current game */
13425 SendToICS(ics_prefix);
13426 SendToICS("unexamine\n");
13430 ForwardInner(target)
13435 if (appData.debugMode)
13436 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13437 target, currentMove, forwardMostMove);
13439 if (gameMode == EditPosition)
13442 if (gameMode == PlayFromGameFile && !pausing)
13445 if (gameMode == IcsExamining && pausing)
13446 limit = pauseExamForwardMostMove;
13448 limit = forwardMostMove;
13450 if (target > limit) target = limit;
13452 if (target > 0 && moveList[target - 1][0]) {
13453 int fromX, fromY, toX, toY;
13454 toX = moveList[target - 1][2] - AAA;
13455 toY = moveList[target - 1][3] - ONE;
13456 if (moveList[target - 1][1] == '@') {
13457 if (appData.highlightLastMove) {
13458 SetHighlights(-1, -1, toX, toY);
13461 fromX = moveList[target - 1][0] - AAA;
13462 fromY = moveList[target - 1][1] - ONE;
13463 if (target == currentMove + 1) {
13464 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13466 if (appData.highlightLastMove) {
13467 SetHighlights(fromX, fromY, toX, toY);
13471 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13472 gameMode == Training || gameMode == PlayFromGameFile ||
13473 gameMode == AnalyzeFile) {
13474 while (currentMove < target) {
13475 SendMoveToProgram(currentMove++, &first);
13478 currentMove = target;
13481 if (gameMode == EditGame || gameMode == EndOfGame) {
13482 whiteTimeRemaining = timeRemaining[0][currentMove];
13483 blackTimeRemaining = timeRemaining[1][currentMove];
13485 DisplayBothClocks();
13486 DisplayMove(currentMove - 1);
13487 DrawPosition(FALSE, boards[currentMove]);
13488 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13489 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13490 DisplayComment(currentMove - 1, commentList[currentMove]);
13498 if (gameMode == IcsExamining && !pausing) {
13499 SendToICS(ics_prefix);
13500 SendToICS("forward\n");
13502 ForwardInner(currentMove + 1);
13509 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13510 /* to optimze, we temporarily turn off analysis mode while we feed
13511 * the remaining moves to the engine. Otherwise we get analysis output
13514 if (first.analysisSupport) {
13515 SendToProgram("exit\nforce\n", &first);
13516 first.analyzing = FALSE;
13520 if (gameMode == IcsExamining && !pausing) {
13521 SendToICS(ics_prefix);
13522 SendToICS("forward 999999\n");
13524 ForwardInner(forwardMostMove);
13527 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13528 /* we have fed all the moves, so reactivate analysis mode */
13529 SendToProgram("analyze\n", &first);
13530 first.analyzing = TRUE;
13531 /*first.maybeThinking = TRUE;*/
13532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13537 BackwardInner(target)
13540 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13542 if (appData.debugMode)
13543 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13544 target, currentMove, forwardMostMove);
13546 if (gameMode == EditPosition) return;
13547 if (currentMove <= backwardMostMove) {
13549 DrawPosition(full_redraw, boards[currentMove]);
13552 if (gameMode == PlayFromGameFile && !pausing)
13555 if (moveList[target][0]) {
13556 int fromX, fromY, toX, toY;
13557 toX = moveList[target][2] - AAA;
13558 toY = moveList[target][3] - ONE;
13559 if (moveList[target][1] == '@') {
13560 if (appData.highlightLastMove) {
13561 SetHighlights(-1, -1, toX, toY);
13564 fromX = moveList[target][0] - AAA;
13565 fromY = moveList[target][1] - ONE;
13566 if (target == currentMove - 1) {
13567 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13569 if (appData.highlightLastMove) {
13570 SetHighlights(fromX, fromY, toX, toY);
13574 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13575 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13576 while (currentMove > target) {
13577 SendToProgram("undo\n", &first);
13581 currentMove = target;
13584 if (gameMode == EditGame || gameMode == EndOfGame) {
13585 whiteTimeRemaining = timeRemaining[0][currentMove];
13586 blackTimeRemaining = timeRemaining[1][currentMove];
13588 DisplayBothClocks();
13589 DisplayMove(currentMove - 1);
13590 DrawPosition(full_redraw, boards[currentMove]);
13591 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13592 // [HGM] PV info: routine tests if comment empty
13593 DisplayComment(currentMove - 1, commentList[currentMove]);
13599 if (gameMode == IcsExamining && !pausing) {
13600 SendToICS(ics_prefix);
13601 SendToICS("backward\n");
13603 BackwardInner(currentMove - 1);
13610 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13611 /* to optimize, we temporarily turn off analysis mode while we undo
13612 * all the moves. Otherwise we get analysis output after each undo.
13614 if (first.analysisSupport) {
13615 SendToProgram("exit\nforce\n", &first);
13616 first.analyzing = FALSE;
13620 if (gameMode == IcsExamining && !pausing) {
13621 SendToICS(ics_prefix);
13622 SendToICS("backward 999999\n");
13624 BackwardInner(backwardMostMove);
13627 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13628 /* we have fed all the moves, so reactivate analysis mode */
13629 SendToProgram("analyze\n", &first);
13630 first.analyzing = TRUE;
13631 /*first.maybeThinking = TRUE;*/
13632 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13639 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13640 if (to >= forwardMostMove) to = forwardMostMove;
13641 if (to <= backwardMostMove) to = backwardMostMove;
13642 if (to < currentMove) {
13650 RevertEvent(Boolean annotate)
13652 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13655 if (gameMode != IcsExamining) {
13656 DisplayError(_("You are not examining a game"), 0);
13660 DisplayError(_("You can't revert while pausing"), 0);
13663 SendToICS(ics_prefix);
13664 SendToICS("revert\n");
13670 switch (gameMode) {
13671 case MachinePlaysWhite:
13672 case MachinePlaysBlack:
13673 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13674 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13677 if (forwardMostMove < 2) return;
13678 currentMove = forwardMostMove = forwardMostMove - 2;
13679 whiteTimeRemaining = timeRemaining[0][currentMove];
13680 blackTimeRemaining = timeRemaining[1][currentMove];
13681 DisplayBothClocks();
13682 DisplayMove(currentMove - 1);
13683 ClearHighlights();/*!! could figure this out*/
13684 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13685 SendToProgram("remove\n", &first);
13686 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13689 case BeginningOfGame:
13693 case IcsPlayingWhite:
13694 case IcsPlayingBlack:
13695 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13696 SendToICS(ics_prefix);
13697 SendToICS("takeback 2\n");
13699 SendToICS(ics_prefix);
13700 SendToICS("takeback 1\n");
13709 ChessProgramState *cps;
13711 switch (gameMode) {
13712 case MachinePlaysWhite:
13713 if (!WhiteOnMove(forwardMostMove)) {
13714 DisplayError(_("It is your turn"), 0);
13719 case MachinePlaysBlack:
13720 if (WhiteOnMove(forwardMostMove)) {
13721 DisplayError(_("It is your turn"), 0);
13726 case TwoMachinesPlay:
13727 if (WhiteOnMove(forwardMostMove) ==
13728 (first.twoMachinesColor[0] == 'w')) {
13734 case BeginningOfGame:
13738 SendToProgram("?\n", cps);
13742 TruncateGameEvent()
13745 if (gameMode != EditGame) return;
13752 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13753 if (forwardMostMove > currentMove) {
13754 if (gameInfo.resultDetails != NULL) {
13755 free(gameInfo.resultDetails);
13756 gameInfo.resultDetails = NULL;
13757 gameInfo.result = GameUnfinished;
13759 forwardMostMove = currentMove;
13760 HistorySet(parseList, backwardMostMove, forwardMostMove,
13768 if (appData.noChessProgram) return;
13769 switch (gameMode) {
13770 case MachinePlaysWhite:
13771 if (WhiteOnMove(forwardMostMove)) {
13772 DisplayError(_("Wait until your turn"), 0);
13776 case BeginningOfGame:
13777 case MachinePlaysBlack:
13778 if (!WhiteOnMove(forwardMostMove)) {
13779 DisplayError(_("Wait until your turn"), 0);
13784 DisplayError(_("No hint available"), 0);
13787 SendToProgram("hint\n", &first);
13788 hintRequested = TRUE;
13794 if (appData.noChessProgram) return;
13795 switch (gameMode) {
13796 case MachinePlaysWhite:
13797 if (WhiteOnMove(forwardMostMove)) {
13798 DisplayError(_("Wait until your turn"), 0);
13802 case BeginningOfGame:
13803 case MachinePlaysBlack:
13804 if (!WhiteOnMove(forwardMostMove)) {
13805 DisplayError(_("Wait until your turn"), 0);
13810 EditPositionDone(TRUE);
13812 case TwoMachinesPlay:
13817 SendToProgram("bk\n", &first);
13818 bookOutput[0] = NULLCHAR;
13819 bookRequested = TRUE;
13825 char *tags = PGNTags(&gameInfo);
13826 TagsPopUp(tags, CmailMsg());
13830 /* end button procedures */
13833 PrintPosition(fp, move)
13839 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13840 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13841 char c = PieceToChar(boards[move][i][j]);
13842 fputc(c == 'x' ? '.' : c, fp);
13843 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13846 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13847 fprintf(fp, "white to play\n");
13849 fprintf(fp, "black to play\n");
13856 if (gameInfo.white != NULL) {
13857 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13863 /* Find last component of program's own name, using some heuristics */
13865 TidyProgramName(prog, host, buf)
13866 char *prog, *host, buf[MSG_SIZ];
13869 int local = (strcmp(host, "localhost") == 0);
13870 while (!local && (p = strchr(prog, ';')) != NULL) {
13872 while (*p == ' ') p++;
13875 if (*prog == '"' || *prog == '\'') {
13876 q = strchr(prog + 1, *prog);
13878 q = strchr(prog, ' ');
13880 if (q == NULL) q = prog + strlen(prog);
13882 while (p >= prog && *p != '/' && *p != '\\') p--;
13884 if(p == prog && *p == '"') p++;
13885 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13886 memcpy(buf, p, q - p);
13887 buf[q - p] = NULLCHAR;
13895 TimeControlTagValue()
13898 if (!appData.clockMode) {
13899 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13900 } else if (movesPerSession > 0) {
13901 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13902 } else if (timeIncrement == 0) {
13903 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13905 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13907 return StrSave(buf);
13913 /* This routine is used only for certain modes */
13914 VariantClass v = gameInfo.variant;
13915 ChessMove r = GameUnfinished;
13918 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13919 r = gameInfo.result;
13920 p = gameInfo.resultDetails;
13921 gameInfo.resultDetails = NULL;
13923 ClearGameInfo(&gameInfo);
13924 gameInfo.variant = v;
13926 switch (gameMode) {
13927 case MachinePlaysWhite:
13928 gameInfo.event = StrSave( appData.pgnEventHeader );
13929 gameInfo.site = StrSave(HostName());
13930 gameInfo.date = PGNDate();
13931 gameInfo.round = StrSave("-");
13932 gameInfo.white = StrSave(first.tidy);
13933 gameInfo.black = StrSave(UserName());
13934 gameInfo.timeControl = TimeControlTagValue();
13937 case MachinePlaysBlack:
13938 gameInfo.event = StrSave( appData.pgnEventHeader );
13939 gameInfo.site = StrSave(HostName());
13940 gameInfo.date = PGNDate();
13941 gameInfo.round = StrSave("-");
13942 gameInfo.white = StrSave(UserName());
13943 gameInfo.black = StrSave(first.tidy);
13944 gameInfo.timeControl = TimeControlTagValue();
13947 case TwoMachinesPlay:
13948 gameInfo.event = StrSave( appData.pgnEventHeader );
13949 gameInfo.site = StrSave(HostName());
13950 gameInfo.date = PGNDate();
13953 snprintf(buf, MSG_SIZ, "%d", roundNr);
13954 gameInfo.round = StrSave(buf);
13956 gameInfo.round = StrSave("-");
13958 if (first.twoMachinesColor[0] == 'w') {
13959 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13960 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13962 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13963 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13965 gameInfo.timeControl = TimeControlTagValue();
13969 gameInfo.event = StrSave("Edited game");
13970 gameInfo.site = StrSave(HostName());
13971 gameInfo.date = PGNDate();
13972 gameInfo.round = StrSave("-");
13973 gameInfo.white = StrSave("-");
13974 gameInfo.black = StrSave("-");
13975 gameInfo.result = r;
13976 gameInfo.resultDetails = p;
13980 gameInfo.event = StrSave("Edited position");
13981 gameInfo.site = StrSave(HostName());
13982 gameInfo.date = PGNDate();
13983 gameInfo.round = StrSave("-");
13984 gameInfo.white = StrSave("-");
13985 gameInfo.black = StrSave("-");
13988 case IcsPlayingWhite:
13989 case IcsPlayingBlack:
13994 case PlayFromGameFile:
13995 gameInfo.event = StrSave("Game from non-PGN file");
13996 gameInfo.site = StrSave(HostName());
13997 gameInfo.date = PGNDate();
13998 gameInfo.round = StrSave("-");
13999 gameInfo.white = StrSave("?");
14000 gameInfo.black = StrSave("?");
14009 ReplaceComment(index, text)
14017 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14018 pvInfoList[index-1].depth == len &&
14019 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14020 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14021 while (*text == '\n') text++;
14022 len = strlen(text);
14023 while (len > 0 && text[len - 1] == '\n') len--;
14025 if (commentList[index] != NULL)
14026 free(commentList[index]);
14029 commentList[index] = NULL;
14032 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14033 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14034 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14035 commentList[index] = (char *) malloc(len + 2);
14036 strncpy(commentList[index], text, len);
14037 commentList[index][len] = '\n';
14038 commentList[index][len + 1] = NULLCHAR;
14040 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14042 commentList[index] = (char *) malloc(len + 7);
14043 safeStrCpy(commentList[index], "{\n", 3);
14044 safeStrCpy(commentList[index]+2, text, len+1);
14045 commentList[index][len+2] = NULLCHAR;
14046 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14047 strcat(commentList[index], "\n}\n");
14061 if (ch == '\r') continue;
14063 } while (ch != '\0');
14067 AppendComment(index, text, addBraces)
14070 Boolean addBraces; // [HGM] braces: tells if we should add {}
14075 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14076 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14079 while (*text == '\n') text++;
14080 len = strlen(text);
14081 while (len > 0 && text[len - 1] == '\n') len--;
14083 if (len == 0) return;
14085 if (commentList[index] != NULL) {
14086 old = commentList[index];
14087 oldlen = strlen(old);
14088 while(commentList[index][oldlen-1] == '\n')
14089 commentList[index][--oldlen] = NULLCHAR;
14090 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14091 safeStrCpy(commentList[index], old, oldlen + len + 6);
14093 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14094 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14095 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14096 while (*text == '\n') { text++; len--; }
14097 commentList[index][--oldlen] = NULLCHAR;
14099 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14100 else strcat(commentList[index], "\n");
14101 strcat(commentList[index], text);
14102 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14103 else strcat(commentList[index], "\n");
14105 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14107 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14108 else commentList[index][0] = NULLCHAR;
14109 strcat(commentList[index], text);
14110 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14111 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14115 static char * FindStr( char * text, char * sub_text )
14117 char * result = strstr( text, sub_text );
14119 if( result != NULL ) {
14120 result += strlen( sub_text );
14126 /* [AS] Try to extract PV info from PGN comment */
14127 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14128 char *GetInfoFromComment( int index, char * text )
14130 char * sep = text, *p;
14132 if( text != NULL && index > 0 ) {
14135 int time = -1, sec = 0, deci;
14136 char * s_eval = FindStr( text, "[%eval " );
14137 char * s_emt = FindStr( text, "[%emt " );
14139 if( s_eval != NULL || s_emt != NULL ) {
14143 if( s_eval != NULL ) {
14144 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14148 if( delim != ']' ) {
14153 if( s_emt != NULL ) {
14158 /* We expect something like: [+|-]nnn.nn/dd */
14161 if(*text != '{') return text; // [HGM] braces: must be normal comment
14163 sep = strchr( text, '/' );
14164 if( sep == NULL || sep < (text+4) ) {
14169 if(p[1] == '(') { // comment starts with PV
14170 p = strchr(p, ')'); // locate end of PV
14171 if(p == NULL || sep < p+5) return text;
14172 // at this point we have something like "{(.*) +0.23/6 ..."
14173 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14174 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14175 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14177 time = -1; sec = -1; deci = -1;
14178 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14179 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14180 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14181 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14185 if( score_lo < 0 || score_lo >= 100 ) {
14189 if(sec >= 0) time = 600*time + 10*sec; else
14190 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14192 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14194 /* [HGM] PV time: now locate end of PV info */
14195 while( *++sep >= '0' && *sep <= '9'); // strip depth
14197 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14199 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14201 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14202 while(*sep == ' ') sep++;
14213 pvInfoList[index-1].depth = depth;
14214 pvInfoList[index-1].score = score;
14215 pvInfoList[index-1].time = 10*time; // centi-sec
14216 if(*sep == '}') *sep = 0; else *--sep = '{';
14217 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14223 SendToProgram(message, cps)
14225 ChessProgramState *cps;
14227 int count, outCount, error;
14230 if (cps->pr == NULL) return;
14233 if (appData.debugMode) {
14236 fprintf(debugFP, "%ld >%-6s: %s",
14237 SubtractTimeMarks(&now, &programStartTime),
14238 cps->which, message);
14241 count = strlen(message);
14242 outCount = OutputToProcess(cps->pr, message, count, &error);
14243 if (outCount < count && !exiting
14244 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14245 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14246 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14247 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14248 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14249 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14250 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14251 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14253 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14254 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14255 gameInfo.result = res;
14257 gameInfo.resultDetails = StrSave(buf);
14259 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14260 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14265 ReceiveFromProgram(isr, closure, message, count, error)
14266 InputSourceRef isr;
14274 ChessProgramState *cps = (ChessProgramState *)closure;
14276 if (isr != cps->isr) return; /* Killed intentionally */
14279 RemoveInputSource(cps->isr);
14280 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14281 _(cps->which), cps->program);
14282 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14283 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14284 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14285 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14286 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14288 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14289 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14290 gameInfo.result = res;
14292 gameInfo.resultDetails = StrSave(buf);
14294 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14295 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14297 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14298 _(cps->which), cps->program);
14299 RemoveInputSource(cps->isr);
14301 /* [AS] Program is misbehaving badly... kill it */
14302 if( count == -2 ) {
14303 DestroyChildProcess( cps->pr, 9 );
14307 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14312 if ((end_str = strchr(message, '\r')) != NULL)
14313 *end_str = NULLCHAR;
14314 if ((end_str = strchr(message, '\n')) != NULL)
14315 *end_str = NULLCHAR;
14317 if (appData.debugMode) {
14318 TimeMark now; int print = 1;
14319 char *quote = ""; char c; int i;
14321 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14322 char start = message[0];
14323 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14324 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14325 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14326 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14327 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14328 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14329 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14330 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14331 sscanf(message, "hint: %c", &c)!=1 &&
14332 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14333 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14334 print = (appData.engineComments >= 2);
14336 message[0] = start; // restore original message
14340 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14341 SubtractTimeMarks(&now, &programStartTime), cps->which,
14347 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14348 if (appData.icsEngineAnalyze) {
14349 if (strstr(message, "whisper") != NULL ||
14350 strstr(message, "kibitz") != NULL ||
14351 strstr(message, "tellics") != NULL) return;
14354 HandleMachineMove(message, cps);
14359 SendTimeControl(cps, mps, tc, inc, sd, st)
14360 ChessProgramState *cps;
14361 int mps, inc, sd, st;
14367 if( timeControl_2 > 0 ) {
14368 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14369 tc = timeControl_2;
14372 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14373 inc /= cps->timeOdds;
14374 st /= cps->timeOdds;
14376 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14379 /* Set exact time per move, normally using st command */
14380 if (cps->stKludge) {
14381 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14383 if (seconds == 0) {
14384 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14386 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14389 snprintf(buf, MSG_SIZ, "st %d\n", st);
14392 /* Set conventional or incremental time control, using level command */
14393 if (seconds == 0) {
14394 /* Note old gnuchess bug -- minutes:seconds used to not work.
14395 Fixed in later versions, but still avoid :seconds
14396 when seconds is 0. */
14397 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14399 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14400 seconds, inc/1000.);
14403 SendToProgram(buf, cps);
14405 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14406 /* Orthogonally, limit search to given depth */
14408 if (cps->sdKludge) {
14409 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14411 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14413 SendToProgram(buf, cps);
14416 if(cps->nps >= 0) { /* [HGM] nps */
14417 if(cps->supportsNPS == FALSE)
14418 cps->nps = -1; // don't use if engine explicitly says not supported!
14420 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14421 SendToProgram(buf, cps);
14426 ChessProgramState *WhitePlayer()
14427 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14429 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14430 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14436 SendTimeRemaining(cps, machineWhite)
14437 ChessProgramState *cps;
14438 int /*boolean*/ machineWhite;
14440 char message[MSG_SIZ];
14443 /* Note: this routine must be called when the clocks are stopped
14444 or when they have *just* been set or switched; otherwise
14445 it will be off by the time since the current tick started.
14447 if (machineWhite) {
14448 time = whiteTimeRemaining / 10;
14449 otime = blackTimeRemaining / 10;
14451 time = blackTimeRemaining / 10;
14452 otime = whiteTimeRemaining / 10;
14454 /* [HGM] translate opponent's time by time-odds factor */
14455 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14456 if (appData.debugMode) {
14457 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14460 if (time <= 0) time = 1;
14461 if (otime <= 0) otime = 1;
14463 snprintf(message, MSG_SIZ, "time %ld\n", time);
14464 SendToProgram(message, cps);
14466 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14467 SendToProgram(message, cps);
14471 BoolFeature(p, name, loc, cps)
14475 ChessProgramState *cps;
14478 int len = strlen(name);
14481 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14483 sscanf(*p, "%d", &val);
14485 while (**p && **p != ' ')
14487 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488 SendToProgram(buf, cps);
14495 IntFeature(p, name, loc, cps)
14499 ChessProgramState *cps;
14502 int len = strlen(name);
14503 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14505 sscanf(*p, "%d", loc);
14506 while (**p && **p != ' ') (*p)++;
14507 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14508 SendToProgram(buf, cps);
14515 StringFeature(p, name, loc, cps)
14519 ChessProgramState *cps;
14522 int len = strlen(name);
14523 if (strncmp((*p), name, len) == 0
14524 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14526 sscanf(*p, "%[^\"]", loc);
14527 while (**p && **p != '\"') (*p)++;
14528 if (**p == '\"') (*p)++;
14529 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14530 SendToProgram(buf, cps);
14537 ParseOption(Option *opt, ChessProgramState *cps)
14538 // [HGM] options: process the string that defines an engine option, and determine
14539 // name, type, default value, and allowed value range
14541 char *p, *q, buf[MSG_SIZ];
14542 int n, min = (-1)<<31, max = 1<<31, def;
14544 if(p = strstr(opt->name, " -spin ")) {
14545 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14546 if(max < min) max = min; // enforce consistency
14547 if(def < min) def = min;
14548 if(def > max) def = max;
14553 } else if((p = strstr(opt->name, " -slider "))) {
14554 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14555 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14556 if(max < min) max = min; // enforce consistency
14557 if(def < min) def = min;
14558 if(def > max) def = max;
14562 opt->type = Spin; // Slider;
14563 } else if((p = strstr(opt->name, " -string "))) {
14564 opt->textValue = p+9;
14565 opt->type = TextBox;
14566 } else if((p = strstr(opt->name, " -file "))) {
14567 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14568 opt->textValue = p+7;
14569 opt->type = FileName; // FileName;
14570 } else if((p = strstr(opt->name, " -path "))) {
14571 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14572 opt->textValue = p+7;
14573 opt->type = PathName; // PathName;
14574 } else if(p = strstr(opt->name, " -check ")) {
14575 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14576 opt->value = (def != 0);
14577 opt->type = CheckBox;
14578 } else if(p = strstr(opt->name, " -combo ")) {
14579 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14580 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14581 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14582 opt->value = n = 0;
14583 while(q = StrStr(q, " /// ")) {
14584 n++; *q = 0; // count choices, and null-terminate each of them
14586 if(*q == '*') { // remember default, which is marked with * prefix
14590 cps->comboList[cps->comboCnt++] = q;
14592 cps->comboList[cps->comboCnt++] = NULL;
14594 opt->type = ComboBox;
14595 } else if(p = strstr(opt->name, " -button")) {
14596 opt->type = Button;
14597 } else if(p = strstr(opt->name, " -save")) {
14598 opt->type = SaveButton;
14599 } else return FALSE;
14600 *p = 0; // terminate option name
14601 // now look if the command-line options define a setting for this engine option.
14602 if(cps->optionSettings && cps->optionSettings[0])
14603 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14604 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14605 snprintf(buf, MSG_SIZ, "option %s", p);
14606 if(p = strstr(buf, ",")) *p = 0;
14607 if(q = strchr(buf, '=')) switch(opt->type) {
14609 for(n=0; n<opt->max; n++)
14610 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14613 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14617 opt->value = atoi(q+1);
14622 SendToProgram(buf, cps);
14628 FeatureDone(cps, val)
14629 ChessProgramState* cps;
14632 DelayedEventCallback cb = GetDelayedEvent();
14633 if ((cb == InitBackEnd3 && cps == &first) ||
14634 (cb == SettingsMenuIfReady && cps == &second) ||
14635 (cb == LoadEngine) ||
14636 (cb == TwoMachinesEventIfReady)) {
14637 CancelDelayedEvent();
14638 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14640 cps->initDone = val;
14643 /* Parse feature command from engine */
14645 ParseFeatures(args, cps)
14647 ChessProgramState *cps;
14655 while (*p == ' ') p++;
14656 if (*p == NULLCHAR) return;
14658 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14659 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14660 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14661 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14662 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14663 if (BoolFeature(&p, "reuse", &val, cps)) {
14664 /* Engine can disable reuse, but can't enable it if user said no */
14665 if (!val) cps->reuse = FALSE;
14668 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14669 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14670 if (gameMode == TwoMachinesPlay) {
14671 DisplayTwoMachinesTitle();
14677 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14678 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14679 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14680 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14681 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14682 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14683 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14684 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14685 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14686 if (IntFeature(&p, "done", &val, cps)) {
14687 FeatureDone(cps, val);
14690 /* Added by Tord: */
14691 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14692 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14693 /* End of additions by Tord */
14695 /* [HGM] added features: */
14696 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14697 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14698 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14699 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14700 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14701 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14702 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14703 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14704 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14705 SendToProgram(buf, cps);
14708 if(cps->nrOptions >= MAX_OPTIONS) {
14710 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14711 DisplayError(buf, 0);
14715 /* End of additions by HGM */
14717 /* unknown feature: complain and skip */
14719 while (*q && *q != '=') q++;
14720 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14721 SendToProgram(buf, cps);
14727 while (*p && *p != '\"') p++;
14728 if (*p == '\"') p++;
14730 while (*p && *p != ' ') p++;
14738 PeriodicUpdatesEvent(newState)
14741 if (newState == appData.periodicUpdates)
14744 appData.periodicUpdates=newState;
14746 /* Display type changes, so update it now */
14747 // DisplayAnalysis();
14749 /* Get the ball rolling again... */
14751 AnalysisPeriodicEvent(1);
14752 StartAnalysisClock();
14757 PonderNextMoveEvent(newState)
14760 if (newState == appData.ponderNextMove) return;
14761 if (gameMode == EditPosition) EditPositionDone(TRUE);
14763 SendToProgram("hard\n", &first);
14764 if (gameMode == TwoMachinesPlay) {
14765 SendToProgram("hard\n", &second);
14768 SendToProgram("easy\n", &first);
14769 thinkOutput[0] = NULLCHAR;
14770 if (gameMode == TwoMachinesPlay) {
14771 SendToProgram("easy\n", &second);
14774 appData.ponderNextMove = newState;
14778 NewSettingEvent(option, feature, command, value)
14780 int option, value, *feature;
14784 if (gameMode == EditPosition) EditPositionDone(TRUE);
14785 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14786 if(feature == NULL || *feature) SendToProgram(buf, &first);
14787 if (gameMode == TwoMachinesPlay) {
14788 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14793 ShowThinkingEvent()
14794 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14796 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14797 int newState = appData.showThinking
14798 // [HGM] thinking: other features now need thinking output as well
14799 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14801 if (oldState == newState) return;
14802 oldState = newState;
14803 if (gameMode == EditPosition) EditPositionDone(TRUE);
14805 SendToProgram("post\n", &first);
14806 if (gameMode == TwoMachinesPlay) {
14807 SendToProgram("post\n", &second);
14810 SendToProgram("nopost\n", &first);
14811 thinkOutput[0] = NULLCHAR;
14812 if (gameMode == TwoMachinesPlay) {
14813 SendToProgram("nopost\n", &second);
14816 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14820 AskQuestionEvent(title, question, replyPrefix, which)
14821 char *title; char *question; char *replyPrefix; char *which;
14823 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14824 if (pr == NoProc) return;
14825 AskQuestion(title, question, replyPrefix, pr);
14829 TypeInEvent(char firstChar)
14831 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14832 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14833 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14834 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14835 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14836 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14837 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14838 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14839 gameMode == Training) PopUpMoveDialog(firstChar);
14843 TypeInDoneEvent(char *move)
14846 int n, fromX, fromY, toX, toY;
14848 ChessMove moveType;
\r
14851 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14852 EditPositionPasteFEN(move);
\r
14855 // [HGM] movenum: allow move number to be typed in any mode
\r
14856 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14857 ToNrEvent(2*n-1);
\r
14861 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14862 gameMode != Training) {
\r
14863 DisplayMoveError(_("Displayed move is not current"));
\r
14865 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14866 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14867 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14868 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14869 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14870 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14872 DisplayMoveError(_("Could not parse move"));
\r
14878 DisplayMove(moveNumber)
14881 char message[MSG_SIZ];
14883 char cpThinkOutput[MSG_SIZ];
14885 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14887 if (moveNumber == forwardMostMove - 1 ||
14888 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14890 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14892 if (strchr(cpThinkOutput, '\n')) {
14893 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14896 *cpThinkOutput = NULLCHAR;
14899 /* [AS] Hide thinking from human user */
14900 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14901 *cpThinkOutput = NULLCHAR;
14902 if( thinkOutput[0] != NULLCHAR ) {
14905 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14906 cpThinkOutput[i] = '.';
14908 cpThinkOutput[i] = NULLCHAR;
14909 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14913 if (moveNumber == forwardMostMove - 1 &&
14914 gameInfo.resultDetails != NULL) {
14915 if (gameInfo.resultDetails[0] == NULLCHAR) {
14916 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14918 snprintf(res, MSG_SIZ, " {%s} %s",
14919 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14925 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14926 DisplayMessage(res, cpThinkOutput);
14928 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14929 WhiteOnMove(moveNumber) ? " " : ".. ",
14930 parseList[moveNumber], res);
14931 DisplayMessage(message, cpThinkOutput);
14936 DisplayComment(moveNumber, text)
14940 char title[MSG_SIZ];
14941 char buf[8000]; // comment can be long!
14944 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14945 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14947 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14948 WhiteOnMove(moveNumber) ? " " : ".. ",
14949 parseList[moveNumber]);
14951 // [HGM] PV info: display PV info together with (or as) comment
14952 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14953 if(text == NULL) text = "";
14954 score = pvInfoList[moveNumber].score;
14955 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14956 depth, (pvInfoList[moveNumber].time+50)/100, text);
14959 if (text != NULL && (appData.autoDisplayComment || commentUp))
14960 CommentPopUp(title, text);
14963 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14964 * might be busy thinking or pondering. It can be omitted if your
14965 * gnuchess is configured to stop thinking immediately on any user
14966 * input. However, that gnuchess feature depends on the FIONREAD
14967 * ioctl, which does not work properly on some flavors of Unix.
14971 ChessProgramState *cps;
14974 if (!cps->useSigint) return;
14975 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14976 switch (gameMode) {
14977 case MachinePlaysWhite:
14978 case MachinePlaysBlack:
14979 case TwoMachinesPlay:
14980 case IcsPlayingWhite:
14981 case IcsPlayingBlack:
14984 /* Skip if we know it isn't thinking */
14985 if (!cps->maybeThinking) return;
14986 if (appData.debugMode)
14987 fprintf(debugFP, "Interrupting %s\n", cps->which);
14988 InterruptChildProcess(cps->pr);
14989 cps->maybeThinking = FALSE;
14994 #endif /*ATTENTION*/
15000 if (whiteTimeRemaining <= 0) {
15003 if (appData.icsActive) {
15004 if (appData.autoCallFlag &&
15005 gameMode == IcsPlayingBlack && !blackFlag) {
15006 SendToICS(ics_prefix);
15007 SendToICS("flag\n");
15011 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15013 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15014 if (appData.autoCallFlag) {
15015 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15022 if (blackTimeRemaining <= 0) {
15025 if (appData.icsActive) {
15026 if (appData.autoCallFlag &&
15027 gameMode == IcsPlayingWhite && !whiteFlag) {
15028 SendToICS(ics_prefix);
15029 SendToICS("flag\n");
15033 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15035 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15036 if (appData.autoCallFlag) {
15037 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15050 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15051 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15054 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15056 if ( !WhiteOnMove(forwardMostMove) ) {
15057 /* White made time control */
15058 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15059 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15060 /* [HGM] time odds: correct new time quota for time odds! */
15061 / WhitePlayer()->timeOdds;
15062 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15064 lastBlack -= blackTimeRemaining;
15065 /* Black made time control */
15066 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15067 / WhitePlayer()->other->timeOdds;
15068 lastWhite = whiteTimeRemaining;
15073 DisplayBothClocks()
15075 int wom = gameMode == EditPosition ?
15076 !blackPlaysFirst : WhiteOnMove(currentMove);
15077 DisplayWhiteClock(whiteTimeRemaining, wom);
15078 DisplayBlackClock(blackTimeRemaining, !wom);
15082 /* Timekeeping seems to be a portability nightmare. I think everyone
15083 has ftime(), but I'm really not sure, so I'm including some ifdefs
15084 to use other calls if you don't. Clocks will be less accurate if
15085 you have neither ftime nor gettimeofday.
15088 /* VS 2008 requires the #include outside of the function */
15089 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15090 #include <sys/timeb.h>
15093 /* Get the current time as a TimeMark */
15098 #if HAVE_GETTIMEOFDAY
15100 struct timeval timeVal;
15101 struct timezone timeZone;
15103 gettimeofday(&timeVal, &timeZone);
15104 tm->sec = (long) timeVal.tv_sec;
15105 tm->ms = (int) (timeVal.tv_usec / 1000L);
15107 #else /*!HAVE_GETTIMEOFDAY*/
15110 // include <sys/timeb.h> / moved to just above start of function
15111 struct timeb timeB;
15114 tm->sec = (long) timeB.time;
15115 tm->ms = (int) timeB.millitm;
15117 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15118 tm->sec = (long) time(NULL);
15124 /* Return the difference in milliseconds between two
15125 time marks. We assume the difference will fit in a long!
15128 SubtractTimeMarks(tm2, tm1)
15129 TimeMark *tm2, *tm1;
15131 return 1000L*(tm2->sec - tm1->sec) +
15132 (long) (tm2->ms - tm1->ms);
15137 * Code to manage the game clocks.
15139 * In tournament play, black starts the clock and then white makes a move.
15140 * We give the human user a slight advantage if he is playing white---the
15141 * clocks don't run until he makes his first move, so it takes zero time.
15142 * Also, we don't account for network lag, so we could get out of sync
15143 * with GNU Chess's clock -- but then, referees are always right.
15146 static TimeMark tickStartTM;
15147 static long intendedTickLength;
15150 NextTickLength(timeRemaining)
15151 long timeRemaining;
15153 long nominalTickLength, nextTickLength;
15155 if (timeRemaining > 0L && timeRemaining <= 10000L)
15156 nominalTickLength = 100L;
15158 nominalTickLength = 1000L;
15159 nextTickLength = timeRemaining % nominalTickLength;
15160 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15162 return nextTickLength;
15165 /* Adjust clock one minute up or down */
15167 AdjustClock(Boolean which, int dir)
15169 if(which) blackTimeRemaining += 60000*dir;
15170 else whiteTimeRemaining += 60000*dir;
15171 DisplayBothClocks();
15174 /* Stop clocks and reset to a fresh time control */
15178 (void) StopClockTimer();
15179 if (appData.icsActive) {
15180 whiteTimeRemaining = blackTimeRemaining = 0;
15181 } else if (searchTime) {
15182 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15183 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15184 } else { /* [HGM] correct new time quote for time odds */
15185 whiteTC = blackTC = fullTimeControlString;
15186 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15187 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15189 if (whiteFlag || blackFlag) {
15191 whiteFlag = blackFlag = FALSE;
15193 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15194 DisplayBothClocks();
15197 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15199 /* Decrement running clock by amount of time that has passed */
15203 long timeRemaining;
15204 long lastTickLength, fudge;
15207 if (!appData.clockMode) return;
15208 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15212 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15214 /* Fudge if we woke up a little too soon */
15215 fudge = intendedTickLength - lastTickLength;
15216 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15218 if (WhiteOnMove(forwardMostMove)) {
15219 if(whiteNPS >= 0) lastTickLength = 0;
15220 timeRemaining = whiteTimeRemaining -= lastTickLength;
15221 if(timeRemaining < 0 && !appData.icsActive) {
15222 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15223 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15224 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15225 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15228 DisplayWhiteClock(whiteTimeRemaining - fudge,
15229 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15231 if(blackNPS >= 0) lastTickLength = 0;
15232 timeRemaining = blackTimeRemaining -= lastTickLength;
15233 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15234 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15236 blackStartMove = forwardMostMove;
15237 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15240 DisplayBlackClock(blackTimeRemaining - fudge,
15241 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15243 if (CheckFlags()) return;
15246 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15247 StartClockTimer(intendedTickLength);
15249 /* if the time remaining has fallen below the alarm threshold, sound the
15250 * alarm. if the alarm has sounded and (due to a takeback or time control
15251 * with increment) the time remaining has increased to a level above the
15252 * threshold, reset the alarm so it can sound again.
15255 if (appData.icsActive && appData.icsAlarm) {
15257 /* make sure we are dealing with the user's clock */
15258 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15259 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15262 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15263 alarmSounded = FALSE;
15264 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15266 alarmSounded = TRUE;
15272 /* A player has just moved, so stop the previously running
15273 clock and (if in clock mode) start the other one.
15274 We redisplay both clocks in case we're in ICS mode, because
15275 ICS gives us an update to both clocks after every move.
15276 Note that this routine is called *after* forwardMostMove
15277 is updated, so the last fractional tick must be subtracted
15278 from the color that is *not* on move now.
15281 SwitchClocks(int newMoveNr)
15283 long lastTickLength;
15285 int flagged = FALSE;
15289 if (StopClockTimer() && appData.clockMode) {
15290 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15291 if (!WhiteOnMove(forwardMostMove)) {
15292 if(blackNPS >= 0) lastTickLength = 0;
15293 blackTimeRemaining -= lastTickLength;
15294 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15295 // if(pvInfoList[forwardMostMove].time == -1)
15296 pvInfoList[forwardMostMove].time = // use GUI time
15297 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15299 if(whiteNPS >= 0) lastTickLength = 0;
15300 whiteTimeRemaining -= lastTickLength;
15301 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15302 // if(pvInfoList[forwardMostMove].time == -1)
15303 pvInfoList[forwardMostMove].time =
15304 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15306 flagged = CheckFlags();
15308 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15309 CheckTimeControl();
15311 if (flagged || !appData.clockMode) return;
15313 switch (gameMode) {
15314 case MachinePlaysBlack:
15315 case MachinePlaysWhite:
15316 case BeginningOfGame:
15317 if (pausing) return;
15321 case PlayFromGameFile:
15329 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15330 if(WhiteOnMove(forwardMostMove))
15331 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15332 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15336 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15337 whiteTimeRemaining : blackTimeRemaining);
15338 StartClockTimer(intendedTickLength);
15342 /* Stop both clocks */
15346 long lastTickLength;
15349 if (!StopClockTimer()) return;
15350 if (!appData.clockMode) return;
15354 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15355 if (WhiteOnMove(forwardMostMove)) {
15356 if(whiteNPS >= 0) lastTickLength = 0;
15357 whiteTimeRemaining -= lastTickLength;
15358 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15360 if(blackNPS >= 0) lastTickLength = 0;
15361 blackTimeRemaining -= lastTickLength;
15362 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15367 /* Start clock of player on move. Time may have been reset, so
15368 if clock is already running, stop and restart it. */
15372 (void) StopClockTimer(); /* in case it was running already */
15373 DisplayBothClocks();
15374 if (CheckFlags()) return;
15376 if (!appData.clockMode) return;
15377 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15379 GetTimeMark(&tickStartTM);
15380 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15381 whiteTimeRemaining : blackTimeRemaining);
15383 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15384 whiteNPS = blackNPS = -1;
15385 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15386 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15387 whiteNPS = first.nps;
15388 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15389 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15390 blackNPS = first.nps;
15391 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15392 whiteNPS = second.nps;
15393 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15394 blackNPS = second.nps;
15395 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15397 StartClockTimer(intendedTickLength);
15404 long second, minute, hour, day;
15406 static char buf[32];
15408 if (ms > 0 && ms <= 9900) {
15409 /* convert milliseconds to tenths, rounding up */
15410 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15412 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15416 /* convert milliseconds to seconds, rounding up */
15417 /* use floating point to avoid strangeness of integer division
15418 with negative dividends on many machines */
15419 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15426 day = second / (60 * 60 * 24);
15427 second = second % (60 * 60 * 24);
15428 hour = second / (60 * 60);
15429 second = second % (60 * 60);
15430 minute = second / 60;
15431 second = second % 60;
15434 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15435 sign, day, hour, minute, second);
15437 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15439 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15446 * This is necessary because some C libraries aren't ANSI C compliant yet.
15449 StrStr(string, match)
15450 char *string, *match;
15454 length = strlen(match);
15456 for (i = strlen(string) - length; i >= 0; i--, string++)
15457 if (!strncmp(match, string, length))
15464 StrCaseStr(string, match)
15465 char *string, *match;
15469 length = strlen(match);
15471 for (i = strlen(string) - length; i >= 0; i--, string++) {
15472 for (j = 0; j < length; j++) {
15473 if (ToLower(match[j]) != ToLower(string[j]))
15476 if (j == length) return string;
15490 c1 = ToLower(*s1++);
15491 c2 = ToLower(*s2++);
15492 if (c1 > c2) return 1;
15493 if (c1 < c2) return -1;
15494 if (c1 == NULLCHAR) return 0;
15503 return isupper(c) ? tolower(c) : c;
15511 return islower(c) ? toupper(c) : c;
15513 #endif /* !_amigados */
15521 if ((ret = (char *) malloc(strlen(s) + 1)))
15523 safeStrCpy(ret, s, strlen(s)+1);
15529 StrSavePtr(s, savePtr)
15530 char *s, **savePtr;
15535 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15536 safeStrCpy(*savePtr, s, strlen(s)+1);
15548 clock = time((time_t *)NULL);
15549 tm = localtime(&clock);
15550 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15551 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15552 return StrSave(buf);
15557 PositionToFEN(move, overrideCastling)
15559 char *overrideCastling;
15561 int i, j, fromX, fromY, toX, toY;
15568 whiteToPlay = (gameMode == EditPosition) ?
15569 !blackPlaysFirst : (move % 2 == 0);
15572 /* Piece placement data */
15573 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15575 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15576 if (boards[move][i][j] == EmptySquare) {
15578 } else { ChessSquare piece = boards[move][i][j];
15579 if (emptycount > 0) {
15580 if(emptycount<10) /* [HGM] can be >= 10 */
15581 *p++ = '0' + emptycount;
15582 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15585 if(PieceToChar(piece) == '+') {
15586 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15588 piece = (ChessSquare)(DEMOTED piece);
15590 *p++ = PieceToChar(piece);
15592 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15593 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15598 if (emptycount > 0) {
15599 if(emptycount<10) /* [HGM] can be >= 10 */
15600 *p++ = '0' + emptycount;
15601 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15608 /* [HGM] print Crazyhouse or Shogi holdings */
15609 if( gameInfo.holdingsWidth ) {
15610 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15612 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15613 piece = boards[move][i][BOARD_WIDTH-1];
15614 if( piece != EmptySquare )
15615 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15616 *p++ = PieceToChar(piece);
15618 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15619 piece = boards[move][BOARD_HEIGHT-i-1][0];
15620 if( piece != EmptySquare )
15621 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15622 *p++ = PieceToChar(piece);
15625 if( q == p ) *p++ = '-';
15631 *p++ = whiteToPlay ? 'w' : 'b';
15634 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15635 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15637 if(nrCastlingRights) {
15639 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15640 /* [HGM] write directly from rights */
15641 if(boards[move][CASTLING][2] != NoRights &&
15642 boards[move][CASTLING][0] != NoRights )
15643 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15644 if(boards[move][CASTLING][2] != NoRights &&
15645 boards[move][CASTLING][1] != NoRights )
15646 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15647 if(boards[move][CASTLING][5] != NoRights &&
15648 boards[move][CASTLING][3] != NoRights )
15649 *p++ = boards[move][CASTLING][3] + AAA;
15650 if(boards[move][CASTLING][5] != NoRights &&
15651 boards[move][CASTLING][4] != NoRights )
15652 *p++ = boards[move][CASTLING][4] + AAA;
15655 /* [HGM] write true castling rights */
15656 if( nrCastlingRights == 6 ) {
15657 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15658 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15659 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15660 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15661 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15662 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15663 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15664 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15667 if (q == p) *p++ = '-'; /* No castling rights */
15671 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15672 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15673 /* En passant target square */
15674 if (move > backwardMostMove) {
15675 fromX = moveList[move - 1][0] - AAA;
15676 fromY = moveList[move - 1][1] - ONE;
15677 toX = moveList[move - 1][2] - AAA;
15678 toY = moveList[move - 1][3] - ONE;
15679 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15680 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15681 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15683 /* 2-square pawn move just happened */
15685 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15689 } else if(move == backwardMostMove) {
15690 // [HGM] perhaps we should always do it like this, and forget the above?
15691 if((signed char)boards[move][EP_STATUS] >= 0) {
15692 *p++ = boards[move][EP_STATUS] + AAA;
15693 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15704 /* [HGM] find reversible plies */
15705 { int i = 0, j=move;
15707 if (appData.debugMode) { int k;
15708 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15709 for(k=backwardMostMove; k<=forwardMostMove; k++)
15710 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15714 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15715 if( j == backwardMostMove ) i += initialRulePlies;
15716 sprintf(p, "%d ", i);
15717 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15719 /* Fullmove number */
15720 sprintf(p, "%d", (move / 2) + 1);
15722 return StrSave(buf);
15726 ParseFEN(board, blackPlaysFirst, fen)
15728 int *blackPlaysFirst;
15738 /* [HGM] by default clear Crazyhouse holdings, if present */
15739 if(gameInfo.holdingsWidth) {
15740 for(i=0; i<BOARD_HEIGHT; i++) {
15741 board[i][0] = EmptySquare; /* black holdings */
15742 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15743 board[i][1] = (ChessSquare) 0; /* black counts */
15744 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15748 /* Piece placement data */
15749 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15752 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15753 if (*p == '/') p++;
15754 emptycount = gameInfo.boardWidth - j;
15755 while (emptycount--)
15756 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15758 #if(BOARD_FILES >= 10)
15759 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15760 p++; emptycount=10;
15761 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15762 while (emptycount--)
15763 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15765 } else if (isdigit(*p)) {
15766 emptycount = *p++ - '0';
15767 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15768 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15769 while (emptycount--)
15770 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15771 } else if (*p == '+' || isalpha(*p)) {
15772 if (j >= gameInfo.boardWidth) return FALSE;
15774 piece = CharToPiece(*++p);
15775 if(piece == EmptySquare) return FALSE; /* unknown piece */
15776 piece = (ChessSquare) (PROMOTED piece ); p++;
15777 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15778 } else piece = CharToPiece(*p++);
15780 if(piece==EmptySquare) return FALSE; /* unknown piece */
15781 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15782 piece = (ChessSquare) (PROMOTED piece);
15783 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15786 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15792 while (*p == '/' || *p == ' ') p++;
15794 /* [HGM] look for Crazyhouse holdings here */
15795 while(*p==' ') p++;
15796 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15798 if(*p == '-' ) p++; /* empty holdings */ else {
15799 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15800 /* if we would allow FEN reading to set board size, we would */
15801 /* have to add holdings and shift the board read so far here */
15802 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15804 if((int) piece >= (int) BlackPawn ) {
15805 i = (int)piece - (int)BlackPawn;
15806 i = PieceToNumber((ChessSquare)i);
15807 if( i >= gameInfo.holdingsSize ) return FALSE;
15808 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15809 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15811 i = (int)piece - (int)WhitePawn;
15812 i = PieceToNumber((ChessSquare)i);
15813 if( i >= gameInfo.holdingsSize ) return FALSE;
15814 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15815 board[i][BOARD_WIDTH-2]++; /* black holdings */
15822 while(*p == ' ') p++;
15826 if(appData.colorNickNames) {
15827 if( c == appData.colorNickNames[0] ) c = 'w'; else
15828 if( c == appData.colorNickNames[1] ) c = 'b';
15832 *blackPlaysFirst = FALSE;
15835 *blackPlaysFirst = TRUE;
15841 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15842 /* return the extra info in global variiables */
15844 /* set defaults in case FEN is incomplete */
15845 board[EP_STATUS] = EP_UNKNOWN;
15846 for(i=0; i<nrCastlingRights; i++ ) {
15847 board[CASTLING][i] =
15848 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15849 } /* assume possible unless obviously impossible */
15850 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15851 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15852 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15853 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15854 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15855 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15856 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15857 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15860 while(*p==' ') p++;
15861 if(nrCastlingRights) {
15862 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15863 /* castling indicator present, so default becomes no castlings */
15864 for(i=0; i<nrCastlingRights; i++ ) {
15865 board[CASTLING][i] = NoRights;
15868 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15869 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15870 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15871 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15872 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15874 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15875 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15876 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15878 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15879 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15880 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15881 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15882 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15883 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15886 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15887 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15888 board[CASTLING][2] = whiteKingFile;
15891 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15892 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15893 board[CASTLING][2] = whiteKingFile;
15896 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15897 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15898 board[CASTLING][5] = blackKingFile;
15901 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15902 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15903 board[CASTLING][5] = blackKingFile;
15906 default: /* FRC castlings */
15907 if(c >= 'a') { /* black rights */
15908 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15909 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15910 if(i == BOARD_RGHT) break;
15911 board[CASTLING][5] = i;
15913 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15914 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15916 board[CASTLING][3] = c;
15918 board[CASTLING][4] = c;
15919 } else { /* white rights */
15920 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15921 if(board[0][i] == WhiteKing) break;
15922 if(i == BOARD_RGHT) break;
15923 board[CASTLING][2] = i;
15924 c -= AAA - 'a' + 'A';
15925 if(board[0][c] >= WhiteKing) break;
15927 board[CASTLING][0] = c;
15929 board[CASTLING][1] = c;
15933 for(i=0; i<nrCastlingRights; i++)
15934 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15935 if (appData.debugMode) {
15936 fprintf(debugFP, "FEN castling rights:");
15937 for(i=0; i<nrCastlingRights; i++)
15938 fprintf(debugFP, " %d", board[CASTLING][i]);
15939 fprintf(debugFP, "\n");
15942 while(*p==' ') p++;
15945 /* read e.p. field in games that know e.p. capture */
15946 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15947 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15949 p++; board[EP_STATUS] = EP_NONE;
15951 char c = *p++ - AAA;
15953 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15954 if(*p >= '0' && *p <='9') p++;
15955 board[EP_STATUS] = c;
15960 if(sscanf(p, "%d", &i) == 1) {
15961 FENrulePlies = i; /* 50-move ply counter */
15962 /* (The move number is still ignored) */
15969 EditPositionPasteFEN(char *fen)
15972 Board initial_position;
15974 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15975 DisplayError(_("Bad FEN position in clipboard"), 0);
15978 int savedBlackPlaysFirst = blackPlaysFirst;
15979 EditPositionEvent();
15980 blackPlaysFirst = savedBlackPlaysFirst;
15981 CopyBoard(boards[0], initial_position);
15982 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15983 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15984 DisplayBothClocks();
15985 DrawPosition(FALSE, boards[currentMove]);
15990 static char cseq[12] = "\\ ";
15992 Boolean set_cont_sequence(char *new_seq)
15997 // handle bad attempts to set the sequence
15999 return 0; // acceptable error - no debug
16001 len = strlen(new_seq);
16002 ret = (len > 0) && (len < sizeof(cseq));
16004 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16005 else if (appData.debugMode)
16006 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16011 reformat a source message so words don't cross the width boundary. internal
16012 newlines are not removed. returns the wrapped size (no null character unless
16013 included in source message). If dest is NULL, only calculate the size required
16014 for the dest buffer. lp argument indicats line position upon entry, and it's
16015 passed back upon exit.
16017 int wrap(char *dest, char *src, int count, int width, int *lp)
16019 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16021 cseq_len = strlen(cseq);
16022 old_line = line = *lp;
16023 ansi = len = clen = 0;
16025 for (i=0; i < count; i++)
16027 if (src[i] == '\033')
16030 // if we hit the width, back up
16031 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16033 // store i & len in case the word is too long
16034 old_i = i, old_len = len;
16036 // find the end of the last word
16037 while (i && src[i] != ' ' && src[i] != '\n')
16043 // word too long? restore i & len before splitting it
16044 if ((old_i-i+clen) >= width)
16051 if (i && src[i-1] == ' ')
16054 if (src[i] != ' ' && src[i] != '\n')
16061 // now append the newline and continuation sequence
16066 strncpy(dest+len, cseq, cseq_len);
16074 dest[len] = src[i];
16078 if (src[i] == '\n')
16083 if (dest && appData.debugMode)
16085 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16086 count, width, line, len, *lp);
16087 show_bytes(debugFP, src, count);
16088 fprintf(debugFP, "\ndest: ");
16089 show_bytes(debugFP, dest, len);
16090 fprintf(debugFP, "\n");
16092 *lp = dest ? line : old_line;
16097 // [HGM] vari: routines for shelving variations
16100 PushInner(int firstMove, int lastMove)
16102 int i, j, nrMoves = lastMove - firstMove;
16104 // push current tail of game on stack
16105 savedResult[storedGames] = gameInfo.result;
16106 savedDetails[storedGames] = gameInfo.resultDetails;
16107 gameInfo.resultDetails = NULL;
16108 savedFirst[storedGames] = firstMove;
16109 savedLast [storedGames] = lastMove;
16110 savedFramePtr[storedGames] = framePtr;
16111 framePtr -= nrMoves; // reserve space for the boards
16112 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16113 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16114 for(j=0; j<MOVE_LEN; j++)
16115 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16116 for(j=0; j<2*MOVE_LEN; j++)
16117 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16118 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16119 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16120 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16121 pvInfoList[firstMove+i-1].depth = 0;
16122 commentList[framePtr+i] = commentList[firstMove+i];
16123 commentList[firstMove+i] = NULL;
16127 forwardMostMove = firstMove; // truncate game so we can start variation
16131 PushTail(int firstMove, int lastMove)
16133 if(appData.icsActive) { // only in local mode
16134 forwardMostMove = currentMove; // mimic old ICS behavior
16137 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16139 PushInner(firstMove, lastMove);
16140 if(storedGames == 1) GreyRevert(FALSE);
16144 PopInner(Boolean annotate)
16147 char buf[8000], moveBuf[20];
16150 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16151 nrMoves = savedLast[storedGames] - currentMove;
16154 if(!WhiteOnMove(currentMove))
16155 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16156 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16157 for(i=currentMove; i<forwardMostMove; i++) {
16159 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16160 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16161 strcat(buf, moveBuf);
16162 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16163 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16167 for(i=1; i<=nrMoves; i++) { // copy last variation back
16168 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16169 for(j=0; j<MOVE_LEN; j++)
16170 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16171 for(j=0; j<2*MOVE_LEN; j++)
16172 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16173 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16174 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16175 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16176 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16177 commentList[currentMove+i] = commentList[framePtr+i];
16178 commentList[framePtr+i] = NULL;
16180 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16181 framePtr = savedFramePtr[storedGames];
16182 gameInfo.result = savedResult[storedGames];
16183 if(gameInfo.resultDetails != NULL) {
16184 free(gameInfo.resultDetails);
16186 gameInfo.resultDetails = savedDetails[storedGames];
16187 forwardMostMove = currentMove + nrMoves;
16191 PopTail(Boolean annotate)
16193 if(appData.icsActive) return FALSE; // only in local mode
16194 if(!storedGames) return FALSE; // sanity
16195 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16197 PopInner(annotate);
16199 if(storedGames == 0) GreyRevert(TRUE);
16205 { // remove all shelved variations
16207 for(i=0; i<storedGames; i++) {
16208 if(savedDetails[i])
16209 free(savedDetails[i]);
16210 savedDetails[i] = NULL;
16212 for(i=framePtr; i<MAX_MOVES; i++) {
16213 if(commentList[i]) free(commentList[i]);
16214 commentList[i] = NULL;
16216 framePtr = MAX_MOVES-1;
16221 LoadVariation(int index, char *text)
16222 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16223 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16224 int level = 0, move;
16226 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16227 // first find outermost bracketing variation
16228 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16229 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16230 if(*p == '{') wait = '}'; else
16231 if(*p == '[') wait = ']'; else
16232 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16233 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16235 if(*p == wait) wait = NULLCHAR; // closing ]} found
16238 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16239 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16240 end[1] = NULLCHAR; // clip off comment beyond variation
16241 ToNrEvent(currentMove-1);
16242 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16243 // kludge: use ParsePV() to append variation to game
16244 move = currentMove;
16245 ParsePV(start, TRUE);
16246 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16247 ClearPremoveHighlights();
16249 ToNrEvent(currentMove+1);