2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard,
\r
6 * Massachusetts. Enhancements Copyright
\r
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
\r
10 * The following terms apply to Digital Equipment Corporation's copyright
\r
11 * interest in XBoard:
\r
12 * ------------------------------------------------------------------------
\r
13 * All Rights Reserved
\r
15 * Permission to use, copy, modify, and distribute this software and its
\r
16 * documentation for any purpose and without fee is hereby granted,
\r
17 * provided that the above copyright notice appear in all copies and that
\r
18 * both that copyright notice and this permission notice appear in
\r
19 * supporting documentation, and that the name of Digital not be
\r
20 * used in advertising or publicity pertaining to distribution of the
\r
21 * software without specific, written prior permission.
\r
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
30 * ------------------------------------------------------------------------
\r
32 * The following terms apply to the enhanced version of XBoard
\r
33 * distributed by the Free Software Foundation:
\r
34 * ------------------------------------------------------------------------
\r
36 * GNU XBoard is free software: you can redistribute it and/or modify
\r
37 * it under the terms of the GNU General Public License as published by
\r
38 * the Free Software Foundation, either version 3 of the License, or (at
\r
39 * your option) any later version.
\r
41 * GNU XBoard is distributed in the hope that it will be useful, but
\r
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
\r
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
44 * General Public License for more details.
\r
46 * You should have received a copy of the GNU General Public License
\r
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
\r
49 *------------------------------------------------------------------------
\r
50 ** See the file ChangeLog for a revision history. */
\r
52 /* [AS] Also useful here for debugging */
\r
54 #include <windows.h>
\r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
70 #include <sys/types.h>
\r
71 #include <sys/stat.h>
\r
76 # include <stdlib.h>
\r
77 # include <string.h>
\r
78 #else /* not STDC_HEADERS */
\r
80 # include <string.h>
\r
81 # else /* not HAVE_STRING_H */
\r
82 # include <strings.h>
\r
83 # endif /* not HAVE_STRING_H */
\r
84 #endif /* not STDC_HEADERS */
\r
86 #if HAVE_SYS_FCNTL_H
\r
87 # include <sys/fcntl.h>
\r
88 #else /* not HAVE_SYS_FCNTL_H */
\r
91 # endif /* HAVE_FCNTL_H */
\r
92 #endif /* not HAVE_SYS_FCNTL_H */
\r
94 #if TIME_WITH_SYS_TIME
\r
95 # include <sys/time.h>
\r
98 # if HAVE_SYS_TIME_H
\r
99 # include <sys/time.h>
\r
105 #if defined(_amigados) && !defined(__GNUC__)
\r
107 int tz_minuteswest;
\r
110 extern int gettimeofday(struct timeval *, struct timezone *);
\r
114 # include <unistd.h>
\r
117 #include "common.h"
\r
118 #include "frontend.h"
\r
119 #include "backend.h"
\r
120 #include "parser.h"
\r
123 # include "zippy.h"
\r
125 #include "backendz.h"
\r
126 #include "gettext.h"
\r
129 # define _(s) gettext (s)
\r
130 # define N_(s) gettext_noop (s)
\r
137 /* A point in time */
\r
139 long sec; /* Assuming this is >= 32 bits */
\r
140 int ms; /* Assuming this is >= 16 bits */
\r
143 int establish P((void));
\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
145 char *buf, int count, int error));
\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
147 char *buf, int count, int error));
\r
148 void SendToICS P((char *s));
\r
149 void SendToICSDelayed P((char *s, long msdelay));
\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
151 int toX, int toY));
\r
152 void InitPosition P((int redraw));
\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
154 int AutoPlayOneMove P((void));
\r
155 int LoadGameOneMove P((ChessMove readAhead));
\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
158 int SavePositionToFile P((char *filename));
\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
164 /*char*/int promoChar));
\r
165 void BackwardInner P((int target));
\r
166 void ForwardInner P((int target));
\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
168 void EditPositionDone P((void));
\r
169 void PrintOpponents P((FILE *fp));
\r
170 void PrintPosition P((FILE *fp, int move));
\r
171 void StartChessProgram P((ChessProgramState *cps));
\r
172 void SendToProgram P((char *message, ChessProgramState *cps));
\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
175 char *buf, int count, int error));
\r
176 void SendTimeControl P((ChessProgramState *cps,
\r
177 int mps, long tc, int inc, int sd, int st));
\r
178 char *TimeControlTagValue P((void));
\r
179 void Attention P((ChessProgramState *cps));
\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
181 void ResurrectChessProgram P((void));
\r
182 void DisplayComment P((int moveNumber, char *text));
\r
183 void DisplayMove P((int moveNumber));
\r
184 void DisplayAnalysis P((void));
\r
186 void ParseGameHistory P((char *game));
\r
187 void ParseBoard12 P((char *string));
\r
188 void StartClocks P((void));
\r
189 void SwitchClocks P((void));
\r
190 void StopClocks P((void));
\r
191 void ResetClocks P((void));
\r
192 char *PGNDate P((void));
\r
193 void SetGameInfo P((void));
\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
195 int RegisterMove P((void));
\r
196 void MakeRegisteredMove P((void));
\r
197 void TruncateGame P((void));
\r
198 int looking_at P((char *, int *, char *));
\r
199 void CopyPlayerNameIntoFileName P((char **, char *));
\r
200 char *SavePart P((char *));
\r
201 int SaveGameOldStyle P((FILE *));
\r
202 int SaveGamePGN P((FILE *));
\r
203 void GetTimeMark P((TimeMark *));
\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
205 int CheckFlags P((void));
\r
206 long NextTickLength P((long));
\r
207 void CheckTimeControl P((void));
\r
208 void show_bytes P((FILE *, char *, int));
\r
209 int string_to_rating P((char *str));
\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
211 void InitBackEnd3 P((void));
\r
212 void FeatureDone P((ChessProgramState* cps, int val));
\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
214 void OutputKibitz(int window, char *text);
\r
215 int PerpetualChase(int first, int last);
\r
216 int EngineOutputIsUp();
\r
217 void InitDrawingSizes(int x, int y);
\r
220 extern void ConsoleCreate();
\r
223 ChessProgramState *WhitePlayer();
\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
225 int VerifyDisplayMode P(());
\r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
231 extern char installDir[MSG_SIZ];
\r
233 extern int tinyLayout, smallLayout;
\r
234 ChessProgramStats programStats;
\r
235 static int exiting = 0; /* [HGM] moved to top */
\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
\r
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
242 int opponentKibitzes;
\r
244 /* States for ics_getting_history */
\r
246 #define H_REQUESTED 1
\r
247 #define H_GOT_REQ_HEADER 2
\r
248 #define H_GOT_UNREQ_HEADER 3
\r
249 #define H_GETTING_MOVES 4
\r
250 #define H_GOT_UNWANTED_HEADER 5
\r
252 /* whosays values for GameEnds */
\r
254 #define GE_ENGINE 1
\r
255 #define GE_PLAYER 2
\r
257 #define GE_XBOARD 4
\r
258 #define GE_ENGINE1 5
\r
259 #define GE_ENGINE2 6
\r
261 /* Maximum number of games in a cmail message */
\r
262 #define CMAIL_MAX_GAMES 20
\r
264 /* Different types of move when calling RegisterMove */
\r
265 #define CMAIL_MOVE 0
\r
266 #define CMAIL_RESIGN 1
\r
267 #define CMAIL_DRAW 2
\r
268 #define CMAIL_ACCEPT 3
\r
270 /* Different types of result to remember for each game */
\r
271 #define CMAIL_NOT_RESULT 0
\r
272 #define CMAIL_OLD_RESULT 1
\r
273 #define CMAIL_NEW_RESULT 2
\r
275 /* Telnet protocol constants */
\r
276 #define TN_WILL 0373
\r
277 #define TN_WONT 0374
\r
279 #define TN_DONT 0376
\r
280 #define TN_IAC 0377
\r
281 #define TN_ECHO 0001
\r
282 #define TN_SGA 0003
\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
288 assert( dst != NULL );
\r
289 assert( src != NULL );
\r
290 assert( count > 0 );
\r
292 strncpy( dst, src, count );
\r
293 dst[ count-1 ] = '\0';
\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.
\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
303 assert( dst != NULL );
\r
304 assert( src != NULL );
\r
305 assert( count > 0 );
\r
307 dst_len = strlen(dst);
\r
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
311 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
317 /* Some compiler can't cast u64 to double
\r
318 * This function do the job for us:
\r
320 * We use the highest bit for cast, this only
\r
321 * works if the highest bit is not
\r
322 * in use (This should not happen)
\r
324 * We used this for all compiler
\r
327 u64ToDouble(u64 value)
\r
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
331 r = (double)(s64)tmp;
\r
332 if (value & u64Const(0x8000000000000000))
\r
333 r += 9.2233720368547758080e18; /* 2^63 */
\r
337 /* Fake up flags for now, as we aren't keeping track of castling
\r
338 availability yet. [HGM] Change of logic: the flag now only
\r
339 indicates the type of castlings allowed by the rule of the game.
\r
340 The actual rights themselves are maintained in the array
\r
341 castlingRights, as part of the game history, and are not probed
\r
347 int flags = F_ALL_CASTLE_OK;
\r
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
349 switch (gameInfo.variant) {
\r
350 case VariantSuicide:
\r
351 flags &= ~F_ALL_CASTLE_OK;
\r
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
353 flags |= F_IGNORE_CHECK;
\r
354 case VariantLosers:
\r
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
\r
357 case VariantAtomic:
\r
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
360 case VariantKriegspiel:
\r
361 flags |= F_KRIEGSPIEL_CAPTURE;
\r
363 case VariantCapaRandom:
\r
364 case VariantFischeRandom:
\r
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
366 case VariantNoCastle:
\r
367 case VariantShatranj:
\r
368 case VariantCourier:
\r
369 flags &= ~F_ALL_CASTLE_OK;
\r
377 FILE *gameFileFP, *debugFP;
\r
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
381 into a fixed-size buffer. Because of this, we must be prepared to
\r
382 receive strings as long as the size of the input buffer, which is currently
\r
383 set to 4K for Windows and 8K for the rest.
\r
384 So, we must either allocate sufficiently large buffers here, or
\r
385 reduce the size of the input buffer in the input reading part.
\r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
390 char thinkOutput1[MSG_SIZ*10];
\r
392 ChessProgramState first, second;
\r
394 /* premove variables */
\r
395 int premoveToX = 0;
\r
396 int premoveToY = 0;
\r
397 int premoveFromX = 0;
\r
398 int premoveFromY = 0;
\r
399 int premovePromoChar = 0;
\r
400 int gotPremove = 0;
\r
401 Boolean alarmSounded;
\r
402 /* end premove variables */
\r
404 char *ics_prefix = "$";
\r
405 int ics_type = ICS_GENERIC;
\r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
408 int pauseExamForwardMostMove = 0;
\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
415 int whiteFlag = FALSE, blackFlag = FALSE;
\r
416 int userOfferedDraw = FALSE;
\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
419 int cmailMoveType[CMAIL_MAX_GAMES];
\r
420 long ics_clock_paused = 0;
\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
423 GameMode gameMode = BeginningOfGame;
\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
427 int hiddenThinkOutputState = 0; /* [AS] */
\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
429 int adjudicateLossPlies = 6;
\r
430 char white_holding[64], black_holding[64];
\r
431 TimeMark lastNodeCountTime;
\r
432 long lastNodeCount=0;
\r
433 int have_sent_ICS_logon = 0;
\r
434 int movesPerSession;
\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
436 long timeControl_2; /* [AS] Allow separate time controls */
\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
438 long timeRemaining[2][MAX_MOVES];
\r
440 TimeMark programStartTime;
\r
441 char ics_handle[MSG_SIZ];
\r
442 int have_set_title = 0;
\r
444 /* animateTraining preserves the state of appData.animate
\r
445 * when Training mode is activated. This allows the
\r
446 * response to be animated when appData.animate == TRUE and
\r
447 * appData.animateDragging == TRUE.
\r
449 Boolean animateTraining;
\r
455 Board boards[MAX_MOVES];
\r
456 /* [HGM] Following 7 needed for accurate legality tests: */
\r
457 char epStatus[MAX_MOVES];
\r
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
462 int initialRulePlies, FENrulePlies;
\r
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
466 int shuffleOpenings;
\r
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
479 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
504 #if (BOARD_SIZE>=10)
\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
548 #define GothicArray CapablancaArray
\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
559 #define FalconArray CapablancaArray
\r
562 #else // !(BOARD_SIZE>=10)
\r
563 #define XiangqiPosition FIDEArray
\r
564 #define CapablancaArray FIDEArray
\r
565 #define GothicArray FIDEArray
\r
566 #define GreatArray FIDEArray
\r
567 #endif // !(BOARD_SIZE>=10)
\r
569 #if (BOARD_SIZE>=12)
\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
576 #else // !(BOARD_SIZE>=12)
\r
577 #define CourierArray CapablancaArray
\r
578 #endif // !(BOARD_SIZE>=12)
\r
581 Board initialPosition;
\r
584 /* Convert str to a rating. Checks for special cases of "----",
\r
586 "++++", etc. Also strips ()'s */
\r
588 string_to_rating(str)
\r
591 while(*str && !isdigit(*str)) ++str;
\r
593 return 0; /* One of the special "no rating" cases */
\r
599 ClearProgramStats()
\r
601 /* Init programStats */
\r
602 programStats.movelist[0] = 0;
\r
603 programStats.depth = 0;
\r
604 programStats.nr_moves = 0;
\r
605 programStats.moves_left = 0;
\r
606 programStats.nodes = 0;
\r
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
608 programStats.score = 0;
\r
609 programStats.got_only_move = 0;
\r
610 programStats.got_fail = 0;
\r
611 programStats.line_is_book = 0;
\r
617 int matched, min, sec;
\r
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
621 GetTimeMark(&programStartTime);
\r
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
\r
624 ClearProgramStats();
\r
625 programStats.ok_to_send = 1;
\r
626 programStats.seen_stat = 0;
\r
629 * Initialize game list
\r
631 ListNew(&gameList);
\r
635 * Internet chess server status
\r
637 if (appData.icsActive) {
\r
638 appData.matchMode = FALSE;
\r
639 appData.matchGames = 0;
\r
641 appData.noChessProgram = !appData.zippyPlay;
\r
643 appData.zippyPlay = FALSE;
\r
644 appData.zippyTalk = FALSE;
\r
645 appData.noChessProgram = TRUE;
\r
647 if (*appData.icsHelper != NULLCHAR) {
\r
648 appData.useTelnet = TRUE;
\r
649 appData.telnetProgram = appData.icsHelper;
\r
652 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
655 /* [AS] Initialize pv info list [HGM] and game state */
\r
659 for( i=0; i<MAX_MOVES; i++ ) {
\r
660 pvInfoList[i].depth = -1;
\r
661 epStatus[i]=EP_NONE;
\r
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
667 * Parse timeControl resource
\r
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
670 appData.movesPerSession)) {
\r
672 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
673 DisplayFatalError(buf, 0, 2);
\r
677 * Parse searchTime resource
\r
679 if (*appData.searchTime != NULLCHAR) {
\r
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
681 if (matched == 1) {
\r
682 searchTime = min * 60;
\r
683 } else if (matched == 2) {
\r
684 searchTime = min * 60 + sec;
\r
687 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
688 DisplayFatalError(buf, 0, 2);
\r
692 /* [AS] Adjudication threshold */
\r
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
695 first.which = "first";
\r
696 second.which = "second";
\r
697 first.maybeThinking = second.maybeThinking = FALSE;
\r
698 first.pr = second.pr = NoProc;
\r
699 first.isr = second.isr = NULL;
\r
700 first.sendTime = second.sendTime = 2;
\r
701 first.sendDrawOffers = 1;
\r
702 if (appData.firstPlaysBlack) {
\r
703 first.twoMachinesColor = "black\n";
\r
704 second.twoMachinesColor = "white\n";
\r
706 first.twoMachinesColor = "white\n";
\r
707 second.twoMachinesColor = "black\n";
\r
709 first.program = appData.firstChessProgram;
\r
710 second.program = appData.secondChessProgram;
\r
711 first.host = appData.firstHost;
\r
712 second.host = appData.secondHost;
\r
713 first.dir = appData.firstDirectory;
\r
714 second.dir = appData.secondDirectory;
\r
715 first.other = &second;
\r
716 second.other = &first;
\r
717 first.initString = appData.initString;
\r
718 second.initString = appData.secondInitString;
\r
719 first.computerString = appData.firstComputerString;
\r
720 second.computerString = appData.secondComputerString;
\r
721 first.useSigint = second.useSigint = TRUE;
\r
722 first.useSigterm = second.useSigterm = TRUE;
\r
723 first.reuse = appData.reuseFirst;
\r
724 second.reuse = appData.reuseSecond;
\r
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
726 second.nps = appData.secondNPS;
\r
727 first.useSetboard = second.useSetboard = FALSE;
\r
728 first.useSAN = second.useSAN = FALSE;
\r
729 first.usePing = second.usePing = FALSE;
\r
730 first.lastPing = second.lastPing = 0;
\r
731 first.lastPong = second.lastPong = 0;
\r
732 first.usePlayother = second.usePlayother = FALSE;
\r
733 first.useColors = second.useColors = TRUE;
\r
734 first.useUsermove = second.useUsermove = FALSE;
\r
735 first.sendICS = second.sendICS = FALSE;
\r
736 first.sendName = second.sendName = appData.icsActive;
\r
737 first.sdKludge = second.sdKludge = FALSE;
\r
738 first.stKludge = second.stKludge = FALSE;
\r
739 TidyProgramName(first.program, first.host, first.tidy);
\r
740 TidyProgramName(second.program, second.host, second.tidy);
\r
741 first.matchWins = second.matchWins = 0;
\r
742 strcpy(first.variants, appData.variant);
\r
743 strcpy(second.variants, appData.variant);
\r
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
745 first.analyzing = second.analyzing = FALSE;
\r
746 first.initDone = second.initDone = FALSE;
\r
748 /* New features added by Tord: */
\r
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
751 /* End of new features added by Tord. */
\r
752 first.fenOverride = appData.fenOverride1;
\r
753 second.fenOverride = appData.fenOverride2;
\r
755 /* [HGM] time odds: set factor for each machine */
\r
756 first.timeOdds = appData.firstTimeOdds;
\r
757 second.timeOdds = appData.secondTimeOdds;
\r
759 if(appData.timeOddsMode) {
\r
760 norm = first.timeOdds;
\r
761 if(norm > second.timeOdds) norm = second.timeOdds;
\r
763 first.timeOdds /= norm;
\r
764 second.timeOdds /= norm;
\r
767 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
768 first.accumulateTC = appData.firstAccumulateTC;
\r
769 second.accumulateTC = appData.secondAccumulateTC;
\r
770 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
773 first.debug = second.debug = FALSE;
\r
774 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
776 /* [HGM] options */
\r
777 first.optionSettings = appData.firstOptions;
\r
778 second.optionSettings = appData.secondOptions;
\r
780 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
781 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
782 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
783 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
784 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
785 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
787 if (appData.firstProtocolVersion > PROTOVER ||
\r
788 appData.firstProtocolVersion < 1) {
\r
790 sprintf(buf, _("protocol version %d not supported"),
\r
791 appData.firstProtocolVersion);
\r
792 DisplayFatalError(buf, 0, 2);
\r
794 first.protocolVersion = appData.firstProtocolVersion;
\r
797 if (appData.secondProtocolVersion > PROTOVER ||
\r
798 appData.secondProtocolVersion < 1) {
\r
800 sprintf(buf, _("protocol version %d not supported"),
\r
801 appData.secondProtocolVersion);
\r
802 DisplayFatalError(buf, 0, 2);
\r
804 second.protocolVersion = appData.secondProtocolVersion;
\r
807 if (appData.icsActive) {
\r
808 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
809 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
810 appData.clockMode = FALSE;
\r
811 first.sendTime = second.sendTime = 0;
\r
815 /* Override some settings from environment variables, for backward
\r
816 compatibility. Unfortunately it's not feasible to have the env
\r
817 vars just set defaults, at least in xboard. Ugh.
\r
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
824 if (appData.noChessProgram) {
\r
825 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
826 + strlen(PATCHLEVEL));
\r
827 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
832 while (*q != ' ' && *q != NULLCHAR) q++;
\r
834 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
835 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
836 + strlen(PATCHLEVEL) + (q - p));
\r
837 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
838 strncat(programVersion, p, q - p);
\r
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
841 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
842 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
843 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
847 if (!appData.icsActive) {
\r
849 /* Check for variants that are supported only in ICS mode,
\r
850 or not at all. Some that are accepted here nevertheless
\r
851 have bugs; see comments below.
\r
853 VariantClass variant = StringToVariant(appData.variant);
\r
855 case VariantBughouse: /* need four players and two boards */
\r
856 case VariantKriegspiel: /* need to hide pieces and move details */
\r
857 /* case VariantFischeRandom: (Fabien: moved below) */
\r
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
859 DisplayFatalError(buf, 0, 2);
\r
862 case VariantUnknown:
\r
863 case VariantLoadable:
\r
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
874 DisplayFatalError(buf, 0, 2);
\r
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
879 case VariantGothic: /* [HGM] should work */
\r
880 case VariantCapablanca: /* [HGM] should work */
\r
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
882 case VariantShogi: /* [HGM] drops not tested for legality */
\r
883 case VariantKnightmate: /* [HGM] should work */
\r
884 case VariantCylinder: /* [HGM] untested */
\r
885 case VariantFalcon: /* [HGM] untested */
\r
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
887 offboard interposition not understood */
\r
888 case VariantNormal: /* definitely works! */
\r
889 case VariantWildCastle: /* pieces not automatically shuffled */
\r
890 case VariantNoCastle: /* pieces not automatically shuffled */
\r
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
892 case VariantLosers: /* should work except for win condition,
\r
893 and doesn't know captures are mandatory */
\r
894 case VariantSuicide: /* should work except for win condition,
\r
895 and doesn't know captures are mandatory */
\r
896 case VariantGiveaway: /* should work except for win condition,
\r
897 and doesn't know captures are mandatory */
\r
898 case VariantTwoKings: /* should work */
\r
899 case VariantAtomic: /* should work except for win condition */
\r
900 case Variant3Check: /* should work except for win condition */
\r
901 case VariantShatranj: /* should work except for all win conditions */
\r
902 case VariantBerolina: /* might work if TestLegality is off */
\r
903 case VariantCapaRandom: /* should work */
\r
904 case VariantJanus: /* should work */
\r
905 case VariantSuper: /* experimental */
\r
906 case VariantGreat: /* experimental, requires legality testing to be off */
\r
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
912 InitEngineUCI( installDir, &second );
\r
915 int NextIntegerFromString( char ** str, long * value )
\r
920 while( *s == ' ' || *s == '\t' ) {
\r
926 if( *s >= '0' && *s <= '9' ) {
\r
927 while( *s >= '0' && *s <= '9' ) {
\r
928 *value = *value * 10 + (*s - '0');
\r
940 int NextTimeControlFromString( char ** str, long * value )
\r
943 int result = NextIntegerFromString( str, &temp );
\r
945 if( result == 0 ) {
\r
946 *value = temp * 60; /* Minutes */
\r
947 if( **str == ':' ) {
\r
949 result = NextIntegerFromString( str, &temp );
\r
950 *value += temp; /* Seconds */
\r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
959 int result = -1; long temp, temp2;
\r
961 if(**str != '+') return -1; // old params remain in force!
\r
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
966 /* time only: incremental or sudden-death time control */
\r
967 if(**str == '+') { /* increment follows; read it */
\r
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
970 *inc = temp2 * 1000;
\r
972 *moves = 0; *tc = temp * 1000;
\r
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
976 (*str)++; /* classical time control */
\r
977 result = NextTimeControlFromString( str, &temp2);
\r
980 *tc = temp2 * 1000;
\r
986 int GetTimeQuota(int movenr)
\r
987 { /* [HGM] get time to add from the multi-session time-control string */
\r
988 int moves=1; /* kludge to force reading of first session */
\r
989 long time, increment;
\r
990 char *s = fullTimeControlString;
\r
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
996 if(movenr == -1) return time; /* last move before new session */
\r
997 if(!moves) return increment; /* current session is incremental */
\r
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
999 } while(movenr >= -1); /* try again for next session */
\r
1001 return 0; // no new time quota on this move
\r
1005 ParseTimeControl(tc, ti, mps)
\r
1011 int matched, min, sec;
\r
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1014 if (matched == 1) {
\r
1015 timeControl = min * 60 * 1000;
\r
1016 } else if (matched == 2) {
\r
1017 timeControl = (min * 60 + sec) * 1000;
\r
1024 char buf[MSG_SIZ];
\r
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1030 else sprintf(buf, "+%s+%d", tc, ti);
\r
1033 sprintf(buf, "+%d/%s", mps, tc);
\r
1034 else sprintf(buf, "+%s", tc);
\r
1036 fullTimeControlString = StrSave(buf);
\r
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1042 if( *tc == '/' ) {
\r
1043 /* Parse second time control */
\r
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1054 timeControl_2 = tc2 * 1000;
\r
1057 timeControl_2 = 0;
\r
1064 timeControl = tc1 * 1000;
\r
1068 timeIncrement = ti * 1000; /* convert to ms */
\r
1069 movesPerSession = 0;
\r
1071 timeIncrement = 0;
\r
1072 movesPerSession = mps;
\r
1080 if (appData.debugMode) {
\r
1081 fprintf(debugFP, "%s\n", programVersion);
\r
1084 if (appData.matchGames > 0) {
\r
1085 appData.matchMode = TRUE;
\r
1086 } else if (appData.matchMode) {
\r
1087 appData.matchGames = 1;
\r
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1090 appData.matchGames = appData.sameColorGames;
\r
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1095 Reset(TRUE, FALSE);
\r
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1099 /* kludge: allow timeout for initial "feature" commands */
\r
1101 DisplayMessage("", _("Starting chess program"));
\r
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1107 InitBackEnd3 P((void))
\r
1109 GameMode initialMode;
\r
1110 char buf[MSG_SIZ];
\r
1113 InitChessProgram(&first, startedFromSetupPosition);
\r
1116 if (appData.icsActive) {
\r
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1121 err = establish();
\r
1123 if (*appData.icsCommPort != NULLCHAR) {
\r
1124 sprintf(buf, _("Could not open comm port %s"),
\r
1125 appData.icsCommPort);
\r
1127 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1128 appData.icsHost, appData.icsPort);
\r
1130 DisplayFatalError(buf, err, 1);
\r
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1138 } else if (appData.noChessProgram) {
\r
1144 if (*appData.cmailGameName != NULLCHAR) {
\r
1146 OpenLoopback(&cmailPR);
\r
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1152 DisplayMessage("", "");
\r
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1154 initialMode = BeginningOfGame;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1156 initialMode = TwoMachinesPlay;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1158 initialMode = AnalyzeFile;
\r
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1160 initialMode = AnalyzeMode;
\r
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1162 initialMode = MachinePlaysWhite;
\r
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1164 initialMode = MachinePlaysBlack;
\r
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1166 initialMode = EditGame;
\r
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1168 initialMode = EditPosition;
\r
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1170 initialMode = Training;
\r
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1173 DisplayFatalError(buf, 0, 2);
\r
1177 if (appData.matchMode) {
\r
1178 /* Set up machine vs. machine match */
\r
1179 if (appData.noChessProgram) {
\r
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1186 if (*appData.loadGameFile != NULLCHAR) {
\r
1187 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1188 if(index<0) lastIndex = index = 1;
\r
1189 if (!LoadGameFromFile(appData.loadGameFile,
\r
1191 appData.loadGameFile, FALSE)) {
\r
1192 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1197 if(index<0) lastIndex = index = 1;
\r
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1200 appData.loadPositionFile)) {
\r
1201 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1205 TwoMachinesEvent();
\r
1206 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1207 /* Set up cmail mode */
\r
1208 ReloadCmailMsgEvent(TRUE);
\r
1210 /* Set up other modes */
\r
1211 if (initialMode == AnalyzeFile) {
\r
1212 if (*appData.loadGameFile == NULLCHAR) {
\r
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1217 if (*appData.loadGameFile != NULLCHAR) {
\r
1218 (void) LoadGameFromFile(appData.loadGameFile,
\r
1219 appData.loadGameIndex,
\r
1220 appData.loadGameFile, TRUE);
\r
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1223 appData.loadPositionIndex,
\r
1224 appData.loadPositionFile);
\r
1225 /* [HGM] try to make self-starting even after FEN load */
\r
1226 /* to allow automatic setup of fairy variants with wtm */
\r
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1228 gameMode = BeginningOfGame;
\r
1229 setboardSpoiledMachineBlack = 1;
\r
1231 /* [HGM] loadPos: make that every new game uses the setup */
\r
1232 /* from file as long as we do not switch variant */
\r
1233 if(!blackPlaysFirst) { int i;
\r
1234 startedFromPositionFile = TRUE;
\r
1235 CopyBoard(filePosition, boards[0]);
\r
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1239 if (initialMode == AnalyzeMode) {
\r
1240 if (appData.noChessProgram) {
\r
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1244 if (appData.icsActive) {
\r
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1248 AnalyzeModeEvent();
\r
1249 } else if (initialMode == AnalyzeFile) {
\r
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1251 ShowThinkingEvent();
\r
1252 AnalyzeFileEvent();
\r
1253 AnalysisPeriodicEvent(1);
\r
1254 } else if (initialMode == MachinePlaysWhite) {
\r
1255 if (appData.noChessProgram) {
\r
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1260 if (appData.icsActive) {
\r
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1265 MachineWhiteEvent();
\r
1266 } else if (initialMode == MachinePlaysBlack) {
\r
1267 if (appData.noChessProgram) {
\r
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1272 if (appData.icsActive) {
\r
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1277 MachineBlackEvent();
\r
1278 } else if (initialMode == TwoMachinesPlay) {
\r
1279 if (appData.noChessProgram) {
\r
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1284 if (appData.icsActive) {
\r
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1289 TwoMachinesEvent();
\r
1290 } else if (initialMode == EditGame) {
\r
1292 } else if (initialMode == EditPosition) {
\r
1293 EditPositionEvent();
\r
1294 } else if (initialMode == Training) {
\r
1295 if (*appData.loadGameFile == NULLCHAR) {
\r
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1305 * Establish will establish a contact to a remote host.port.
\r
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1307 * used to talk to the host.
\r
1308 * Returns 0 if okay, error code if not.
\r
1313 char buf[MSG_SIZ];
\r
1315 if (*appData.icsCommPort != NULLCHAR) {
\r
1316 /* Talk to the host through a serial comm port */
\r
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1319 } else if (*appData.gateway != NULLCHAR) {
\r
1320 if (*appData.remoteShell == NULLCHAR) {
\r
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1322 sprintf(buf, "%s %s %s",
\r
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1327 /* Use the rsh program to run telnet program on a gateway host */
\r
1328 if (*appData.remoteUser == NULLCHAR) {
\r
1329 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1330 appData.gateway, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1334 appData.remoteShell, appData.gateway,
\r
1335 appData.remoteUser, appData.telnetProgram,
\r
1336 appData.icsHost, appData.icsPort);
\r
1338 return StartChildProcess(buf, "", &icsPR);
\r
1341 } else if (appData.useTelnet) {
\r
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1345 /* TCP socket interface differs somewhat between
\r
1346 Unix and NT; handle details in the front end.
\r
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1353 show_bytes(fp, buf, count)
\r
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1360 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1369 /* Returns an errno value */
\r
1371 OutputMaybeTelnet(pr, message, count, outError)
\r
1377 char buf[8192], *p, *q, *buflim;
\r
1378 int left, newcount, outcount;
\r
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1381 *appData.gateway != NULLCHAR) {
\r
1382 if (appData.debugMode) {
\r
1383 fprintf(debugFP, ">ICS: ");
\r
1384 show_bytes(debugFP, message, count);
\r
1385 fprintf(debugFP, "\n");
\r
1387 return OutputToProcess(pr, message, count, outError);
\r
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1396 if (q >= buflim) {
\r
1397 if (appData.debugMode) {
\r
1398 fprintf(debugFP, ">ICS: ");
\r
1399 show_bytes(debugFP, buf, newcount);
\r
1400 fprintf(debugFP, "\n");
\r
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1403 if (outcount < newcount) return -1; /* to be sure */
\r
1410 } else if (((unsigned char) *p) == TN_IAC) {
\r
1411 *q++ = (char) TN_IAC;
\r
1418 if (appData.debugMode) {
\r
1419 fprintf(debugFP, ">ICS: ");
\r
1420 show_bytes(debugFP, buf, newcount);
\r
1421 fprintf(debugFP, "\n");
\r
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1424 if (outcount < newcount) return -1; /* to be sure */
\r
1429 read_from_player(isr, closure, message, count, error)
\r
1430 InputSourceRef isr;
\r
1436 int outError, outCount;
\r
1437 static int gotEof = 0;
\r
1439 /* Pass data read from player on to ICS */
\r
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1443 if (outCount < count) {
\r
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1446 } else if (count < 0) {
\r
1447 RemoveInputSource(isr);
\r
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1449 } else if (gotEof++ > 0) {
\r
1450 RemoveInputSource(isr);
\r
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1459 int count, outCount, outError;
\r
1461 if (icsPR == NULL) return;
\r
1463 count = strlen(s);
\r
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1465 if (outCount < count) {
\r
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1470 /* This is used for sending logon scripts to the ICS. Sending
\r
1471 without a delay causes problems when using timestamp on ICC
\r
1472 (at least on my machine). */
\r
1474 SendToICSDelayed(s,msdelay)
\r
1478 int count, outCount, outError;
\r
1480 if (icsPR == NULL) return;
\r
1482 count = strlen(s);
\r
1483 if (appData.debugMode) {
\r
1484 fprintf(debugFP, ">ICS: ");
\r
1485 show_bytes(debugFP, s, count);
\r
1486 fprintf(debugFP, "\n");
\r
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1490 if (outCount < count) {
\r
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1496 /* Remove all highlighting escape sequences in s
\r
1497 Also deletes any suffix starting with '('
\r
1500 StripHighlightAndTitle(s)
\r
1503 static char retbuf[MSG_SIZ];
\r
1506 while (*s != NULLCHAR) {
\r
1507 while (*s == '\033') {
\r
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1509 if (*s != NULLCHAR) s++;
\r
1511 while (*s != NULLCHAR && *s != '\033') {
\r
1512 if (*s == '(' || *s == '[') {
\r
1523 /* Remove all highlighting escape sequences in s */
\r
1528 static char retbuf[MSG_SIZ];
\r
1531 while (*s != NULLCHAR) {
\r
1532 while (*s == '\033') {
\r
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1534 if (*s != NULLCHAR) s++;
\r
1536 while (*s != NULLCHAR && *s != '\033') {
\r
1544 char *variantNames[] = VARIANT_NAMES;
\r
1549 return variantNames[v];
\r
1553 /* Identify a variant from the strings the chess servers use or the
\r
1554 PGN Variant tag names we use. */
\r
1556 StringToVariant(e)
\r
1561 VariantClass v = VariantNormal;
\r
1562 int i, found = FALSE;
\r
1563 char buf[MSG_SIZ];
\r
1567 /* [HGM] skip over optional board-size prefixes */
\r
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1570 while( *e++ != '_');
\r
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1574 if (StrCaseStr(e, variantNames[i])) {
\r
1575 v = (VariantClass) i;
\r
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1583 || StrCaseStr(e, "wild/fr")
\r
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1585 v = VariantFischeRandom;
\r
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1587 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1590 if (isdigit(*p)) {
\r
1596 case 0: /* FICS only, actually */
\r
1598 /* Castling legal even if K starts on d-file */
\r
1599 v = VariantWildCastle;
\r
1604 /* Castling illegal even if K & R happen to start in
\r
1605 normal positions. */
\r
1606 v = VariantNoCastle;
\r
1619 /* Castling legal iff K & R start in normal positions */
\r
1620 v = VariantNormal;
\r
1625 /* Special wilds for position setup; unclear what to do here */
\r
1626 v = VariantLoadable;
\r
1629 /* Bizarre ICC game */
\r
1630 v = VariantTwoKings;
\r
1633 v = VariantKriegspiel;
\r
1636 v = VariantLosers;
\r
1639 v = VariantFischeRandom;
\r
1642 v = VariantCrazyhouse;
\r
1645 v = VariantBughouse;
\r
1648 v = Variant3Check;
\r
1651 /* Not quite the same as FICS suicide! */
\r
1652 v = VariantGiveaway;
\r
1655 v = VariantAtomic;
\r
1658 v = VariantShatranj;
\r
1661 /* Temporary names for future ICC types. The name *will* change in
\r
1662 the next xboard/WinBoard release after ICC defines it. */
\r
1691 v = VariantXiangqi;
\r
1694 v = VariantCourier;
\r
1697 v = VariantGothic;
\r
1700 v = VariantCapablanca;
\r
1703 v = VariantKnightmate;
\r
1709 v = VariantCylinder;
\r
1712 v = VariantFalcon;
\r
1715 v = VariantCapaRandom;
\r
1718 v = VariantBerolina;
\r
1730 /* Found "wild" or "w" in the string but no number;
\r
1731 must assume it's normal chess. */
\r
1732 v = VariantNormal;
\r
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1736 DisplayError(buf, 0);
\r
1737 v = VariantUnknown;
\r
1742 if (appData.debugMode) {
\r
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1744 e, wnum, VariantName(v));
\r
1749 static int leftover_start = 0, leftover_len = 0;
\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1753 advance *index beyond it, and set leftover_start to the new value of
\r
1754 *index; else return FALSE. If pattern contains the character '*', it
\r
1755 matches any sequence of characters not containing '\r', '\n', or the
\r
1756 character following the '*' (if any), and the matched sequence(s) are
\r
1757 copied into star_match.
\r
1760 looking_at(buf, index, pattern)
\r
1765 char *bufp = &buf[*index], *patternp = pattern;
\r
1766 int star_count = 0;
\r
1767 char *matchp = star_match[0];
\r
1770 if (*patternp == NULLCHAR) {
\r
1771 *index = leftover_start = bufp - buf;
\r
1772 *matchp = NULLCHAR;
\r
1775 if (*bufp == NULLCHAR) return FALSE;
\r
1776 if (*patternp == '*') {
\r
1777 if (*bufp == *(patternp + 1)) {
\r
1778 *matchp = NULLCHAR;
\r
1779 matchp = star_match[++star_count];
\r
1783 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1785 if (*patternp == NULLCHAR)
\r
1790 *matchp++ = *bufp++;
\r
1794 if (*patternp != *bufp) return FALSE;
\r
1801 SendToPlayer(data, length)
\r
1805 int error, outCount;
\r
1806 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1807 if (outCount < length) {
\r
1808 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1813 PackHolding(packed, holding)
\r
1817 char *p = holding;
\r
1819 int runlength = 0;
\r
1825 switch (runlength) {
\r
1836 sprintf(q, "%d", runlength);
\r
1848 /* Telnet protocol requests from the front end */
\r
1850 TelnetRequest(ddww, option)
\r
1851 unsigned char ddww, option;
\r
1853 unsigned char msg[3];
\r
1854 int outCount, outError;
\r
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1858 if (appData.debugMode) {
\r
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1875 sprintf(buf1, "%d", ddww);
\r
1880 optionStr = "ECHO";
\r
1884 sprintf(buf2, "%d", option);
\r
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1893 if (outCount < 3) {
\r
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1901 if (!appData.icsActive) return;
\r
1902 TelnetRequest(TN_DO, TN_ECHO);
\r
1908 if (!appData.icsActive) return;
\r
1909 TelnetRequest(TN_DONT, TN_ECHO);
\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1915 /* put the holdings sent to us by the server on the board holdings area */
\r
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1918 ChessSquare piece;
\r
1920 if(gameInfo.holdingsWidth < 2) return;
\r
1922 if( (int)lowestPiece >= BlackPawn ) {
\r
1923 holdingsColumn = 0;
\r
1925 holdingsStartRow = BOARD_HEIGHT-1;
\r
1928 holdingsColumn = BOARD_WIDTH-1;
\r
1929 countsColumn = BOARD_WIDTH-2;
\r
1930 holdingsStartRow = 0;
\r
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1935 board[i][holdingsColumn] = EmptySquare;
\r
1936 board[i][countsColumn] = (ChessSquare) 0;
\r
1938 while( (p=*holdings++) != NULLCHAR ) {
\r
1939 piece = CharToPiece( ToUpper(p) );
\r
1940 if(piece == EmptySquare) continue;
\r
1941 /*j = (int) piece - (int) WhitePawn;*/
\r
1942 j = PieceToNumber(piece);
\r
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1944 if(j < 0) continue; /* should not happen */
\r
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1947 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1954 VariantSwitch(Board board, VariantClass newVariant)
\r
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1960 startedFromPositionFile = FALSE;
\r
1961 if(gameInfo.variant == newVariant) return;
\r
1963 /* [HGM] This routine is called each time an assignment is made to
\r
1964 * gameInfo.variant during a game, to make sure the board sizes
\r
1965 * are set to match the new variant. If that means adding or deleting
\r
1966 * holdings, we shift the playing board accordingly
\r
1967 * This kludge is needed because in ICS observe mode, we get boards
\r
1968 * of an ongoing game without knowing the variant, and learn about the
\r
1969 * latter only later. This can be because of the move list we requested,
\r
1970 * in which case the game history is refilled from the beginning anyway,
\r
1971 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1972 * case we want to add those holdings to the already received position.
\r
1976 if (appData.debugMode) {
\r
1977 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1978 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1979 setbuf(debugFP, NULL);
\r
1981 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1983 switch(newVariant) {
\r
1984 case VariantShogi:
\r
1985 newWidth = 9; newHeight = 9;
\r
1986 gameInfo.holdingsSize = 7;
\r
1987 case VariantBughouse:
\r
1988 case VariantCrazyhouse:
\r
1989 newHoldingsWidth = 2; break;
\r
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1994 if(newWidth != gameInfo.boardWidth ||
\r
1995 newHeight != gameInfo.boardHeight ||
\r
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1998 /* shift position to new playing area, if needed */
\r
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
2000 for(i=0; i<BOARD_HEIGHT; i++)
\r
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2004 for(i=0; i<newHeight; i++) {
\r
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2009 for(i=0; i<BOARD_HEIGHT; i++)
\r
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2015 gameInfo.boardWidth = newWidth;
\r
2016 gameInfo.boardHeight = newHeight;
\r
2017 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2018 gameInfo.variant = newVariant;
\r
2019 InitDrawingSizes(-2, 0);
\r
2021 /* [HGM] The following should definitely be solved in a better way */
\r
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2025 saveEP = epStatus[0];
\r
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2029 epStatus[0] = saveEP;
\r
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2035 forwardMostMove = oldForwardMostMove;
\r
2036 backwardMostMove = oldBackwardMostMove;
\r
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2040 static int loggedOn = FALSE;
\r
2042 /*-- Game start info cache: --*/
\r
2044 char gs_kind[MSG_SIZ];
\r
2045 static char player1Name[128] = "";
\r
2046 static char player2Name[128] = "";
\r
2047 static int player1Rating = -1;
\r
2048 static int player2Rating = -1;
\r
2049 /*----------------------------*/
\r
2051 ColorClass curColor = ColorNormal;
\r
2052 int suppressKibitz = 0;
\r
2055 read_from_ics(isr, closure, data, count, error)
\r
2056 InputSourceRef isr;
\r
2062 #define BUF_SIZE 8192
\r
2063 #define STARTED_NONE 0
\r
2064 #define STARTED_MOVES 1
\r
2065 #define STARTED_BOARD 2
\r
2066 #define STARTED_OBSERVE 3
\r
2067 #define STARTED_HOLDINGS 4
\r
2068 #define STARTED_CHATTER 5
\r
2069 #define STARTED_COMMENT 6
\r
2070 #define STARTED_MOVES_NOHIDE 7
\r
2072 static int started = STARTED_NONE;
\r
2073 static char parse[20000];
\r
2074 static int parse_pos = 0;
\r
2075 static char buf[BUF_SIZE + 1];
\r
2076 static int firstTime = TRUE, intfSet = FALSE;
\r
2077 static ColorClass prevColor = ColorNormal;
\r
2078 static int savingComment = FALSE;
\r
2084 int backup; /* [DM] For zippy color lines */
\r
2087 if (appData.debugMode) {
\r
2089 fprintf(debugFP, "<ICS: ");
\r
2090 show_bytes(debugFP, data, count);
\r
2091 fprintf(debugFP, "\n");
\r
2095 if (appData.debugMode) { int f = forwardMostMove;
\r
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2100 /* If last read ended with a partial line that we couldn't parse,
\r
2101 prepend it to the new read and try again. */
\r
2102 if (leftover_len > 0) {
\r
2103 for (i=0; i<leftover_len; i++)
\r
2104 buf[i] = buf[leftover_start + i];
\r
2107 /* Copy in new characters, removing nulls and \r's */
\r
2108 buf_len = leftover_len;
\r
2109 for (i = 0; i < count; i++) {
\r
2110 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2111 buf[buf_len++] = data[i];
\r
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2117 buf[buf_len] = NULLCHAR;
\r
2118 next_out = leftover_len;
\r
2119 leftover_start = 0;
\r
2122 while (i < buf_len) {
\r
2123 /* Deal with part of the TELNET option negotiation
\r
2124 protocol. We refuse to do anything beyond the
\r
2125 defaults, except that we allow the WILL ECHO option,
\r
2126 which ICS uses to turn off password echoing when we are
\r
2127 directly connected to it. We reject this option
\r
2128 if localLineEditing mode is on (always on in xboard)
\r
2129 and we are talking to port 23, which might be a real
\r
2130 telnet server that will try to keep WILL ECHO on permanently.
\r
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2134 unsigned char option;
\r
2136 switch ((unsigned char) buf[++i]) {
\r
2138 if (appData.debugMode)
\r
2139 fprintf(debugFP, "\n<WILL ");
\r
2140 switch (option = (unsigned char) buf[++i]) {
\r
2142 if (appData.debugMode)
\r
2143 fprintf(debugFP, "ECHO ");
\r
2144 /* Reply only if this is a change, according
\r
2145 to the protocol rules. */
\r
2146 if (remoteEchoOption) break;
\r
2147 if (appData.localLineEditing &&
\r
2148 atoi(appData.icsPort) == TN_PORT) {
\r
2149 TelnetRequest(TN_DONT, TN_ECHO);
\r
2152 TelnetRequest(TN_DO, TN_ECHO);
\r
2153 remoteEchoOption = TRUE;
\r
2157 if (appData.debugMode)
\r
2158 fprintf(debugFP, "%d ", option);
\r
2159 /* Whatever this is, we don't want it. */
\r
2160 TelnetRequest(TN_DONT, option);
\r
2165 if (appData.debugMode)
\r
2166 fprintf(debugFP, "\n<WONT ");
\r
2167 switch (option = (unsigned char) buf[++i]) {
\r
2169 if (appData.debugMode)
\r
2170 fprintf(debugFP, "ECHO ");
\r
2171 /* Reply only if this is a change, according
\r
2172 to the protocol rules. */
\r
2173 if (!remoteEchoOption) break;
\r
2175 TelnetRequest(TN_DONT, TN_ECHO);
\r
2176 remoteEchoOption = FALSE;
\r
2179 if (appData.debugMode)
\r
2180 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2181 /* Whatever this is, it must already be turned
\r
2182 off, because we never agree to turn on
\r
2183 anything non-default, so according to the
\r
2184 protocol rules, we don't reply. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "\n<DO ");
\r
2191 switch (option = (unsigned char) buf[++i]) {
\r
2193 /* Whatever this is, we refuse to do it. */
\r
2194 if (appData.debugMode)
\r
2195 fprintf(debugFP, "%d ", option);
\r
2196 TelnetRequest(TN_WONT, option);
\r
2201 if (appData.debugMode)
\r
2202 fprintf(debugFP, "\n<DONT ");
\r
2203 switch (option = (unsigned char) buf[++i]) {
\r
2205 if (appData.debugMode)
\r
2206 fprintf(debugFP, "%d ", option);
\r
2207 /* Whatever this is, we are already not doing
\r
2208 it, because we never agree to do anything
\r
2209 non-default, so according to the protocol
\r
2210 rules, we don't reply. */
\r
2215 if (appData.debugMode)
\r
2216 fprintf(debugFP, "\n<IAC ");
\r
2217 /* Doubled IAC; pass it through */
\r
2221 if (appData.debugMode)
\r
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2223 /* Drop all other telnet commands on the floor */
\r
2226 if (oldi > next_out)
\r
2227 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2228 if (++i > next_out)
\r
2233 /* OK, this at least will *usually* work */
\r
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2238 if (loggedOn && !intfSet) {
\r
2239 if (ics_type == ICS_ICC) {
\r
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2244 } else if (ics_type == ICS_CHESSNET) {
\r
2245 sprintf(str, "/style 12\n");
\r
2247 strcpy(str, "alias $ @\n$set interface ");
\r
2248 strcat(str, programVersion);
\r
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2251 strcat(str, "$iset nohighlight 1\n");
\r
2253 strcat(str, "$iset lock 1\n$style 12\n");
\r
2259 if (started == STARTED_COMMENT) {
\r
2260 /* Accumulate characters in comment */
\r
2261 parse[parse_pos++] = buf[i];
\r
2262 if (buf[i] == '\n') {
\r
2263 parse[parse_pos] = NULLCHAR;
\r
2264 if(!suppressKibitz) // [HGM] kibitz
\r
2265 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2267 int nrDigit = 0, nrAlph = 0, i;
\r
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2270 parse[parse_pos] = NULLCHAR;
\r
2271 // try to be smart: if it does not look like search info, it should go to
\r
2272 // ICS interaction window after all, not to engine-output window.
\r
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2279 int depth=0; float score;
\r
2280 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
\r
2281 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
\r
2282 pvInfoList[forwardMostMove-1].depth = depth;
\r
2283 pvInfoList[forwardMostMove-1].score = 100*score;
\r
2285 OutputKibitz(suppressKibitz, parse);
\r
2287 char tmp[MSG_SIZ];
\r
2288 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2289 SendToPlayer(tmp, strlen(tmp));
\r
2292 started = STARTED_NONE;
\r
2294 /* Don't match patterns against characters in chatter */
\r
2299 if (started == STARTED_CHATTER) {
\r
2300 if (buf[i] != '\n') {
\r
2301 /* Don't match patterns against characters in chatter */
\r
2305 started = STARTED_NONE;
\r
2308 /* Kludge to deal with rcmd protocol */
\r
2309 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2310 DisplayFatalError(&buf[1], 0, 1);
\r
2313 firstTime = FALSE;
\r
2316 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2317 ics_type = ICS_ICC;
\r
2319 if (appData.debugMode)
\r
2320 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2323 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2324 ics_type = ICS_FICS;
\r
2326 if (appData.debugMode)
\r
2327 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2330 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2331 ics_type = ICS_CHESSNET;
\r
2333 if (appData.debugMode)
\r
2334 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2339 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2340 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2341 looking_at(buf, &i, "will be \"*\""))) {
\r
2342 strcpy(ics_handle, star_match[0]);
\r
2346 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2347 char buf[MSG_SIZ];
\r
2348 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2349 DisplayIcsInteractionTitle(buf);
\r
2350 have_set_title = TRUE;
\r
2353 /* skip finger notes */
\r
2354 if (started == STARTED_NONE &&
\r
2355 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2356 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2357 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2358 started = STARTED_CHATTER;
\r
2363 /* skip formula vars */
\r
2364 if (started == STARTED_NONE &&
\r
2365 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2366 started = STARTED_CHATTER;
\r
2372 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2373 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2374 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2375 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2376 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2377 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2378 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2379 suppressKibitz = TRUE;
\r
2380 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2381 && (gameMode == IcsPlayingWhite)) ||
\r
2382 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2383 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2384 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2386 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2387 parse_pos = 0; parse[0] = NULLCHAR;
\r
2388 savingComment = TRUE;
\r
2389 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2390 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2394 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2395 started = STARTED_CHATTER;
\r
2396 suppressKibitz = TRUE;
\r
2398 } // [HGM] kibitz: end of patch
\r
2400 if (appData.zippyTalk || appData.zippyPlay) {
\r
2401 /* [DM] Backup address for color zippy lines */
\r
2405 if (loggedOn == TRUE)
\r
2406 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2407 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2409 if (ZippyControl(buf, &i) ||
\r
2410 ZippyConverse(buf, &i) ||
\r
2411 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2413 if (!appData.colorize) continue;
\r
2417 } // [DM] 'else { ' deleted
\r
2418 if (/* Don't color "message" or "messages" output */
\r
2419 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2420 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2421 looking_at(buf, &i, "--* (*:*): ") ||
\r
2422 /* Regular tells and says */
\r
2423 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2424 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2425 looking_at(buf, &i, "* says: ") ||
\r
2426 /* Message notifications (same color as tells) */
\r
2427 looking_at(buf, &i, "* has left a message ") ||
\r
2428 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2429 /* Whispers and kibitzes */
\r
2430 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2431 looking_at(buf, &i, "* kibitzes: ") ||
\r
2432 /* Channel tells */
\r
2433 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2435 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2436 /* Avoid "tells you:" spoofs in channels */
\r
2439 if (star_match[0][0] == NULLCHAR ||
\r
2440 strchr(star_match[0], ' ') ||
\r
2441 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2442 /* Reject bogus matches */
\r
2445 if (appData.colorize) {
\r
2446 if (oldi > next_out) {
\r
2447 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2452 Colorize(ColorTell, FALSE);
\r
2453 curColor = ColorTell;
\r
2456 Colorize(ColorKibitz, FALSE);
\r
2457 curColor = ColorKibitz;
\r
2460 p = strrchr(star_match[1], '(');
\r
2462 p = star_match[1];
\r
2466 if (atoi(p) == 1) {
\r
2467 Colorize(ColorChannel1, FALSE);
\r
2468 curColor = ColorChannel1;
\r
2470 Colorize(ColorChannel, FALSE);
\r
2471 curColor = ColorChannel;
\r
2475 curColor = ColorNormal;
\r
2479 if (started == STARTED_NONE && appData.autoComment &&
\r
2480 (gameMode == IcsObserving ||
\r
2481 gameMode == IcsPlayingWhite ||
\r
2482 gameMode == IcsPlayingBlack)) {
\r
2483 parse_pos = i - oldi;
\r
2484 memcpy(parse, &buf[oldi], parse_pos);
\r
2485 parse[parse_pos] = NULLCHAR;
\r
2486 started = STARTED_COMMENT;
\r
2487 savingComment = TRUE;
\r
2489 started = STARTED_CHATTER;
\r
2490 savingComment = FALSE;
\r
2497 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2498 looking_at(buf, &i, "* c-shouts: ")) {
\r
2499 if (appData.colorize) {
\r
2500 if (oldi > next_out) {
\r
2501 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2504 Colorize(ColorSShout, FALSE);
\r
2505 curColor = ColorSShout;
\r
2508 started = STARTED_CHATTER;
\r
2512 if (looking_at(buf, &i, "--->")) {
\r
2517 if (looking_at(buf, &i, "* shouts: ") ||
\r
2518 looking_at(buf, &i, "--> ")) {
\r
2519 if (appData.colorize) {
\r
2520 if (oldi > next_out) {
\r
2521 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2524 Colorize(ColorShout, FALSE);
\r
2525 curColor = ColorShout;
\r
2528 started = STARTED_CHATTER;
\r
2532 if (looking_at( buf, &i, "Challenge:")) {
\r
2533 if (appData.colorize) {
\r
2534 if (oldi > next_out) {
\r
2535 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2538 Colorize(ColorChallenge, FALSE);
\r
2539 curColor = ColorChallenge;
\r
2545 if (looking_at(buf, &i, "* offers you") ||
\r
2546 looking_at(buf, &i, "* offers to be") ||
\r
2547 looking_at(buf, &i, "* would like to") ||
\r
2548 looking_at(buf, &i, "* requests to") ||
\r
2549 looking_at(buf, &i, "Your opponent offers") ||
\r
2550 looking_at(buf, &i, "Your opponent requests")) {
\r
2552 if (appData.colorize) {
\r
2553 if (oldi > next_out) {
\r
2554 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2557 Colorize(ColorRequest, FALSE);
\r
2558 curColor = ColorRequest;
\r
2563 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2564 if (appData.colorize) {
\r
2565 if (oldi > next_out) {
\r
2566 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2569 Colorize(ColorSeek, FALSE);
\r
2570 curColor = ColorSeek;
\r
2575 if (looking_at(buf, &i, "\\ ")) {
\r
2576 if (prevColor != ColorNormal) {
\r
2577 if (oldi > next_out) {
\r
2578 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2581 Colorize(prevColor, TRUE);
\r
2582 curColor = prevColor;
\r
2584 if (savingComment) {
\r
2585 parse_pos = i - oldi;
\r
2586 memcpy(parse, &buf[oldi], parse_pos);
\r
2587 parse[parse_pos] = NULLCHAR;
\r
2588 started = STARTED_COMMENT;
\r
2590 started = STARTED_CHATTER;
\r
2595 if (looking_at(buf, &i, "Black Strength :") ||
\r
2596 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2597 looking_at(buf, &i, "<10>") ||
\r
2598 looking_at(buf, &i, "#@#")) {
\r
2599 /* Wrong board style */
\r
2601 SendToICS(ics_prefix);
\r
2602 SendToICS("set style 12\n");
\r
2603 SendToICS(ics_prefix);
\r
2604 SendToICS("refresh\n");
\r
2608 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2610 have_sent_ICS_logon = 1;
\r
2614 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2615 (looking_at(buf, &i, "\n<12> ") ||
\r
2616 looking_at(buf, &i, "<12> "))) {
\r
2618 if (oldi > next_out) {
\r
2619 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2622 started = STARTED_BOARD;
\r
2627 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2628 looking_at(buf, &i, "<b1> ")) {
\r
2629 if (oldi > next_out) {
\r
2630 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2633 started = STARTED_HOLDINGS;
\r
2638 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2640 /* Header for a move list -- first line */
\r
2642 switch (ics_getting_history) {
\r
2644 switch (gameMode) {
\r
2646 case BeginningOfGame:
\r
2647 /* User typed "moves" or "oldmoves" while we
\r
2648 were idle. Pretend we asked for these
\r
2649 moves and soak them up so user can step
\r
2650 through them and/or save them.
\r
2652 Reset(FALSE, TRUE);
\r
2653 gameMode = IcsObserving;
\r
2656 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2658 case EditGame: /*?*/
\r
2659 case EditPosition: /*?*/
\r
2660 /* Should above feature work in these modes too? */
\r
2661 /* For now it doesn't */
\r
2662 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2665 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2670 /* Is this the right one? */
\r
2671 if (gameInfo.white && gameInfo.black &&
\r
2672 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2673 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2675 ics_getting_history = H_GOT_REQ_HEADER;
\r
2678 case H_GOT_REQ_HEADER:
\r
2679 case H_GOT_UNREQ_HEADER:
\r
2680 case H_GOT_UNWANTED_HEADER:
\r
2681 case H_GETTING_MOVES:
\r
2682 /* Should not happen */
\r
2683 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2684 ics_getting_history = H_FALSE;
\r
2688 /* Save player ratings into gameInfo if needed */
\r
2689 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2690 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2691 (gameInfo.whiteRating == -1 ||
\r
2692 gameInfo.blackRating == -1)) {
\r
2694 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2695 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2696 if (appData.debugMode)
\r
2697 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2698 gameInfo.whiteRating, gameInfo.blackRating);
\r
2703 if (looking_at(buf, &i,
\r
2704 "* * match, initial time: * minute*, increment: * second")) {
\r
2705 /* Header for a move list -- second line */
\r
2706 /* Initial board will follow if this is a wild game */
\r
2707 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2708 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2709 gameInfo.event = StrSave(str);
\r
2710 /* [HGM] we switched variant. Translate boards if needed. */
\r
2711 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2715 if (looking_at(buf, &i, "Move ")) {
\r
2716 /* Beginning of a move list */
\r
2717 switch (ics_getting_history) {
\r
2719 /* Normally should not happen */
\r
2720 /* Maybe user hit reset while we were parsing */
\r
2723 /* Happens if we are ignoring a move list that is not
\r
2724 * the one we just requested. Common if the user
\r
2725 * tries to observe two games without turning off
\r
2728 case H_GETTING_MOVES:
\r
2729 /* Should not happen */
\r
2730 DisplayError(_("Error gathering move list: nested"), 0);
\r
2731 ics_getting_history = H_FALSE;
\r
2733 case H_GOT_REQ_HEADER:
\r
2734 ics_getting_history = H_GETTING_MOVES;
\r
2735 started = STARTED_MOVES;
\r
2737 if (oldi > next_out) {
\r
2738 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2741 case H_GOT_UNREQ_HEADER:
\r
2742 ics_getting_history = H_GETTING_MOVES;
\r
2743 started = STARTED_MOVES_NOHIDE;
\r
2746 case H_GOT_UNWANTED_HEADER:
\r
2747 ics_getting_history = H_FALSE;
\r
2753 if (looking_at(buf, &i, "% ") ||
\r
2754 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2755 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2756 savingComment = FALSE;
\r
2757 switch (started) {
\r
2758 case STARTED_MOVES:
\r
2759 case STARTED_MOVES_NOHIDE:
\r
2760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2761 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2762 ParseGameHistory(parse);
\r
2764 if (appData.zippyPlay && first.initDone) {
\r
2765 FeedMovesToProgram(&first, forwardMostMove);
\r
2766 if (gameMode == IcsPlayingWhite) {
\r
2767 if (WhiteOnMove(forwardMostMove)) {
\r
2768 if (first.sendTime) {
\r
2769 if (first.useColors) {
\r
2770 SendToProgram("black\n", &first);
\r
2772 SendTimeRemaining(&first, TRUE);
\r
2775 if (first.useColors) {
\r
2776 SendToProgram("white\ngo\n", &first);
\r
2778 SendToProgram("go\n", &first);
\r
2781 if (first.useColors) {
\r
2782 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2786 first.maybeThinking = TRUE;
\r
2788 if (first.usePlayother) {
\r
2789 if (first.sendTime) {
\r
2790 SendTimeRemaining(&first, TRUE);
\r
2792 SendToProgram("playother\n", &first);
\r
2793 firstMove = FALSE;
\r
2798 } else if (gameMode == IcsPlayingBlack) {
\r
2799 if (!WhiteOnMove(forwardMostMove)) {
\r
2800 if (first.sendTime) {
\r
2801 if (first.useColors) {
\r
2802 SendToProgram("white\n", &first);
\r
2804 SendTimeRemaining(&first, FALSE);
\r
2807 if (first.useColors) {
\r
2808 SendToProgram("black\ngo\n", &first);
\r
2810 SendToProgram("go\n", &first);
\r
2813 if (first.useColors) {
\r
2814 SendToProgram("black\n", &first);
\r
2816 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2818 first.maybeThinking = TRUE;
\r
2820 if (first.usePlayother) {
\r
2821 if (first.sendTime) {
\r
2822 SendTimeRemaining(&first, FALSE);
\r
2824 SendToProgram("playother\n", &first);
\r
2825 firstMove = FALSE;
\r
2833 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2834 /* Moves came from oldmoves or moves command
\r
2835 while we weren't doing anything else.
\r
2837 currentMove = forwardMostMove;
\r
2838 ClearHighlights();/*!!could figure this out*/
\r
2839 flipView = appData.flipView;
\r
2840 DrawPosition(FALSE, boards[currentMove]);
\r
2841 DisplayBothClocks();
\r
2842 sprintf(str, "%s vs. %s",
\r
2843 gameInfo.white, gameInfo.black);
\r
2844 DisplayTitle(str);
\r
2845 gameMode = IcsIdle;
\r
2847 /* Moves were history of an active game */
\r
2848 if (gameInfo.resultDetails != NULL) {
\r
2849 free(gameInfo.resultDetails);
\r
2850 gameInfo.resultDetails = NULL;
\r
2853 HistorySet(parseList, backwardMostMove,
\r
2854 forwardMostMove, currentMove-1);
\r
2855 DisplayMove(currentMove - 1);
\r
2856 if (started == STARTED_MOVES) next_out = i;
\r
2857 started = STARTED_NONE;
\r
2858 ics_getting_history = H_FALSE;
\r
2861 case STARTED_OBSERVE:
\r
2862 started = STARTED_NONE;
\r
2863 SendToICS(ics_prefix);
\r
2864 SendToICS("refresh\n");
\r
2870 if(bookHit) { // [HGM] book: simulate book reply
\r
2871 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2873 programStats.nodes = programStats.depth = programStats.time =
\r
2874 programStats.score = programStats.got_only_move = 0;
\r
2875 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2877 strcpy(bookMove, "move ");
\r
2878 strcat(bookMove, bookHit);
\r
2879 HandleMachineMove(bookMove, &first);
\r
2884 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2885 started == STARTED_HOLDINGS ||
\r
2886 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2887 /* Accumulate characters in move list or board */
\r
2888 parse[parse_pos++] = buf[i];
\r
2891 /* Start of game messages. Mostly we detect start of game
\r
2892 when the first board image arrives. On some versions
\r
2893 of the ICS, though, we need to do a "refresh" after starting
\r
2894 to observe in order to get the current board right away. */
\r
2895 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2896 started = STARTED_OBSERVE;
\r
2900 /* Handle auto-observe */
\r
2901 if (appData.autoObserve &&
\r
2902 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2903 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2905 /* Choose the player that was highlighted, if any. */
\r
2906 if (star_match[0][0] == '\033' ||
\r
2907 star_match[1][0] != '\033') {
\r
2908 player = star_match[0];
\r
2910 player = star_match[2];
\r
2912 sprintf(str, "%sobserve %s\n",
\r
2913 ics_prefix, StripHighlightAndTitle(player));
\r
2916 /* Save ratings from notify string */
\r
2917 strcpy(player1Name, star_match[0]);
\r
2918 player1Rating = string_to_rating(star_match[1]);
\r
2919 strcpy(player2Name, star_match[2]);
\r
2920 player2Rating = string_to_rating(star_match[3]);
\r
2922 if (appData.debugMode)
\r
2924 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2925 player1Name, player1Rating,
\r
2926 player2Name, player2Rating);
\r
2931 /* Deal with automatic examine mode after a game,
\r
2932 and with IcsObserving -> IcsExamining transition */
\r
2933 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2934 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2936 int gamenum = atoi(star_match[0]);
\r
2937 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2938 gamenum == ics_gamenum) {
\r
2939 /* We were already playing or observing this game;
\r
2940 no need to refetch history */
\r
2941 gameMode = IcsExamining;
\r
2943 pauseExamForwardMostMove = forwardMostMove;
\r
2944 } else if (currentMove < forwardMostMove) {
\r
2945 ForwardInner(forwardMostMove);
\r
2948 /* I don't think this case really can happen */
\r
2949 SendToICS(ics_prefix);
\r
2950 SendToICS("refresh\n");
\r
2955 /* Error messages */
\r
2956 if (ics_user_moved) {
\r
2957 if (looking_at(buf, &i, "Illegal move") ||
\r
2958 looking_at(buf, &i, "Not a legal move") ||
\r
2959 looking_at(buf, &i, "Your king is in check") ||
\r
2960 looking_at(buf, &i, "It isn't your turn") ||
\r
2961 looking_at(buf, &i, "It is not your move")) {
\r
2962 /* Illegal move */
\r
2963 ics_user_moved = 0;
\r
2964 if (forwardMostMove > backwardMostMove) {
\r
2965 currentMove = --forwardMostMove;
\r
2966 DisplayMove(currentMove - 1); /* before DMError */
\r
2967 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2968 DrawPosition(FALSE, boards[currentMove]);
\r
2970 DisplayBothClocks();
\r
2976 if (looking_at(buf, &i, "still have time") ||
\r
2977 looking_at(buf, &i, "not out of time") ||
\r
2978 looking_at(buf, &i, "either player is out of time") ||
\r
2979 looking_at(buf, &i, "has timeseal; checking")) {
\r
2980 /* We must have called his flag a little too soon */
\r
2981 whiteFlag = blackFlag = FALSE;
\r
2985 if (looking_at(buf, &i, "added * seconds to") ||
\r
2986 looking_at(buf, &i, "seconds were added to")) {
\r
2987 /* Update the clocks */
\r
2988 SendToICS(ics_prefix);
\r
2989 SendToICS("refresh\n");
\r
2993 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2994 ics_clock_paused = TRUE;
\r
2999 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
3000 ics_clock_paused = FALSE;
\r
3005 /* Grab player ratings from the Creating: message.
\r
3006 Note we have to check for the special case when
\r
3007 the ICS inserts things like [white] or [black]. */
\r
3008 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3009 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3011 0 player 1 name (not necessarily white)
\r
3013 2 empty, white, or black (IGNORED)
\r
3014 3 player 2 name (not necessarily black)
\r
3017 The names/ratings are sorted out when the game
\r
3018 actually starts (below).
\r
3020 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3021 player1Rating = string_to_rating(star_match[1]);
\r
3022 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3023 player2Rating = string_to_rating(star_match[4]);
\r
3025 if (appData.debugMode)
\r
3027 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3028 player1Name, player1Rating,
\r
3029 player2Name, player2Rating);
\r
3034 /* Improved generic start/end-of-game messages */
\r
3035 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3036 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3037 /* If tkind == 0: */
\r
3038 /* star_match[0] is the game number */
\r
3039 /* [1] is the white player's name */
\r
3040 /* [2] is the black player's name */
\r
3041 /* For end-of-game: */
\r
3042 /* [3] is the reason for the game end */
\r
3043 /* [4] is a PGN end game-token, preceded by " " */
\r
3044 /* For start-of-game: */
\r
3045 /* [3] begins with "Creating" or "Continuing" */
\r
3046 /* [4] is " *" or empty (don't care). */
\r
3047 int gamenum = atoi(star_match[0]);
\r
3048 char *whitename, *blackname, *why, *endtoken;
\r
3049 ChessMove endtype = (ChessMove) 0;
\r
3052 whitename = star_match[1];
\r
3053 blackname = star_match[2];
\r
3054 why = star_match[3];
\r
3055 endtoken = star_match[4];
\r
3057 whitename = star_match[1];
\r
3058 blackname = star_match[3];
\r
3059 why = star_match[5];
\r
3060 endtoken = star_match[6];
\r
3063 /* Game start messages */
\r
3064 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3065 strncmp(why, "Continuing ", 11) == 0) {
\r
3066 gs_gamenum = gamenum;
\r
3067 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3069 if (appData.zippyPlay) {
\r
3070 ZippyGameStart(whitename, blackname);
\r
3076 /* Game end messages */
\r
3077 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3078 ics_gamenum != gamenum) {
\r
3081 while (endtoken[0] == ' ') endtoken++;
\r
3082 switch (endtoken[0]) {
\r
3085 endtype = GameUnfinished;
\r
3088 endtype = BlackWins;
\r
3091 if (endtoken[1] == '/')
\r
3092 endtype = GameIsDrawn;
\r
3094 endtype = WhiteWins;
\r
3097 GameEnds(endtype, why, GE_ICS);
\r
3099 if (appData.zippyPlay && first.initDone) {
\r
3100 ZippyGameEnd(endtype, why);
\r
3101 if (first.pr == NULL) {
\r
3102 /* Start the next process early so that we'll
\r
3103 be ready for the next challenge */
\r
3104 StartChessProgram(&first);
\r
3106 /* Send "new" early, in case this command takes
\r
3107 a long time to finish, so that we'll be ready
\r
3108 for the next challenge. */
\r
3109 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3110 Reset(TRUE, TRUE);
\r
3116 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3117 looking_at(buf, &i, "no longer observing game *") ||
\r
3118 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3119 if (gameMode == IcsObserving &&
\r
3120 atoi(star_match[0]) == ics_gamenum)
\r
3122 /* icsEngineAnalyze */
\r
3123 if (appData.icsEngineAnalyze) {
\r
3124 ExitAnalyzeMode();
\r
3128 gameMode = IcsIdle;
\r
3130 ics_user_moved = FALSE;
\r
3135 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3136 if (gameMode == IcsExamining &&
\r
3137 atoi(star_match[0]) == ics_gamenum)
\r
3139 gameMode = IcsIdle;
\r
3141 ics_user_moved = FALSE;
\r
3146 /* Advance leftover_start past any newlines we find,
\r
3147 so only partial lines can get reparsed */
\r
3148 if (looking_at(buf, &i, "\n")) {
\r
3149 prevColor = curColor;
\r
3150 if (curColor != ColorNormal) {
\r
3151 if (oldi > next_out) {
\r
3152 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3155 Colorize(ColorNormal, FALSE);
\r
3156 curColor = ColorNormal;
\r
3158 if (started == STARTED_BOARD) {
\r
3159 started = STARTED_NONE;
\r
3160 parse[parse_pos] = NULLCHAR;
\r
3161 ParseBoard12(parse);
\r
3162 ics_user_moved = 0;
\r
3164 /* Send premove here */
\r
3165 if (appData.premove) {
\r
3166 char str[MSG_SIZ];
\r
3167 if (currentMove == 0 &&
\r
3168 gameMode == IcsPlayingWhite &&
\r
3169 appData.premoveWhite) {
\r
3170 sprintf(str, "%s%s\n", ics_prefix,
\r
3171 appData.premoveWhiteText);
\r
3172 if (appData.debugMode)
\r
3173 fprintf(debugFP, "Sending premove:\n");
\r
3175 } else if (currentMove == 1 &&
\r
3176 gameMode == IcsPlayingBlack &&
\r
3177 appData.premoveBlack) {
\r
3178 sprintf(str, "%s%s\n", ics_prefix,
\r
3179 appData.premoveBlackText);
\r
3180 if (appData.debugMode)
\r
3181 fprintf(debugFP, "Sending premove:\n");
\r
3183 } else if (gotPremove) {
\r
3185 ClearPremoveHighlights();
\r
3186 if (appData.debugMode)
\r
3187 fprintf(debugFP, "Sending premove:\n");
\r
3188 UserMoveEvent(premoveFromX, premoveFromY,
\r
3189 premoveToX, premoveToY,
\r
3190 premovePromoChar);
\r
3194 /* Usually suppress following prompt */
\r
3195 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3196 if (looking_at(buf, &i, "*% ")) {
\r
3197 savingComment = FALSE;
\r
3201 } else if (started == STARTED_HOLDINGS) {
\r
3203 char new_piece[MSG_SIZ];
\r
3204 started = STARTED_NONE;
\r
3205 parse[parse_pos] = NULLCHAR;
\r
3206 if (appData.debugMode)
\r
3207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3208 parse, currentMove);
\r
3209 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3210 gamenum == ics_gamenum) {
\r
3211 if (gameInfo.variant == VariantNormal) {
\r
3212 /* [HGM] We seem to switch variant during a game!
\r
3213 * Presumably no holdings were displayed, so we have
\r
3214 * to move the position two files to the right to
\r
3215 * create room for them!
\r
3217 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3218 /* Get a move list just to see the header, which
\r
3219 will tell us whether this is really bug or zh */
\r
3220 if (ics_getting_history == H_FALSE) {
\r
3221 ics_getting_history = H_REQUESTED;
\r
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3226 new_piece[0] = NULLCHAR;
\r
3227 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3228 &gamenum, white_holding, black_holding,
\r
3230 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3231 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3232 /* [HGM] copy holdings to board holdings area */
\r
3233 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3234 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3236 if (appData.zippyPlay && first.initDone) {
\r
3237 ZippyHoldings(white_holding, black_holding,
\r
3241 if (tinyLayout || smallLayout) {
\r
3242 char wh[16], bh[16];
\r
3243 PackHolding(wh, white_holding);
\r
3244 PackHolding(bh, black_holding);
\r
3245 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3246 gameInfo.white, gameInfo.black);
\r
3248 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3249 gameInfo.white, white_holding,
\r
3250 gameInfo.black, black_holding);
\r
3253 DrawPosition(FALSE, boards[currentMove]);
\r
3254 DisplayTitle(str);
\r
3256 /* Suppress following prompt */
\r
3257 if (looking_at(buf, &i, "*% ")) {
\r
3258 savingComment = FALSE;
\r
3265 i++; /* skip unparsed character and loop back */
\r
3268 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3269 started != STARTED_HOLDINGS && i > next_out) {
\r
3270 SendToPlayer(&buf[next_out], i - next_out);
\r
3273 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3275 leftover_len = buf_len - leftover_start;
\r
3276 /* if buffer ends with something we couldn't parse,
\r
3277 reparse it after appending the next read */
\r
3279 } else if (count == 0) {
\r
3280 RemoveInputSource(isr);
\r
3281 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3283 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3288 /* Board style 12 looks like this:
\r
3290 <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
\r
3292 * The "<12> " is stripped before it gets to this routine. The two
\r
3293 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3294 * some chess servers may not have them, or may have only the first.
\r
3295 * Additional trailing fields may be added in the future.
\r
3298 #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"
\r
3300 #define RELATION_OBSERVING_PLAYED 0
\r
3301 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3302 #define RELATION_PLAYING_MYMOVE 1
\r
3303 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3304 #define RELATION_EXAMINING 2
\r
3305 #define RELATION_ISOLATED_BOARD -3
\r
3306 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3309 ParseBoard12(string)
\r
3312 GameMode newGameMode;
\r
3313 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3314 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3315 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3316 char to_play, board_chars[200];
\r
3317 char move_str[500], str[500], elapsed_time[500];
\r
3318 char black[32], white[32];
\r
3320 int prevMove = currentMove;
\r
3322 ChessMove moveType;
\r
3323 int fromX, fromY, toX, toY;
\r
3325 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3326 char *bookHit = NULL; // [HGM] book
\r
3328 fromX = fromY = toX = toY = -1;
\r
3332 if (appData.debugMode)
\r
3333 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3335 move_str[0] = NULLCHAR;
\r
3336 elapsed_time[0] = NULLCHAR;
\r
3337 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3339 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3340 if(string[i] == ' ') { ranks++; files = 0; }
\r
3344 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3345 board_chars[i] = '\0';
\r
3348 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3349 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3350 &gamenum, white, black, &relation, &basetime, &increment,
\r
3351 &white_stren, &black_stren, &white_time, &black_time,
\r
3352 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3356 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3357 DisplayError(str, 0);
\r
3361 /* Convert the move number to internal form */
\r
3362 moveNum = (moveNum - 1) * 2;
\r
3363 if (to_play == 'B') moveNum++;
\r
3364 if (moveNum >= MAX_MOVES) {
\r
3365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3370 switch (relation) {
\r
3371 case RELATION_OBSERVING_PLAYED:
\r
3372 case RELATION_OBSERVING_STATIC:
\r
3373 if (gamenum == -1) {
\r
3374 /* Old ICC buglet */
\r
3375 relation = RELATION_OBSERVING_STATIC;
\r
3377 newGameMode = IcsObserving;
\r
3379 case RELATION_PLAYING_MYMOVE:
\r
3380 case RELATION_PLAYING_NOTMYMOVE:
\r
3382 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3383 IcsPlayingWhite : IcsPlayingBlack;
\r
3385 case RELATION_EXAMINING:
\r
3386 newGameMode = IcsExamining;
\r
3388 case RELATION_ISOLATED_BOARD:
\r
3390 /* Just display this board. If user was doing something else,
\r
3391 we will forget about it until the next board comes. */
\r
3392 newGameMode = IcsIdle;
\r
3394 case RELATION_STARTING_POSITION:
\r
3395 newGameMode = gameMode;
\r
3399 /* Modify behavior for initial board display on move listing
\r
3402 switch (ics_getting_history) {
\r
3406 case H_GOT_REQ_HEADER:
\r
3407 case H_GOT_UNREQ_HEADER:
\r
3408 /* This is the initial position of the current game */
\r
3409 gamenum = ics_gamenum;
\r
3410 moveNum = 0; /* old ICS bug workaround */
\r
3411 if (to_play == 'B') {
\r
3412 startedFromSetupPosition = TRUE;
\r
3413 blackPlaysFirst = TRUE;
\r
3415 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3416 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3417 if (currentMove == 0) currentMove = 1;
\r
3419 newGameMode = gameMode;
\r
3420 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3422 case H_GOT_UNWANTED_HEADER:
\r
3423 /* This is an initial board that we don't want */
\r
3425 case H_GETTING_MOVES:
\r
3426 /* Should not happen */
\r
3427 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3428 ics_getting_history = H_FALSE;
\r
3432 /* Take action if this is the first board of a new game, or of a
\r
3433 different game than is currently being displayed. */
\r
3434 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3435 relation == RELATION_ISOLATED_BOARD) {
\r
3437 /* Forget the old game and get the history (if any) of the new one */
\r
3438 if (gameMode != BeginningOfGame) {
\r
3439 Reset(FALSE, TRUE);
\r
3442 if (appData.autoRaiseBoard) BoardToTop();
\r
3444 if (gamenum == -1) {
\r
3445 newGameMode = IcsIdle;
\r
3446 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3447 appData.getMoveList) {
\r
3448 /* Need to get game history */
\r
3449 ics_getting_history = H_REQUESTED;
\r
3450 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3454 /* Initially flip the board to have black on the bottom if playing
\r
3455 black or if the ICS flip flag is set, but let the user change
\r
3456 it with the Flip View button. */
\r
3457 flipView = appData.autoFlipView ?
\r
3458 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3461 /* Done with values from previous mode; copy in new ones */
\r
3462 gameMode = newGameMode;
\r
3464 ics_gamenum = gamenum;
\r
3465 if (gamenum == gs_gamenum) {
\r
3466 int klen = strlen(gs_kind);
\r
3467 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3468 sprintf(str, "ICS %s", gs_kind);
\r
3469 gameInfo.event = StrSave(str);
\r
3471 gameInfo.event = StrSave("ICS game");
\r
3473 gameInfo.site = StrSave(appData.icsHost);
\r
3474 gameInfo.date = PGNDate();
\r
3475 gameInfo.round = StrSave("-");
\r
3476 gameInfo.white = StrSave(white);
\r
3477 gameInfo.black = StrSave(black);
\r
3478 timeControl = basetime * 60 * 1000;
\r
3479 timeControl_2 = 0;
\r
3480 timeIncrement = increment * 1000;
\r
3481 movesPerSession = 0;
\r
3482 gameInfo.timeControl = TimeControlTagValue();
\r
3483 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3484 if (appData.debugMode) {
\r
3485 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3486 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3487 setbuf(debugFP, NULL);
\r
3490 gameInfo.outOfBook = NULL;
\r
3492 /* Do we have the ratings? */
\r
3493 if (strcmp(player1Name, white) == 0 &&
\r
3494 strcmp(player2Name, black) == 0) {
\r
3495 if (appData.debugMode)
\r
3496 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3497 player1Rating, player2Rating);
\r
3498 gameInfo.whiteRating = player1Rating;
\r
3499 gameInfo.blackRating = player2Rating;
\r
3500 } else if (strcmp(player2Name, white) == 0 &&
\r
3501 strcmp(player1Name, black) == 0) {
\r
3502 if (appData.debugMode)
\r
3503 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3504 player2Rating, player1Rating);
\r
3505 gameInfo.whiteRating = player2Rating;
\r
3506 gameInfo.blackRating = player1Rating;
\r
3508 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3510 /* Silence shouts if requested */
\r
3511 if (appData.quietPlay &&
\r
3512 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3513 SendToICS(ics_prefix);
\r
3514 SendToICS("set shout 0\n");
\r
3518 /* Deal with midgame name changes */
\r
3520 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3521 if (gameInfo.white) free(gameInfo.white);
\r
3522 gameInfo.white = StrSave(white);
\r
3524 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3525 if (gameInfo.black) free(gameInfo.black);
\r
3526 gameInfo.black = StrSave(black);
\r
3530 /* Throw away game result if anything actually changes in examine mode */
\r
3531 if (gameMode == IcsExamining && !newGame) {
\r
3532 gameInfo.result = GameUnfinished;
\r
3533 if (gameInfo.resultDetails != NULL) {
\r
3534 free(gameInfo.resultDetails);
\r
3535 gameInfo.resultDetails = NULL;
\r
3539 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3540 in if they are in a different variation than we are. */
\r
3541 if (pauseExamInvalid) return;
\r
3542 if (pausing && gameMode == IcsExamining) {
\r
3543 if (moveNum <= pauseExamForwardMostMove) {
\r
3544 pauseExamInvalid = TRUE;
\r
3545 forwardMostMove = pauseExamForwardMostMove;
\r
3550 if (appData.debugMode) {
\r
3551 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3553 /* Parse the board */
\r
3554 for (k = 0; k < ranks; k++) {
\r
3555 for (j = 0; j < files; j++)
\r
3556 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3557 if(gameInfo.holdingsWidth > 1) {
\r
3558 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3559 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3562 CopyBoard(boards[moveNum], board);
\r
3563 if (moveNum == 0) {
\r
3564 startedFromSetupPosition =
\r
3565 !CompareBoards(board, initialPosition);
\r
3566 if(startedFromSetupPosition)
\r
3567 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3570 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3571 to make it also work for FRC opening positions. Note that board12
\r
3572 is really defective for later FRC positions, as it has no way to
\r
3573 indicate which Rook can castle if they are on the same side of King.
\r
3574 For the initial position we grant rights to the outermost Rooks,
\r
3575 and remember thos rights, and we then copy them on positions
\r
3576 later in an FRC game. This means WB might not recognize castlings with
\r
3577 Rooks that have moved back to their original position as illegal,
\r
3578 but in ICS mode that is not its job anyway.
\r
3580 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3581 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3584 if(board[0][i] == WhiteRook) j = i;
\r
3585 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3587 if(board[0][i] == WhiteRook) j = i;
\r
3588 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3591 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3594 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3596 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3597 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3598 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3600 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3601 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3603 r = castlingRights[moveNum][0] = initialRights[0];
\r
3604 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3605 r = castlingRights[moveNum][1] = initialRights[1];
\r
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3607 r = castlingRights[moveNum][3] = initialRights[3];
\r
3608 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3609 r = castlingRights[moveNum][4] = initialRights[4];
\r
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3611 /* wildcastle kludge: always assume King has rights */
\r
3612 r = castlingRights[moveNum][2] = initialRights[2];
\r
3613 r = castlingRights[moveNum][5] = initialRights[5];
\r
3615 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3616 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3619 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3620 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3621 /* This was an initial position from a move list, not
\r
3622 the current position */
\r
3626 /* Update currentMove and known move number limits */
\r
3627 newMove = newGame || moveNum > forwardMostMove;
\r
3629 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3630 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3631 takeback = forwardMostMove - moveNum;
\r
3632 for (i = 0; i < takeback; i++) {
\r
3633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3634 SendToProgram("undo\n", &first);
\r
3639 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3640 if (gameMode == IcsExamining && moveNum == 0) {
\r
3641 /* Workaround for ICS limitation: we are not told the wild
\r
3642 type when starting to examine a game. But if we ask for
\r
3643 the move list, the move list header will tell us */
\r
3644 ics_getting_history = H_REQUESTED;
\r
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3648 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3649 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3650 forwardMostMove = moveNum;
\r
3651 if (!pausing || currentMove > forwardMostMove)
\r
3652 currentMove = forwardMostMove;
\r
3654 /* New part of history that is not contiguous with old part */
\r
3655 if (pausing && gameMode == IcsExamining) {
\r
3656 pauseExamInvalid = TRUE;
\r
3657 forwardMostMove = pauseExamForwardMostMove;
\r
3660 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3661 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3662 ics_getting_history = H_REQUESTED;
\r
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3668 /* Update the clocks */
\r
3669 if (strchr(elapsed_time, '.')) {
\r
3670 /* Time is in ms */
\r
3671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3674 /* Time is in seconds */
\r
3675 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3676 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3681 if (appData.zippyPlay && newGame &&
\r
3682 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3683 gameMode != IcsExamining)
\r
3684 ZippyFirstBoard(moveNum, basetime, increment);
\r
3687 /* Put the move on the move list, first converting
\r
3688 to canonical algebraic form. */
\r
3689 if (moveNum > 0) {
\r
3690 if (appData.debugMode) {
\r
3691 if (appData.debugMode) { int f = forwardMostMove;
\r
3692 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3693 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3695 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3696 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3697 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3698 setbuf(debugFP, NULL);
\r
3700 if (moveNum <= backwardMostMove) {
\r
3701 /* We don't know what the board looked like before
\r
3702 this move. Punt. */
\r
3703 strcpy(parseList[moveNum - 1], move_str);
\r
3704 strcat(parseList[moveNum - 1], " ");
\r
3705 strcat(parseList[moveNum - 1], elapsed_time);
\r
3706 moveList[moveNum - 1][0] = NULLCHAR;
\r
3707 } else if (strcmp(move_str, "none") == 0) {
\r
3708 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3709 /* Again, we don't know what the board looked like;
\r
3710 this is really the start of the game. */
\r
3711 parseList[moveNum - 1][0] = NULLCHAR;
\r
3712 moveList[moveNum - 1][0] = NULLCHAR;
\r
3713 backwardMostMove = moveNum;
\r
3714 startedFromSetupPosition = TRUE;
\r
3715 fromX = fromY = toX = toY = -1;
\r
3717 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3718 // So we parse the long-algebraic move string in stead of the SAN move
\r
3719 int valid; char buf[MSG_SIZ], *prom;
\r
3721 // str looks something like "Q/a1-a2"; kill the slash
\r
3722 if(str[1] == '/')
\r
3723 sprintf(buf, "%c%s", str[0], str+2);
\r
3724 else strcpy(buf, str); // might be castling
\r
3725 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3726 strcat(buf, prom); // long move lacks promo specification!
\r
3727 if(!appData.testLegality) {
\r
3728 if(appData.debugMode)
\r
3729 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3730 strcpy(move_str, buf);
\r
3732 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3733 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3734 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3735 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3736 // end of long SAN patch
\r
3738 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3739 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3740 fromY, fromX, toY, toX, promoChar,
\r
3741 parseList[moveNum-1]);
\r
3742 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3743 castlingRights[moveNum]) ) {
\r
3745 case MT_STALEMATE:
\r
3749 if(gameInfo.variant != VariantShogi)
\r
3750 strcat(parseList[moveNum - 1], "+");
\r
3752 case MT_CHECKMATE:
\r
3753 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
\r
3754 strcat(parseList[moveNum - 1], "#");
\r
3757 strcat(parseList[moveNum - 1], " ");
\r
3758 strcat(parseList[moveNum - 1], elapsed_time);
\r
3759 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3760 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3761 strcat(moveList[moveNum - 1], "\n");
\r
3763 /* Move from ICS was illegal!? Punt. */
\r
3764 if (appData.debugMode) {
\r
3765 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3766 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3769 if (appData.testLegality && appData.debugMode) {
\r
3770 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3771 DisplayError(str, 0);
\r
3774 strcpy(parseList[moveNum - 1], move_str);
\r
3775 strcat(parseList[moveNum - 1], " ");
\r
3776 strcat(parseList[moveNum - 1], elapsed_time);
\r
3777 moveList[moveNum - 1][0] = NULLCHAR;
\r
3778 fromX = fromY = toX = toY = -1;
\r
3781 if (appData.debugMode) {
\r
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3783 setbuf(debugFP, NULL);
\r
3787 /* Send move to chess program (BEFORE animating it). */
\r
3788 if (appData.zippyPlay && !newGame && newMove &&
\r
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3796 DisplayError(str, 0);
\r
3798 if (first.sendTime) {
\r
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3802 if (firstMove && !bookHit) {
\r
3803 firstMove = FALSE;
\r
3804 if (first.useColors) {
\r
3805 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3807 "black\ngo\n", &first);
\r
3809 SendToProgram("go\n", &first);
\r
3811 first.maybeThinking = TRUE;
\r
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3817 DisplayError(str, 0);
\r
3819 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3820 SendMoveToProgram(moveNum - 1, &first);
\r
3827 if (moveNum > 0 && !gotPremove) {
\r
3828 /* If move comes from a remote source, animate it. If it
\r
3829 isn't remote, it will have already been animated. */
\r
3830 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3831 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3833 if (!pausing && appData.highlightLastMove) {
\r
3834 SetHighlights(fromX, fromY, toX, toY);
\r
3838 /* Start the clocks */
\r
3839 whiteFlag = blackFlag = FALSE;
\r
3840 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3841 if (ticking == 0) {
\r
3842 ics_clock_paused = TRUE;
\r
3844 } else if (ticking == 1) {
\r
3845 ics_clock_paused = FALSE;
\r
3847 if (gameMode == IcsIdle ||
\r
3848 relation == RELATION_OBSERVING_STATIC ||
\r
3849 relation == RELATION_EXAMINING ||
\r
3851 DisplayBothClocks();
\r
3855 /* Display opponents and material strengths */
\r
3856 if (gameInfo.variant != VariantBughouse &&
\r
3857 gameInfo.variant != VariantCrazyhouse) {
\r
3858 if (tinyLayout || smallLayout) {
\r
3859 if(gameInfo.variant == VariantNormal)
\r
3860 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3861 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3862 basetime, increment);
\r
3864 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3865 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3866 basetime, increment, (int) gameInfo.variant);
\r
3868 if(gameInfo.variant == VariantNormal)
\r
3869 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3870 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3871 basetime, increment);
\r
3873 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3875 basetime, increment, VariantName(gameInfo.variant));
\r
3877 DisplayTitle(str);
\r
3878 if (appData.debugMode) {
\r
3879 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3884 /* Display the board */
\r
3887 if (appData.premove)
\r
3888 if (!gotPremove ||
\r
3889 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3890 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3891 ClearPremoveHighlights();
\r
3893 DrawPosition(FALSE, boards[currentMove]);
\r
3894 DisplayMove(moveNum - 1);
\r
3895 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3899 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3901 if(bookHit) { // [HGM] book: simulate book reply
\r
3902 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3904 programStats.nodes = programStats.depth = programStats.time =
\r
3905 programStats.score = programStats.got_only_move = 0;
\r
3906 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3908 strcpy(bookMove, "move ");
\r
3909 strcat(bookMove, bookHit);
\r
3910 HandleMachineMove(bookMove, &first);
\r
3916 GetMoveListEvent()
\r
3918 char buf[MSG_SIZ];
\r
3919 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3920 ics_getting_history = H_REQUESTED;
\r
3921 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3927 AnalysisPeriodicEvent(force)
\r
3930 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3931 && !force) || !appData.periodicUpdates)
\r
3934 /* Send . command to Crafty to collect stats */
\r
3935 SendToProgram(".\n", &first);
\r
3937 /* Don't send another until we get a response (this makes
\r
3938 us stop sending to old Crafty's which don't understand
\r
3939 the "." command (sending illegal cmds resets node count & time,
\r
3940 which looks bad)) */
\r
3941 programStats.ok_to_send = 0;
\r
3945 SendMoveToProgram(moveNum, cps)
\r
3947 ChessProgramState *cps;
\r
3949 char buf[MSG_SIZ];
\r
3951 if (cps->useUsermove) {
\r
3952 SendToProgram("usermove ", cps);
\r
3954 if (cps->useSAN) {
\r
3956 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3957 int len = space - parseList[moveNum];
\r
3958 memcpy(buf, parseList[moveNum], len);
\r
3959 buf[len++] = '\n';
\r
3960 buf[len] = NULLCHAR;
\r
3962 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3964 SendToProgram(buf, cps);
\r
3966 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3967 AlphaRank(moveList[moveNum], 4);
\r
3968 SendToProgram(moveList[moveNum], cps);
\r
3969 AlphaRank(moveList[moveNum], 4); // and back
\r
3971 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3972 * the engine. It would be nice to have a better way to identify castle
\r
3974 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3975 && cps->useOOCastle) {
\r
3976 int fromX = moveList[moveNum][0] - AAA;
\r
3977 int fromY = moveList[moveNum][1] - ONE;
\r
3978 int toX = moveList[moveNum][2] - AAA;
\r
3979 int toY = moveList[moveNum][3] - ONE;
\r
3980 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3981 && boards[moveNum][toY][toX] == WhiteRook)
\r
3982 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3983 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3984 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3985 else SendToProgram("O-O-O\n", cps);
\r
3987 else SendToProgram(moveList[moveNum], cps);
\r
3989 else SendToProgram(moveList[moveNum], cps);
\r
3990 /* End of additions by Tord */
\r
3993 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3994 /* Send 'go' if we are in a mode where machine should play. */
\r
3995 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3996 (gameMode == TwoMachinesPlay ||
\r
3998 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
4000 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
4001 SendToProgram("go\n", cps);
\r
4002 if (appData.debugMode) {
\r
4003 fprintf(debugFP, "(extra)\n");
\r
4006 setboardSpoiledMachineBlack = 0;
\r
4010 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4011 ChessMove moveType;
\r
4012 int fromX, fromY, toX, toY;
\r
4014 char user_move[MSG_SIZ];
\r
4016 switch (moveType) {
\r
4018 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4019 (int)moveType, fromX, fromY, toX, toY);
\r
4020 DisplayError(user_move + strlen("say "), 0);
\r
4022 case WhiteKingSideCastle:
\r
4023 case BlackKingSideCastle:
\r
4024 case WhiteQueenSideCastleWild:
\r
4025 case BlackQueenSideCastleWild:
\r
4027 case WhiteHSideCastleFR:
\r
4028 case BlackHSideCastleFR:
\r
4030 sprintf(user_move, "o-o\n");
\r
4032 case WhiteQueenSideCastle:
\r
4033 case BlackQueenSideCastle:
\r
4034 case WhiteKingSideCastleWild:
\r
4035 case BlackKingSideCastleWild:
\r
4037 case WhiteASideCastleFR:
\r
4038 case BlackASideCastleFR:
\r
4040 sprintf(user_move, "o-o-o\n");
\r
4042 case WhitePromotionQueen:
\r
4043 case BlackPromotionQueen:
\r
4044 case WhitePromotionRook:
\r
4045 case BlackPromotionRook:
\r
4046 case WhitePromotionBishop:
\r
4047 case BlackPromotionBishop:
\r
4048 case WhitePromotionKnight:
\r
4049 case BlackPromotionKnight:
\r
4050 case WhitePromotionKing:
\r
4051 case BlackPromotionKing:
\r
4052 case WhitePromotionChancellor:
\r
4053 case BlackPromotionChancellor:
\r
4054 case WhitePromotionArchbishop:
\r
4055 case BlackPromotionArchbishop:
\r
4056 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4057 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4058 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4059 PieceToChar(WhiteFerz));
\r
4060 else if(gameInfo.variant == VariantGreat)
\r
4061 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4062 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4063 PieceToChar(WhiteMan));
\r
4065 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4066 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4067 PieceToChar(PromoPiece(moveType)));
\r
4071 sprintf(user_move, "%c@%c%c\n",
\r
4072 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4073 AAA + toX, ONE + toY);
\r
4076 case WhiteCapturesEnPassant:
\r
4077 case BlackCapturesEnPassant:
\r
4078 case IllegalMove: /* could be a variant we don't quite understand */
\r
4079 sprintf(user_move, "%c%c%c%c\n",
\r
4080 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4083 SendToICS(user_move);
\r
4087 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4088 int rf, ff, rt, ft;
\r
4092 if (rf == DROP_RANK) {
\r
4093 sprintf(move, "%c@%c%c\n",
\r
4094 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4096 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4097 sprintf(move, "%c%c%c%c\n",
\r
4098 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4100 sprintf(move, "%c%c%c%c%c\n",
\r
4101 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4107 ProcessICSInitScript(f)
\r
4110 char buf[MSG_SIZ];
\r
4112 while (fgets(buf, MSG_SIZ, f)) {
\r
4113 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4120 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4122 AlphaRank(char *move, int n)
\r
4124 // char *p = move, c; int x, y;
\r
4126 if (appData.debugMode) {
\r
4127 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4130 if(move[1]=='*' &&
\r
4131 move[2]>='0' && move[2]<='9' &&
\r
4132 move[3]>='a' && move[3]<='x' ) {
\r
4134 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4135 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4137 if(move[0]>='0' && move[0]<='9' &&
\r
4138 move[1]>='a' && move[1]<='x' &&
\r
4139 move[2]>='0' && move[2]<='9' &&
\r
4140 move[3]>='a' && move[3]<='x' ) {
\r
4141 /* input move, Shogi -> normal */
\r
4142 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4143 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4144 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4145 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4147 if(move[1]=='@' &&
\r
4148 move[3]>='0' && move[3]<='9' &&
\r
4149 move[2]>='a' && move[2]<='x' ) {
\r
4151 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4152 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4155 move[0]>='a' && move[0]<='x' &&
\r
4156 move[3]>='0' && move[3]<='9' &&
\r
4157 move[2]>='a' && move[2]<='x' ) {
\r
4158 /* output move, normal -> Shogi */
\r
4159 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4160 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4161 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4162 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4163 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4165 if (appData.debugMode) {
\r
4166 fprintf(debugFP, " out = '%s'\n", move);
\r
4170 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4172 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4175 ChessMove *moveType;
\r
4176 int *fromX, *fromY, *toX, *toY;
\r
4179 if (appData.debugMode) {
\r
4180 fprintf(debugFP, "move to parse: %s\n", move);
\r
4182 *moveType = yylexstr(moveNum, move);
\r
4184 switch (*moveType) {
\r
4185 case WhitePromotionChancellor:
\r
4186 case BlackPromotionChancellor:
\r
4187 case WhitePromotionArchbishop:
\r
4188 case BlackPromotionArchbishop:
\r
4189 case WhitePromotionQueen:
\r
4190 case BlackPromotionQueen:
\r
4191 case WhitePromotionRook:
\r
4192 case BlackPromotionRook:
\r
4193 case WhitePromotionBishop:
\r
4194 case BlackPromotionBishop:
\r
4195 case WhitePromotionKnight:
\r
4196 case BlackPromotionKnight:
\r
4197 case WhitePromotionKing:
\r
4198 case BlackPromotionKing:
\r
4200 case WhiteCapturesEnPassant:
\r
4201 case BlackCapturesEnPassant:
\r
4202 case WhiteKingSideCastle:
\r
4203 case WhiteQueenSideCastle:
\r
4204 case BlackKingSideCastle:
\r
4205 case BlackQueenSideCastle:
\r
4206 case WhiteKingSideCastleWild:
\r
4207 case WhiteQueenSideCastleWild:
\r
4208 case BlackKingSideCastleWild:
\r
4209 case BlackQueenSideCastleWild:
\r
4210 /* Code added by Tord: */
\r
4211 case WhiteHSideCastleFR:
\r
4212 case WhiteASideCastleFR:
\r
4213 case BlackHSideCastleFR:
\r
4214 case BlackASideCastleFR:
\r
4215 /* End of code added by Tord */
\r
4216 case IllegalMove: /* bug or odd chess variant */
\r
4217 *fromX = currentMoveString[0] - AAA;
\r
4218 *fromY = currentMoveString[1] - ONE;
\r
4219 *toX = currentMoveString[2] - AAA;
\r
4220 *toY = currentMoveString[3] - ONE;
\r
4221 *promoChar = currentMoveString[4];
\r
4222 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4223 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4224 if (appData.debugMode) {
\r
4225 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4227 *fromX = *fromY = *toX = *toY = 0;
\r
4230 if (appData.testLegality) {
\r
4231 return (*moveType != IllegalMove);
\r
4233 return !(fromX == fromY && toX == toY);
\r
4238 *fromX = *moveType == WhiteDrop ?
\r
4239 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4240 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4241 *fromY = DROP_RANK;
\r
4242 *toX = currentMoveString[2] - AAA;
\r
4243 *toY = currentMoveString[3] - ONE;
\r
4244 *promoChar = NULLCHAR;
\r
4247 case AmbiguousMove:
\r
4248 case ImpossibleMove:
\r
4249 case (ChessMove) 0: /* end of file */
\r
4258 if (appData.debugMode) {
\r
4259 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4262 *fromX = *fromY = *toX = *toY = 0;
\r
4263 *promoChar = NULLCHAR;
\r
4268 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
\r
4269 // All positions will have equal probability, but the current method will not provide a unique
\r
4270 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4275 int squaresLeft[4];
\r
4276 int piecesLeft[(int)BlackPawn];
\r
4277 int seed, nrOfShuffles;
\r
4279 void GetPositionNumber()
\r
4280 { // sets global variable seed
\r
4283 seed = appData.defaultFrcPosition;
\r
4284 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4285 for(i=0; i<50; i++) seed += random();
\r
4286 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4287 if(seed<0) seed = -seed;
\r
4291 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4292 // put the piece on the (n-1)-th empty squares of the given shade
\r
4296 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4297 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4298 board[rank][i] = (ChessSquare) pieceType;
\r
4299 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4300 squaresLeft[ANY]--;
\r
4301 piecesLeft[pieceType]--;
\r
4309 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4310 // calculate where the next piece goes, (any empty square), and put it there
\r
4314 i = seed % squaresLeft[shade];
\r
4315 nrOfShuffles *= squaresLeft[shade];
\r
4316 seed /= squaresLeft[shade];
\r
4317 put(board, pieceType, rank, i, shade);
\r
4320 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4321 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4323 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4325 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4326 i = seed % k; // pick one
\r
4327 nrOfShuffles *= k;
\r
4329 while(i >= j) i -= j--;
\r
4330 j = n - 1 - j; i += j;
\r
4331 put(board, pieceType, rank, j, ANY);
\r
4332 put(board, pieceType, rank, i, ANY);
\r
4335 void SetUpShuffle(Board board, int number)
\r
4337 int i, p, first=1;
\r
4339 GetPositionNumber(); nrOfShuffles = 1;
\r
4341 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4342 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4343 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4345 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4347 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4348 p = (int) board[0][i];
\r
4349 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4350 board[0][i] = EmptySquare;
\r
4353 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4354 // shuffles restricted to allow normal castling put KRR first
\r
4355 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4356 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4357 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4358 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4359 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4360 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4361 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4362 put(board, WhiteRook, 0, 0, ANY);
\r
4363 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4366 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4367 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4368 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4369 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4370 while(piecesLeft[p] >= 2) {
\r
4371 AddOnePiece(board, p, 0, LITE);
\r
4372 AddOnePiece(board, p, 0, DARK);
\r
4374 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4377 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4378 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4379 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4380 if(p == (int)WhiteRook) continue;
\r
4381 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4382 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4385 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4387 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4388 // Last King gets castling rights
\r
4389 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4390 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4391 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4394 while(piecesLeft[(int)WhiteKing]) {
\r
4395 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4396 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4401 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4402 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4405 // Only Rooks can be left; simply place them all
\r
4406 while(piecesLeft[(int)WhiteRook]) {
\r
4407 i = put(board, WhiteRook, 0, 0, ANY);
\r
4408 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4411 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4413 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4416 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4417 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4420 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4423 int SetCharTable( char *table, const char * map )
\r
4424 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4425 /* Basically a safe strcpy that uses the last character as King */
\r
4427 int result = FALSE; int NrPieces;
\r
4429 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4430 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4431 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4433 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4434 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4435 table[i] = map[i];
\r
4436 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4438 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4439 table[(int) BlackKing] = map[NrPieces-1];
\r
4447 void Prelude(Board board)
\r
4448 { // [HGM] superchess: random selection of exo-pieces
\r
4449 int i, j, k; ChessSquare p;
\r
4450 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4452 GetPositionNumber(); // use FRC position number
\r
4454 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4455 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4456 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4457 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4460 j = seed%4; seed /= 4;
\r
4461 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4462 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4463 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4464 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4465 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4466 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4467 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4468 j = seed%3; seed /= 3;
\r
4469 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4470 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4471 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4472 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4473 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4474 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4475 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4476 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4477 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4478 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4479 put(board, exoPieces[0], 0, 0, ANY);
\r
4480 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4484 InitPosition(redraw)
\r
4487 ChessSquare (* pieces)[BOARD_SIZE];
\r
4488 int i, j, pawnRow, overrule,
\r
4489 oldx = gameInfo.boardWidth,
\r
4490 oldy = gameInfo.boardHeight,
\r
4491 oldh = gameInfo.holdingsWidth,
\r
4492 oldv = gameInfo.variant;
\r
4494 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4495 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4497 /* [AS] Initialize pv info list [HGM] and game status */
\r
4499 for( i=0; i<MAX_MOVES; i++ ) {
\r
4500 pvInfoList[i].depth = 0;
\r
4501 epStatus[i]=EP_NONE;
\r
4502 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4505 initialRulePlies = 0; /* 50-move counter start */
\r
4507 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4508 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4512 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4513 /* the initialized data only consist of the two backranks. The switch */
\r
4514 /* selects which one we will use, which is than copied to the Board */
\r
4515 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4516 /* empty squares. This initial position is then copied to boards[0], */
\r
4517 /* possibly after shuffling, so that it remains available. */
\r
4519 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4520 gameInfo.boardWidth = 8;
\r
4521 gameInfo.boardHeight = 8;
\r
4522 gameInfo.holdingsSize = 0;
\r
4523 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4524 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4525 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4527 switch (gameInfo.variant) {
\r
4528 case VariantFischeRandom:
\r
4529 shuffleOpenings = TRUE;
\r
4531 pieces = FIDEArray;
\r
4533 case VariantShatranj:
\r
4534 pieces = ShatranjArray;
\r
4535 nrCastlingRights = 0;
\r
4536 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4538 case VariantTwoKings:
\r
4539 pieces = twoKingsArray;
\r
4541 case VariantCapaRandom:
\r
4542 shuffleOpenings = TRUE;
\r
4543 case VariantCapablanca:
\r
4544 pieces = CapablancaArray;
\r
4545 gameInfo.boardWidth = 10;
\r
4546 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4548 case VariantGothic:
\r
4549 pieces = GothicArray;
\r
4550 gameInfo.boardWidth = 10;
\r
4551 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4553 case VariantJanus:
\r
4554 pieces = JanusArray;
\r
4555 gameInfo.boardWidth = 10;
\r
4556 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4557 nrCastlingRights = 6;
\r
4558 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4559 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4560 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4561 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4562 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4563 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4565 case VariantFalcon:
\r
4566 pieces = FalconArray;
\r
4567 gameInfo.boardWidth = 10;
\r
4568 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4570 case VariantXiangqi:
\r
4571 pieces = XiangqiArray;
\r
4572 gameInfo.boardWidth = 9;
\r
4573 gameInfo.boardHeight = 10;
\r
4574 nrCastlingRights = 0;
\r
4575 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4577 case VariantShogi:
\r
4578 pieces = ShogiArray;
\r
4579 gameInfo.boardWidth = 9;
\r
4580 gameInfo.boardHeight = 9;
\r
4581 gameInfo.holdingsSize = 7;
\r
4582 nrCastlingRights = 0;
\r
4583 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4585 case VariantCourier:
\r
4586 pieces = CourierArray;
\r
4587 gameInfo.boardWidth = 12;
\r
4588 nrCastlingRights = 0;
\r
4589 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4590 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4592 case VariantKnightmate:
\r
4593 pieces = KnightmateArray;
\r
4594 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4596 case VariantFairy:
\r
4597 pieces = fairyArray;
\r
4598 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4600 case VariantGreat:
\r
4601 pieces = GreatArray;
\r
4602 gameInfo.boardWidth = 10;
\r
4603 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4604 gameInfo.holdingsSize = 8;
\r
4606 case VariantSuper:
\r
4607 pieces = FIDEArray;
\r
4608 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4609 gameInfo.holdingsSize = 8;
\r
4610 startedFromSetupPosition = TRUE;
\r
4612 case VariantCrazyhouse:
\r
4613 case VariantBughouse:
\r
4614 pieces = FIDEArray;
\r
4615 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4616 gameInfo.holdingsSize = 5;
\r
4618 case VariantWildCastle:
\r
4619 pieces = FIDEArray;
\r
4620 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4621 shuffleOpenings = 1;
\r
4623 case VariantNoCastle:
\r
4624 pieces = FIDEArray;
\r
4625 nrCastlingRights = 0;
\r
4626 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4627 /* !!?unconstrained back-rank shuffle */
\r
4628 shuffleOpenings = 1;
\r
4633 if(appData.NrFiles >= 0) {
\r
4634 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4635 gameInfo.boardWidth = appData.NrFiles;
\r
4637 if(appData.NrRanks >= 0) {
\r
4638 gameInfo.boardHeight = appData.NrRanks;
\r
4640 if(appData.holdingsSize >= 0) {
\r
4641 i = appData.holdingsSize;
\r
4642 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4643 gameInfo.holdingsSize = i;
\r
4645 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4646 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4647 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4649 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4650 if(pawnRow < 1) pawnRow = 1;
\r
4652 /* User pieceToChar list overrules defaults */
\r
4653 if(appData.pieceToCharTable != NULL)
\r
4654 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4656 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4658 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4659 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4660 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4661 initialPosition[i][j] = s;
\r
4663 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4664 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4665 initialPosition[pawnRow][j] = WhitePawn;
\r
4666 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4667 if(gameInfo.variant == VariantXiangqi) {
\r
4669 initialPosition[pawnRow][j] =
\r
4670 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4671 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4672 initialPosition[2][j] = WhiteCannon;
\r
4673 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4677 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4679 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4682 initialPosition[1][j] = WhiteBishop;
\r
4683 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4685 initialPosition[1][j] = WhiteRook;
\r
4686 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4689 if( nrCastlingRights == -1) {
\r
4690 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4691 /* This sets default castling rights from none to normal corners */
\r
4692 /* Variants with other castling rights must set them themselves above */
\r
4693 nrCastlingRights = 6;
\r
4695 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4696 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4697 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4698 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4699 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4700 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4703 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4704 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4705 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4706 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4707 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4708 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4711 if(gameInfo.variant == VariantFischeRandom) {
\r
4712 if( appData.defaultFrcPosition < 0 ) {
\r
4713 ShuffleFRC( initialPosition );
\r
4716 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4718 startedFromSetupPosition = TRUE;
\r
4721 if (appData.debugMode) {
\r
4722 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4724 if(shuffleOpenings) {
\r
4725 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4726 startedFromSetupPosition = TRUE;
\r
4729 if(startedFromPositionFile) {
\r
4730 /* [HGM] loadPos: use PositionFile for every new game */
\r
4731 CopyBoard(initialPosition, filePosition);
\r
4732 for(i=0; i<nrCastlingRights; i++)
\r
4733 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4734 startedFromSetupPosition = TRUE;
\r
4737 CopyBoard(boards[0], initialPosition);
\r
4739 if(oldx != gameInfo.boardWidth ||
\r
4740 oldy != gameInfo.boardHeight ||
\r
4741 oldh != gameInfo.holdingsWidth
\r
4743 || oldv == VariantGothic || // For licensing popups
\r
4744 gameInfo.variant == VariantGothic
\r
4747 || oldv == VariantFalcon ||
\r
4748 gameInfo.variant == VariantFalcon
\r
4751 InitDrawingSizes(-2 ,0);
\r
4754 DrawPosition(TRUE, boards[currentMove]);
\r
4758 SendBoard(cps, moveNum)
\r
4759 ChessProgramState *cps;
\r
4762 char message[MSG_SIZ];
\r
4764 if (cps->useSetboard) {
\r
4765 char* fen = PositionToFEN(moveNum, cps->fenOverride);
\r
4766 sprintf(message, "setboard %s\n", fen);
\r
4767 SendToProgram(message, cps);
\r
4773 /* Kludge to set black to move, avoiding the troublesome and now
\r
4774 * deprecated "black" command.
\r
4776 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4778 SendToProgram("edit\n", cps);
\r
4779 SendToProgram("#\n", cps);
\r
4780 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4781 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4782 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4783 if ((int) *bp < (int) BlackPawn) {
\r
4784 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4785 AAA + j, ONE + i);
\r
4786 if(message[0] == '+' || message[0] == '~') {
\r
4787 sprintf(message, "%c%c%c+\n",
\r
4788 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4789 AAA + j, ONE + i);
\r
4791 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4792 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4793 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4795 SendToProgram(message, cps);
\r
4800 SendToProgram("c\n", cps);
\r
4801 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4802 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4803 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4804 if (((int) *bp != (int) EmptySquare)
\r
4805 && ((int) *bp >= (int) BlackPawn)) {
\r
4806 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4807 AAA + j, ONE + i);
\r
4808 if(message[0] == '+' || message[0] == '~') {
\r
4809 sprintf(message, "%c%c%c+\n",
\r
4810 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4811 AAA + j, ONE + i);
\r
4813 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4814 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4815 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4817 SendToProgram(message, cps);
\r
4822 SendToProgram(".\n", cps);
\r
4824 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4828 IsPromotion(fromX, fromY, toX, toY)
\r
4829 int fromX, fromY, toX, toY;
\r
4831 /* [HGM] add Shogi promotions */
\r
4832 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4833 ChessSquare piece;
\r
4835 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4836 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4837 /* [HGM] Note to self: line above also weeds out drops */
\r
4838 piece = boards[currentMove][fromY][fromX];
\r
4839 if(gameInfo.variant == VariantShogi) {
\r
4840 promotionZoneSize = 3;
\r
4841 highestPromotingPiece = (int)WhiteKing;
\r
4842 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4843 and if in normal chess we then allow promotion to King, why not
\r
4844 allow promotion of other piece in Shogi? */
\r
4846 if((int)piece >= BlackPawn) {
\r
4847 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4849 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4851 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4852 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4854 return ( (int)piece <= highestPromotingPiece );
\r
4858 InPalace(row, column)
\r
4860 { /* [HGM] for Xiangqi */
\r
4861 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4862 column < (BOARD_WIDTH + 4)/2 &&
\r
4863 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4868 PieceForSquare (x, y)
\r
4872 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4875 return boards[currentMove][y][x];
\r
4879 OKToStartUserMove(x, y)
\r
4882 ChessSquare from_piece;
\r
4885 if (matchMode) return FALSE;
\r
4886 if (gameMode == EditPosition) return TRUE;
\r
4888 if (x >= 0 && y >= 0)
\r
4889 from_piece = boards[currentMove][y][x];
\r
4891 from_piece = EmptySquare;
\r
4893 if (from_piece == EmptySquare) return FALSE;
\r
4895 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4896 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4898 switch (gameMode) {
\r
4899 case PlayFromGameFile:
\r
4901 case TwoMachinesPlay:
\r
4905 case IcsObserving:
\r
4909 case MachinePlaysWhite:
\r
4910 case IcsPlayingBlack:
\r
4911 if (appData.zippyPlay) return FALSE;
\r
4912 if (white_piece) {
\r
4913 DisplayMoveError(_("You are playing Black"));
\r
4918 case MachinePlaysBlack:
\r
4919 case IcsPlayingWhite:
\r
4920 if (appData.zippyPlay) return FALSE;
\r
4921 if (!white_piece) {
\r
4922 DisplayMoveError(_("You are playing White"));
\r
4928 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4929 DisplayMoveError(_("It is White's turn"));
\r
4932 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4933 DisplayMoveError(_("It is Black's turn"));
\r
4936 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4937 /* Editing correspondence game history */
\r
4938 /* Could disallow this or prompt for confirmation */
\r
4939 cmailOldMove = -1;
\r
4941 if (currentMove < forwardMostMove) {
\r
4942 /* Discarding moves */
\r
4943 /* Could prompt for confirmation here,
\r
4944 but I don't think that's such a good idea */
\r
4945 forwardMostMove = currentMove;
\r
4949 case BeginningOfGame:
\r
4950 if (appData.icsActive) return FALSE;
\r
4951 if (!appData.noChessProgram) {
\r
4952 if (!white_piece) {
\r
4953 DisplayMoveError(_("You are playing White"));
\r
4960 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4961 DisplayMoveError(_("It is White's turn"));
\r
4964 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4965 DisplayMoveError(_("It is Black's turn"));
\r
4971 case IcsExamining:
\r
4974 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4975 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4976 DisplayMoveError(_("Displayed position is not current"));
\r
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4984 int lastLoadGameUseList = FALSE;
\r
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4990 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4991 int fromX, fromY, toX, toY;
\r
4994 ChessMove moveType;
\r
4995 ChessSquare pdown, pup;
\r
4997 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4998 if ((fromX == toX) && (fromY == toY)) {
\r
4999 return ImpossibleMove;
\r
5002 /* [HGM] suppress all moves into holdings area and guard band */
\r
5003 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5004 return ImpossibleMove;
\r
5006 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5007 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5008 pdown = boards[currentMove][fromY][fromX];
\r
5009 pup = boards[currentMove][toY][toX];
\r
5010 if ( gameMode != EditPosition &&
\r
5011 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5012 WhitePawn <= pup && pup < BlackPawn ||
\r
5013 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5014 BlackPawn <= pup && pup < EmptySquare
\r
5015 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5016 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5017 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5019 return ImpossibleMove;
\r
5021 /* Check if the user is playing in turn. This is complicated because we
\r
5022 let the user "pick up" a piece before it is his turn. So the piece he
\r
5023 tried to pick up may have been captured by the time he puts it down!
\r
5024 Therefore we use the color the user is supposed to be playing in this
\r
5025 test, not the color of the piece that is currently on the starting
\r
5026 square---except in EditGame mode, where the user is playing both
\r
5027 sides; fortunately there the capture race can't happen. (It can
\r
5028 now happen in IcsExamining mode, but that's just too bad. The user
\r
5029 will get a somewhat confusing message in that case.)
\r
5032 switch (gameMode) {
\r
5033 case PlayFromGameFile:
\r
5035 case TwoMachinesPlay:
\r
5037 case IcsObserving:
\r
5039 /* We switched into a game mode where moves are not accepted,
\r
5040 perhaps while the mouse button was down. */
\r
5041 return ImpossibleMove;
\r
5043 case MachinePlaysWhite:
\r
5044 /* User is moving for Black */
\r
5045 if (WhiteOnMove(currentMove)) {
\r
5046 DisplayMoveError(_("It is White's turn"));
\r
5047 return ImpossibleMove;
\r
5051 case MachinePlaysBlack:
\r
5052 /* User is moving for White */
\r
5053 if (!WhiteOnMove(currentMove)) {
\r
5054 DisplayMoveError(_("It is Black's turn"));
\r
5055 return ImpossibleMove;
\r
5060 case IcsExamining:
\r
5061 case BeginningOfGame:
\r
5064 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5065 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5066 /* User is moving for Black */
\r
5067 if (WhiteOnMove(currentMove)) {
\r
5068 DisplayMoveError(_("It is White's turn"));
\r
5069 return ImpossibleMove;
\r
5072 /* User is moving for White */
\r
5073 if (!WhiteOnMove(currentMove)) {
\r
5074 DisplayMoveError(_("It is Black's turn"));
\r
5075 return ImpossibleMove;
\r
5080 case IcsPlayingBlack:
\r
5081 /* User is moving for Black */
\r
5082 if (WhiteOnMove(currentMove)) {
\r
5083 if (!appData.premove) {
\r
5084 DisplayMoveError(_("It is White's turn"));
\r
5085 } else if (toX >= 0 && toY >= 0) {
\r
5088 premoveFromX = fromX;
\r
5089 premoveFromY = fromY;
\r
5090 premovePromoChar = promoChar;
\r
5092 if (appData.debugMode)
\r
5093 fprintf(debugFP, "Got premove: fromX %d,"
\r
5094 "fromY %d, toX %d, toY %d\n",
\r
5095 fromX, fromY, toX, toY);
\r
5097 return ImpossibleMove;
\r
5101 case IcsPlayingWhite:
\r
5102 /* User is moving for White */
\r
5103 if (!WhiteOnMove(currentMove)) {
\r
5104 if (!appData.premove) {
\r
5105 DisplayMoveError(_("It is Black's turn"));
\r
5106 } else if (toX >= 0 && toY >= 0) {
\r
5109 premoveFromX = fromX;
\r
5110 premoveFromY = fromY;
\r
5111 premovePromoChar = promoChar;
\r
5113 if (appData.debugMode)
\r
5114 fprintf(debugFP, "Got premove: fromX %d,"
\r
5115 "fromY %d, toX %d, toY %d\n",
\r
5116 fromX, fromY, toX, toY);
\r
5118 return ImpossibleMove;
\r
5125 case EditPosition:
\r
5126 /* EditPosition, empty square, or different color piece;
\r
5127 click-click move is possible */
\r
5128 if (toX == -2 || toY == -2) {
\r
5129 boards[0][fromY][fromX] = EmptySquare;
\r
5130 return AmbiguousMove;
\r
5131 } else if (toX >= 0 && toY >= 0) {
\r
5132 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5133 boards[0][fromY][fromX] = EmptySquare;
\r
5134 return AmbiguousMove;
\r
5136 return ImpossibleMove;
\r
5139 /* [HGM] If move started in holdings, it means a drop */
\r
5140 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5141 if( pup != EmptySquare ) return ImpossibleMove;
\r
5142 if(appData.testLegality) {
\r
5143 /* it would be more logical if LegalityTest() also figured out
\r
5144 * which drops are legal. For now we forbid pawns on back rank.
\r
5145 * Shogi is on its own here...
\r
5147 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5148 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5149 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5151 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5154 userOfferedDraw = FALSE;
\r
5156 /* [HGM] always test for legality, to get promotion info */
\r
5157 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5158 epStatus[currentMove], castlingRights[currentMove],
\r
5159 fromY, fromX, toY, toX, promoChar);
\r
5161 /* [HGM] but possibly ignore an IllegalMove result */
\r
5162 if (appData.testLegality) {
\r
5163 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5164 DisplayMoveError(_("Illegal move"));
\r
5165 return ImpossibleMove;
\r
5168 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5170 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5171 function is made into one that returns an OK move type if FinishMove
\r
5172 should be called. This to give the calling driver routine the
\r
5173 opportunity to finish the userMove input with a promotion popup,
\r
5174 without bothering the user with this for invalid or illegal moves */
\r
5176 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5179 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5181 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5182 ChessMove moveType;
\r
5183 int fromX, fromY, toX, toY;
\r
5184 /*char*/int promoChar;
\r
5186 char *bookHit = 0;
\r
5187 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5188 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5189 // [HGM] superchess: suppress promotions to non-available piece
\r
5190 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5191 if(WhiteOnMove(currentMove)) {
\r
5192 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5194 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5198 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5199 move type in caller when we know the move is a legal promotion */
\r
5200 if(moveType == NormalMove && promoChar)
\r
5201 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5202 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5203 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5204 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5205 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5206 fromX = boards[currentMove][fromY][fromX];
\r
5207 fromY = DROP_RANK;
\r
5210 /* [HGM] <popupFix> The following if has been moved here from
\r
5211 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5212 piece drops in training games?), and because it can only be
\r
5213 performed after it is known to what we promote. */
\r
5214 if (gameMode == Training) {
\r
5215 /* compare the move played on the board to the next move in the
\r
5216 * game. If they match, display the move and the opponent's response.
\r
5217 * If they don't match, display an error message.
\r
5221 CopyBoard(testBoard, boards[currentMove]);
\r
5222 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5224 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5225 ForwardInner(currentMove+1);
\r
5227 /* Autoplay the opponent's response.
\r
5228 * if appData.animate was TRUE when Training mode was entered,
\r
5229 * the response will be animated.
\r
5231 saveAnimate = appData.animate;
\r
5232 appData.animate = animateTraining;
\r
5233 ForwardInner(currentMove+1);
\r
5234 appData.animate = saveAnimate;
\r
5236 /* check for the end of the game */
\r
5237 if (currentMove >= forwardMostMove) {
\r
5238 gameMode = PlayFromGameFile;
\r
5240 SetTrainingModeOff();
\r
5241 DisplayInformation(_("End of game"));
\r
5244 DisplayError(_("Incorrect move"), 0);
\r
5249 /* Ok, now we know that the move is good, so we can kill
\r
5250 the previous line in Analysis Mode */
\r
5251 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5252 forwardMostMove = currentMove;
\r
5255 /* If we need the chess program but it's dead, restart it */
\r
5256 ResurrectChessProgram();
\r
5258 /* A user move restarts a paused game*/
\r
5262 thinkOutput[0] = NULLCHAR;
\r
5264 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5266 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5267 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5268 // [HGM] superchess: take promotion piece out of holdings
\r
5269 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5270 if(WhiteOnMove(forwardMostMove-1)) {
\r
5271 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5272 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5274 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5275 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5279 if (gameMode == BeginningOfGame) {
\r
5280 if (appData.noChessProgram) {
\r
5281 gameMode = EditGame;
\r
5284 char buf[MSG_SIZ];
\r
5285 gameMode = MachinePlaysBlack;
\r
5288 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5289 DisplayTitle(buf);
\r
5290 if (first.sendName) {
\r
5291 sprintf(buf, "name %s\n", gameInfo.white);
\r
5292 SendToProgram(buf, &first);
\r
5298 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5299 /* Relay move to ICS or chess engine */
\r
5300 if (appData.icsActive) {
\r
5301 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5302 gameMode == IcsExamining) {
\r
5303 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5304 ics_user_moved = 1;
\r
5307 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5308 gameMode == MachinePlaysWhite ||
\r
5309 gameMode == MachinePlaysBlack)) {
\r
5310 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5312 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5313 // [HGM] book: if program might be playing, let it use book
\r
5314 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5315 first.maybeThinking = TRUE;
\r
5316 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5317 if (currentMove == cmailOldMove + 1) {
\r
5318 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5322 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5324 switch (gameMode) {
\r
5326 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5327 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5331 case MT_CHECKMATE:
\r
5332 case MT_STAINMATE:
\r
5333 if (WhiteOnMove(currentMove)) {
\r
5334 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5336 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5339 case MT_STALEMATE:
\r
5340 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5345 case MachinePlaysBlack:
\r
5346 case MachinePlaysWhite:
\r
5347 /* disable certain menu options while machine is thinking */
\r
5348 SetMachineThinkingEnables();
\r
5355 if(bookHit) { // [HGM] book: simulate book reply
\r
5356 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5358 programStats.nodes = programStats.depth = programStats.time =
\r
5359 programStats.score = programStats.got_only_move = 0;
\r
5360 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5362 strcpy(bookMove, "move ");
\r
5363 strcat(bookMove, bookHit);
\r
5364 HandleMachineMove(bookMove, &first);
\r
5370 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5371 int fromX, fromY, toX, toY;
\r
5374 /* [HGM] This routine was added to allow calling of its two logical
\r
5375 parts from other modules in the old way. Before, UserMoveEvent()
\r
5376 automatically called FinishMove() if the move was OK, and returned
\r
5377 otherwise. I separated the two, in order to make it possible to
\r
5378 slip a promotion popup in between. But that it always needs two
\r
5379 calls, to the first part, (now called UserMoveTest() ), and to
\r
5380 FinishMove if the first part succeeded. Calls that do not need
\r
5381 to do anything in between, can call this routine the old way.
\r
5383 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5384 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5385 if(moveType != ImpossibleMove)
\r
5386 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5389 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5391 // char * hint = lastHint;
\r
5392 FrontEndProgramStats stats;
\r
5394 stats.which = cps == &first ? 0 : 1;
\r
5395 stats.depth = cpstats->depth;
\r
5396 stats.nodes = cpstats->nodes;
\r
5397 stats.score = cpstats->score;
\r
5398 stats.time = cpstats->time;
\r
5399 stats.pv = cpstats->movelist;
\r
5400 stats.hint = lastHint;
\r
5401 stats.an_move_index = 0;
\r
5402 stats.an_move_count = 0;
\r
5404 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5405 stats.hint = cpstats->move_name;
\r
5406 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5407 stats.an_move_count = cpstats->nr_moves;
\r
5410 SetProgramStats( &stats );
\r
5413 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5414 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5415 char *bookHit = NULL;
\r
5417 //first determine if the incoming move brings opponent into his book
\r
5418 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5419 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5420 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5421 if(bookHit != NULL && !cps->bookSuspend) {
\r
5422 // make sure opponent is not going to reply after receiving move to book position
\r
5423 SendToProgram("force\n", cps);
\r
5424 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5426 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5427 // now arrange restart after book miss
\r
5429 // after a book hit we never send 'go', and the code after the call to this routine
\r
5430 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5431 char buf[MSG_SIZ];
\r
5432 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5433 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5434 SendToProgram(buf, cps);
\r
5435 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5436 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5437 SendToProgram("go\n", cps);
\r
5438 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5439 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5440 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5441 SendToProgram("go\n", cps);
\r
5442 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5444 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5447 char *savedMessage;
\r
5448 ChessProgramState *savedState;
\r
5449 void DeferredBookMove(void)
\r
5451 if(savedState->lastPing != savedState->lastPong)
\r
5452 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5454 HandleMachineMove(savedMessage, savedState);
\r
5458 HandleMachineMove(message, cps)
\r
5460 ChessProgramState *cps;
\r
5462 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5463 char realname[MSG_SIZ];
\r
5464 int fromX, fromY, toX, toY;
\r
5465 ChessMove moveType;
\r
5471 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5473 * Kludge to ignore BEL characters
\r
5475 while (*message == '\007') message++;
\r
5478 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5480 if(cps->debug && *message == '#') return;
\r
5483 * Look for book output
\r
5485 if (cps == &first && bookRequested) {
\r
5486 if (message[0] == '\t' || message[0] == ' ') {
\r
5487 /* Part of the book output is here; append it */
\r
5488 strcat(bookOutput, message);
\r
5489 strcat(bookOutput, " \n");
\r
5491 } else if (bookOutput[0] != NULLCHAR) {
\r
5492 /* All of book output has arrived; display it */
\r
5493 char *p = bookOutput;
\r
5494 while (*p != NULLCHAR) {
\r
5495 if (*p == '\t') *p = ' ';
\r
5498 DisplayInformation(bookOutput);
\r
5499 bookRequested = FALSE;
\r
5500 /* Fall through to parse the current output */
\r
5505 * Look for machine move.
\r
5507 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5508 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5510 /* This method is only useful on engines that support ping */
\r
5511 if (cps->lastPing != cps->lastPong) {
\r
5512 if (gameMode == BeginningOfGame) {
\r
5513 /* Extra move from before last new; ignore */
\r
5514 if (appData.debugMode) {
\r
5515 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5518 if (appData.debugMode) {
\r
5519 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5520 cps->which, gameMode);
\r
5523 SendToProgram("undo\n", cps);
\r
5528 switch (gameMode) {
\r
5529 case BeginningOfGame:
\r
5530 /* Extra move from before last reset; ignore */
\r
5531 if (appData.debugMode) {
\r
5532 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5539 /* Extra move after we tried to stop. The mode test is
\r
5540 not a reliable way of detecting this problem, but it's
\r
5541 the best we can do on engines that don't support ping.
\r
5543 if (appData.debugMode) {
\r
5544 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5545 cps->which, gameMode);
\r
5547 SendToProgram("undo\n", cps);
\r
5550 case MachinePlaysWhite:
\r
5551 case IcsPlayingWhite:
\r
5552 machineWhite = TRUE;
\r
5555 case MachinePlaysBlack:
\r
5556 case IcsPlayingBlack:
\r
5557 machineWhite = FALSE;
\r
5560 case TwoMachinesPlay:
\r
5561 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5564 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5565 if (appData.debugMode) {
\r
5567 "Ignoring move out of turn by %s, gameMode %d"
\r
5568 ", forwardMost %d\n",
\r
5569 cps->which, gameMode, forwardMostMove);
\r
5574 if (appData.debugMode) { int f = forwardMostMove;
\r
5575 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5576 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5578 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5579 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5580 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5581 /* Machine move could not be parsed; ignore it. */
\r
5582 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5583 machineMove, cps->which);
\r
5584 DisplayError(buf1, 0);
\r
5585 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5586 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5587 if (gameMode == TwoMachinesPlay) {
\r
5588 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5594 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5595 /* So we have to redo legality test with true e.p. status here, */
\r
5596 /* to make sure an illegal e.p. capture does not slip through, */
\r
5597 /* to cause a forfeit on a justified illegal-move complaint */
\r
5598 /* of the opponent. */
\r
5599 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5600 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5602 ChessMove moveType;
\r
5603 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5604 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5605 fromY, fromX, toY, toX, promoChar);
\r
5606 if (appData.debugMode) {
\r
5608 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5609 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5610 fprintf(debugFP, "castling rights\n");
\r
5612 if(moveType == IllegalMove) {
\r
5613 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5614 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5615 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5618 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5619 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5620 when they shouldn't (like TSCP-Gothic) */
\r
5621 switch(moveType) {
\r
5622 case WhiteASideCastleFR:
\r
5623 case BlackASideCastleFR:
\r
5625 currentMoveString[2]++;
\r
5627 case WhiteHSideCastleFR:
\r
5628 case BlackHSideCastleFR:
\r
5630 currentMoveString[2]--;
\r
5632 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5635 hintRequested = FALSE;
\r
5636 lastHint[0] = NULLCHAR;
\r
5637 bookRequested = FALSE;
\r
5638 /* Program may be pondering now */
\r
5639 cps->maybeThinking = TRUE;
\r
5640 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5641 if (cps->offeredDraw) cps->offeredDraw--;
\r
5644 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5646 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5647 ics_user_moved = 1;
\r
5648 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5649 char buf[3*MSG_SIZ];
\r
5651 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
\r
5652 programStats.score / 100.,
\r
5653 programStats.depth,
\r
5654 programStats.time / 100.,
\r
5655 u64ToDouble(programStats.nodes),
\r
5656 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5657 programStats.movelist);
\r
5662 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5663 strcpy(machineMove, currentMoveString);
\r
5664 strcat(machineMove, "\n");
\r
5665 strcpy(moveList[forwardMostMove], machineMove);
\r
5667 /* [AS] Save move info and clear stats for next move */
\r
5668 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5669 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5670 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5671 ClearProgramStats();
\r
5672 thinkOutput[0] = NULLCHAR;
\r
5673 hiddenThinkOutputState = 0;
\r
5675 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5677 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5678 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5681 while( count < adjudicateLossPlies ) {
\r
5682 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5685 score = -score; /* Flip score for winning side */
\r
5688 if( score > adjudicateLossThreshold ) {
\r
5695 if( count >= adjudicateLossPlies ) {
\r
5696 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5698 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5699 "Xboard adjudication",
\r
5706 if( gameMode == TwoMachinesPlay ) {
\r
5707 // [HGM] some adjudications useful with buggy engines
\r
5708 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5709 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5712 if( appData.testLegality )
\r
5713 { /* [HGM] Some more adjudications for obstinate engines */
\r
5714 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5715 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5716 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5717 static int moveCount = 6;
\r
5719 char *reason = NULL;
\r
5721 /* Count what is on board. */
\r
5722 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5723 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5727 { /* count B,N,R and other of each side */
\r
5730 NrK++; break; // [HGM] atomic: count Kings
\r
5734 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5735 bishopsColor |= 1 << ((i^j)&1);
\r
5740 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5741 bishopsColor |= 1 << ((i^j)&1);
\r
5751 case EmptySquare:
\r
5756 PawnAdvance += m; NrPawns++;
\r
5758 NrPieces += (p != EmptySquare);
\r
5759 NrW += ((int)p < (int)BlackPawn);
\r
5760 if(gameInfo.variant == VariantXiangqi &&
\r
5761 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5762 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5763 NrW -= ((int)p < (int)BlackPawn);
\r
5767 /* Some material-based adjudications that have to be made before stalemate test */
\r
5768 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5769 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5770 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5771 if(appData.checkMates) {
\r
5772 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5773 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5774 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5775 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5780 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5781 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5782 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5783 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
\r
5784 if(appData.checkMates) {
\r
5785 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
\r
5786 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5787 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5788 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5792 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5794 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
\r
5795 if(appData.checkMates) {
\r
5796 /* but only adjudicate if adjudication enabled */
\r
5797 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5798 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5799 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5800 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5807 // don't wait for engine to announce game end if we can judge ourselves
\r
5808 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
\r
5809 castlingRights[forwardMostMove]) ) {
\r
5814 case MT_STALEMATE:
\r
5815 case MT_STAINMATE:
\r
5816 reason = "Xboard adjudication: Stalemate";
\r
5817 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
\r
5818 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
\r
5819 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5820 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
\r
5821 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
\r
5822 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
\r
5823 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
\r
5824 EP_CHECKMATE : EP_WINS);
\r
5825 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
\r
5826 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
\r
5829 case MT_CHECKMATE:
\r
5830 reason = "Xboard adjudication: Checkmate";
\r
5831 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
\r
5835 switch(i = epStatus[forwardMostMove]) {
\r
5836 case EP_STALEMATE:
\r
5837 result = GameIsDrawn; break;
\r
5838 case EP_CHECKMATE:
\r
5839 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
\r
5841 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
\r
5843 result = (ChessMove) 0;
\r
5845 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
\r
5846 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5847 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5848 GameEnds( result, reason, GE_XBOARD );
\r
5852 /* Next absolutely insufficient mating material. */
\r
5853 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5854 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5855 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5856 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5857 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5859 /* always flag draws, for judging claims */
\r
5860 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5862 if(appData.materialDraws) {
\r
5863 /* but only adjudicate them if adjudication enabled */
\r
5864 SendToProgram("force\n", cps->other); // suppress reply
\r
5865 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5866 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5867 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5872 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5873 if(NrPieces == 4 &&
\r
5874 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5875 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5876 || NrWN==2 || NrBN==2 /* KNNK */
\r
5877 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5879 if(--moveCount < 0 && appData.trivialDraws)
\r
5880 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5881 SendToProgram("force\n", cps->other); // suppress reply
\r
5882 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5883 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5884 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5887 } else moveCount = 6;
\r
5891 if (appData.debugMode) { int i;
\r
5892 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5893 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5894 appData.drawRepeats);
\r
5895 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5896 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5900 /* Check for rep-draws */
\r
5902 for(k = forwardMostMove-2;
\r
5903 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5904 epStatus[k] < EP_UNKNOWN &&
\r
5905 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5909 if (appData.debugMode) {
\r
5910 fprintf(debugFP, " loop\n");
\r
5913 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5915 if (appData.debugMode) {
\r
5916 fprintf(debugFP, "match\n");
\r
5919 /* compare castling rights */
\r
5920 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5921 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5922 rights++; /* King lost rights, while rook still had them */
\r
5923 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5924 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5925 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5926 rights++; /* but at least one rook lost them */
\r
5928 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5929 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5931 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5932 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5933 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5937 if (appData.debugMode) {
\r
5938 for(i=0; i<nrCastlingRights; i++)
\r
5939 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5942 if (appData.debugMode) {
\r
5943 fprintf(debugFP, " %d %d\n", rights, k);
\r
5946 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5947 && appData.drawRepeats > 1) {
\r
5948 /* adjudicate after user-specified nr of repeats */
\r
5949 SendToProgram("force\n", cps->other); // suppress reply
\r
5950 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5951 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5952 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5953 // [HGM] xiangqi: check for forbidden perpetuals
\r
5954 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5955 for(m=forwardMostMove; m>k; m-=2) {
\r
5956 if(MateTest(boards[m], PosFlags(m),
\r
5957 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5958 ourPerpetual = 0; // the current mover did not always check
\r
5959 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5960 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5961 hisPerpetual = 0; // the opponent did not always check
\r
5963 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5964 ourPerpetual, hisPerpetual);
\r
5965 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5966 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5967 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5970 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5971 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5972 // Now check for perpetual chases
\r
5973 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5974 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5975 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5976 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
\r
5977 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5978 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5981 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5982 break; // Abort repetition-checking loop.
\r
5984 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5986 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5989 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5990 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5994 /* Now we test for 50-move draws. Determine ply count */
\r
5995 count = forwardMostMove;
\r
5996 /* look for last irreversble move */
\r
5997 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5999 /* if we hit starting position, add initial plies */
\r
6000 if( count == backwardMostMove )
\r
6001 count -= initialRulePlies;
\r
6002 count = forwardMostMove - count;
\r
6004 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6005 /* this is used to judge if draw claims are legal */
\r
6006 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6007 SendToProgram("force\n", cps->other); // suppress reply
\r
6008 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6009 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6010 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6014 /* if draw offer is pending, treat it as a draw claim
\r
6015 * when draw condition present, to allow engines a way to
\r
6016 * claim draws before making their move to avoid a race
\r
6017 * condition occurring after their move
\r
6019 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6021 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6022 p = "Draw claim: 50-move rule";
\r
6023 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6024 p = "Draw claim: 3-fold repetition";
\r
6025 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6026 p = "Draw claim: insufficient mating material";
\r
6028 SendToProgram("force\n", cps->other); // suppress reply
\r
6029 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6030 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6031 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6037 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6038 SendToProgram("force\n", cps->other); // suppress reply
\r
6039 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6040 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6042 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6049 if (gameMode == TwoMachinesPlay) {
\r
6050 /* [HGM] relaying draw offers moved to after reception of move */
\r
6051 /* and interpreting offer as claim if it brings draw condition */
\r
6052 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6053 SendToProgram("draw\n", cps->other);
\r
6055 if (cps->other->sendTime) {
\r
6056 SendTimeRemaining(cps->other,
\r
6057 cps->other->twoMachinesColor[0] == 'w');
\r
6059 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6060 if (firstMove && !bookHit) {
\r
6061 firstMove = FALSE;
\r
6062 if (cps->other->useColors) {
\r
6063 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6065 SendToProgram("go\n", cps->other);
\r
6067 cps->other->maybeThinking = TRUE;
\r
6070 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6072 if (!pausing && appData.ringBellAfterMoves) {
\r
6077 * Reenable menu items that were disabled while
\r
6078 * machine was thinking
\r
6080 if (gameMode != TwoMachinesPlay)
\r
6081 SetUserThinkingEnables();
\r
6083 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6084 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6085 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6087 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6089 strcpy(bookMove, "move ");
\r
6090 strcat(bookMove, bookHit);
\r
6091 message = bookMove;
\r
6093 programStats.nodes = programStats.depth = programStats.time =
\r
6094 programStats.score = programStats.got_only_move = 0;
\r
6095 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6097 if(cps->lastPing != cps->lastPong) {
\r
6098 savedMessage = message; // args for deferred call
\r
6100 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6103 goto FakeBookMove;
\r
6109 /* Set special modes for chess engines. Later something general
\r
6110 * could be added here; for now there is just one kludge feature,
\r
6111 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6112 * when "xboard" is given as an interactive command.
\r
6114 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6115 cps->useSigint = FALSE;
\r
6116 cps->useSigterm = FALSE;
\r
6119 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6120 * want this, I was asked to put it in, and obliged.
\r
6122 if (!strncmp(message, "setboard ", 9)) {
\r
6123 Board initial_position; int i;
\r
6125 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6127 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6128 DisplayError(_("Bad FEN received from engine"), 0);
\r
6131 Reset(FALSE, FALSE);
\r
6132 CopyBoard(boards[0], initial_position);
\r
6133 initialRulePlies = FENrulePlies;
\r
6134 epStatus[0] = FENepStatus;
\r
6135 for( i=0; i<nrCastlingRights; i++ )
\r
6136 castlingRights[0][i] = FENcastlingRights[i];
\r
6137 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6138 else gameMode = MachinePlaysBlack;
\r
6139 DrawPosition(FALSE, boards[currentMove]);
\r
6145 * Look for communication commands
\r
6147 if (!strncmp(message, "telluser ", 9)) {
\r
6148 DisplayNote(message + 9);
\r
6151 if (!strncmp(message, "tellusererror ", 14)) {
\r
6152 DisplayError(message + 14, 0);
\r
6155 if (!strncmp(message, "tellopponent ", 13)) {
\r
6156 if (appData.icsActive) {
\r
6158 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6162 DisplayNote(message + 13);
\r
6166 if (!strncmp(message, "tellothers ", 11)) {
\r
6167 if (appData.icsActive) {
\r
6169 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6175 if (!strncmp(message, "tellall ", 8)) {
\r
6176 if (appData.icsActive) {
\r
6178 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6182 DisplayNote(message + 8);
\r
6186 if (strncmp(message, "warning", 7) == 0) {
\r
6187 /* Undocumented feature, use tellusererror in new code */
\r
6188 DisplayError(message, 0);
\r
6191 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6192 strcpy(realname, cps->tidy);
\r
6193 strcat(realname, " query");
\r
6194 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6197 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6198 * sent until we are logged on. Crafty kibitzes have been known to
\r
6199 * interfere with the login process.
\r
6202 if (!strncmp(message, "tellics ", 8)) {
\r
6203 SendToICS(message + 8);
\r
6207 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6208 SendToICS(ics_prefix);
\r
6209 SendToICS(message + 15);
\r
6213 /* The following are for backward compatibility only */
\r
6214 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6215 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6216 SendToICS(ics_prefix);
\r
6217 SendToICS(message);
\r
6222 if (strncmp(message, "feature ", 8) == 0) {
\r
6223 ParseFeatures(message+8, cps);
\r
6225 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6229 * If the move is illegal, cancel it and redraw the board.
\r
6230 * Also deal with other error cases. Matching is rather loose
\r
6231 * here to accommodate engines written before the spec.
\r
6233 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6234 strncmp(message, "Error", 5) == 0) {
\r
6235 if (StrStr(message, "name") ||
\r
6236 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6237 StrStr(message, "result") || StrStr(message, "board") ||
\r
6238 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6239 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6240 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6241 StrStr(message, "accepted")) {
\r
6244 if (StrStr(message, "protover")) {
\r
6245 /* Program is responding to input, so it's apparently done
\r
6246 initializing, and this error message indicates it is
\r
6247 protocol version 1. So we don't need to wait any longer
\r
6248 for it to initialize and send feature commands. */
\r
6249 FeatureDone(cps, 1);
\r
6250 cps->protocolVersion = 1;
\r
6253 cps->maybeThinking = FALSE;
\r
6255 if (StrStr(message, "draw")) {
\r
6256 /* Program doesn't have "draw" command */
\r
6257 cps->sendDrawOffers = 0;
\r
6260 if (cps->sendTime != 1 &&
\r
6261 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6262 /* Program apparently doesn't have "time" or "otim" command */
\r
6263 cps->sendTime = 0;
\r
6266 if (StrStr(message, "analyze")) {
\r
6267 cps->analysisSupport = FALSE;
\r
6268 cps->analyzing = FALSE;
\r
6269 Reset(FALSE, TRUE);
\r
6270 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6271 DisplayError(buf2, 0);
\r
6274 if (StrStr(message, "(no matching move)st")) {
\r
6275 /* Special kludge for GNU Chess 4 only */
\r
6276 cps->stKludge = TRUE;
\r
6277 SendTimeControl(cps, movesPerSession, timeControl,
\r
6278 timeIncrement, appData.searchDepth,
\r
6282 if (StrStr(message, "(no matching move)sd")) {
\r
6283 /* Special kludge for GNU Chess 4 only */
\r
6284 cps->sdKludge = TRUE;
\r
6285 SendTimeControl(cps, movesPerSession, timeControl,
\r
6286 timeIncrement, appData.searchDepth,
\r
6290 if (!StrStr(message, "llegal")) {
\r
6293 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6294 gameMode == IcsIdle) return;
\r
6295 if (forwardMostMove <= backwardMostMove) return;
\r
6297 /* Following removed: it caused a bug where a real illegal move
\r
6298 message in analyze mored would be ignored. */
\r
6299 if (cps == &first && programStats.ok_to_send == 0) {
\r
6300 /* Bogus message from Crafty responding to "." This filtering
\r
6301 can miss some of the bad messages, but fortunately the bug
\r
6302 is fixed in current Crafty versions, so it doesn't matter. */
\r
6306 if (pausing) PauseEvent();
\r
6307 if (gameMode == PlayFromGameFile) {
\r
6308 /* Stop reading this game file */
\r
6309 gameMode = EditGame;
\r
6312 currentMove = --forwardMostMove;
\r
6313 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6315 DisplayBothClocks();
\r
6316 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6317 parseList[currentMove], cps->which);
\r
6318 DisplayMoveError(buf1);
\r
6319 DrawPosition(FALSE, boards[currentMove]);
\r
6321 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6322 /* only passes fully legal moves */
\r
6323 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6324 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6325 "False illegal-move claim", GE_XBOARD );
\r
6329 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6330 /* Program has a broken "time" command that
\r
6331 outputs a string not ending in newline.
\r
6333 cps->sendTime = 0;
\r
6337 * If chess program startup fails, exit with an error message.
\r
6338 * Attempts to recover here are futile.
\r
6340 if ((StrStr(message, "unknown host") != NULL)
\r
6341 || (StrStr(message, "No remote directory") != NULL)
\r
6342 || (StrStr(message, "not found") != NULL)
\r
6343 || (StrStr(message, "No such file") != NULL)
\r
6344 || (StrStr(message, "can't alloc") != NULL)
\r
6345 || (StrStr(message, "Permission denied") != NULL)) {
\r
6347 cps->maybeThinking = FALSE;
\r
6348 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6349 cps->which, cps->program, cps->host, message);
\r
6350 RemoveInputSource(cps->isr);
\r
6351 DisplayFatalError(buf1, 0, 1);
\r
6356 * Look for hint output
\r
6358 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6359 if (cps == &first && hintRequested) {
\r
6360 hintRequested = FALSE;
\r
6361 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6362 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6363 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6364 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6365 fromY, fromX, toY, toX, promoChar, buf1);
\r
6366 sprintf(buf2, _("Hint: %s"), buf1);
\r
6367 DisplayInformation(buf2);
\r
6369 /* Hint move could not be parsed!? */
\r
6371 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6372 buf1, cps->which);
\r
6373 DisplayError(buf2, 0);
\r
6376 strcpy(lastHint, buf1);
\r
6382 * Ignore other messages if game is not in progress
\r
6384 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6385 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6388 * look for win, lose, draw, or draw offer
\r
6390 if (strncmp(message, "1-0", 3) == 0) {
\r
6391 char *p, *q, *r = "";
\r
6392 p = strchr(message, '{');
\r
6394 q = strchr(p, '}');
\r
6400 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6402 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6403 char *p, *q, *r = "";
\r
6404 p = strchr(message, '{');
\r
6406 q = strchr(p, '}');
\r
6412 /* Kludge for Arasan 4.1 bug */
\r
6413 if (strcmp(r, "Black resigns") == 0) {
\r
6414 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6417 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6419 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6420 char *p, *q, *r = "";
\r
6421 p = strchr(message, '{');
\r
6423 q = strchr(p, '}');
\r
6430 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6433 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6434 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6436 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6437 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6439 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6440 strncmp(message, "Black matches", 13) == 0 ) {
\r
6441 /* [HGM] ignore GNUShogi noises */
\r
6443 } else if (strncmp(message, "White", 5) == 0 &&
\r
6444 message[5] != '(' &&
\r
6445 StrStr(message, "Black") == NULL) {
\r
6446 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6448 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6449 message[5] != '(') {
\r
6450 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6452 } else if (strcmp(message, "resign") == 0 ||
\r
6453 strcmp(message, "computer resigns") == 0) {
\r
6454 switch (gameMode) {
\r
6455 case MachinePlaysBlack:
\r
6456 case IcsPlayingBlack:
\r
6457 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6459 case MachinePlaysWhite:
\r
6460 case IcsPlayingWhite:
\r
6461 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6463 case TwoMachinesPlay:
\r
6464 if (cps->twoMachinesColor[0] == 'w')
\r
6465 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6467 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6470 /* can't happen */
\r
6474 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6475 switch (gameMode) {
\r
6476 case MachinePlaysBlack:
\r
6477 case IcsPlayingBlack:
\r
6478 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6480 case MachinePlaysWhite:
\r
6481 case IcsPlayingWhite:
\r
6482 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6484 case TwoMachinesPlay:
\r
6485 if (cps->twoMachinesColor[0] == 'w')
\r
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6488 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6491 /* can't happen */
\r
6495 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6496 switch (gameMode) {
\r
6497 case MachinePlaysBlack:
\r
6498 case IcsPlayingBlack:
\r
6499 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6501 case MachinePlaysWhite:
\r
6502 case IcsPlayingWhite:
\r
6503 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6505 case TwoMachinesPlay:
\r
6506 if (cps->twoMachinesColor[0] == 'w')
\r
6507 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6509 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6512 /* can't happen */
\r
6516 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6517 if (WhiteOnMove(forwardMostMove)) {
\r
6518 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6520 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6523 } else if (strstr(message, "Draw") != NULL ||
\r
6524 strstr(message, "game is a draw") != NULL) {
\r
6525 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6527 } else if (strstr(message, "offer") != NULL &&
\r
6528 strstr(message, "draw") != NULL) {
\r
6530 if (appData.zippyPlay && first.initDone) {
\r
6531 /* Relay offer to ICS */
\r
6532 SendToICS(ics_prefix);
\r
6533 SendToICS("draw\n");
\r
6536 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6537 if (gameMode == TwoMachinesPlay) {
\r
6538 if (cps->other->offeredDraw) {
\r
6539 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6540 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6541 /* until after we also have move, to see if it is really claim */
\r
6545 if (cps->other->sendDrawOffers) {
\r
6546 SendToProgram("draw\n", cps->other);
\r
6550 } else if (gameMode == MachinePlaysWhite ||
\r
6551 gameMode == MachinePlaysBlack) {
\r
6552 if (userOfferedDraw) {
\r
6553 DisplayInformation(_("Machine accepts your draw offer"));
\r
6554 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6556 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6563 * Look for thinking output
\r
6565 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6566 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6568 int plylev, mvleft, mvtot, curscore, time;
\r
6569 char mvname[MOVE_LEN];
\r
6570 u64 nodes; // [DM]
\r
6572 int ignore = FALSE;
\r
6573 int prefixHint = FALSE;
\r
6574 mvname[0] = NULLCHAR;
\r
6576 switch (gameMode) {
\r
6577 case MachinePlaysBlack:
\r
6578 case IcsPlayingBlack:
\r
6579 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6581 case MachinePlaysWhite:
\r
6582 case IcsPlayingWhite:
\r
6583 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6588 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6589 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6591 case TwoMachinesPlay:
\r
6592 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6602 buf1[0] = NULLCHAR;
\r
6603 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6604 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6606 if (plyext != ' ' && plyext != '\t') {
\r
6610 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6611 if( cps->scoreIsAbsolute &&
\r
6612 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6614 curscore = -curscore;
\r
6618 programStats.depth = plylev;
\r
6619 programStats.nodes = nodes;
\r
6620 programStats.time = time;
\r
6621 programStats.score = curscore;
\r
6622 programStats.got_only_move = 0;
\r
6624 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6627 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6628 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6629 if(WhiteOnMove(forwardMostMove))
\r
6630 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6631 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6634 /* Buffer overflow protection */
\r
6635 if (buf1[0] != NULLCHAR) {
\r
6636 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6637 && appData.debugMode) {
\r
6639 "PV is too long; using the first %d bytes.\n",
\r
6640 sizeof(programStats.movelist) - 1);
\r
6643 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6645 sprintf(programStats.movelist, " no PV\n");
\r
6648 if (programStats.seen_stat) {
\r
6649 programStats.ok_to_send = 1;
\r
6652 if (strchr(programStats.movelist, '(') != NULL) {
\r
6653 programStats.line_is_book = 1;
\r
6654 programStats.nr_moves = 0;
\r
6655 programStats.moves_left = 0;
\r
6657 programStats.line_is_book = 0;
\r
6660 SendProgramStatsToFrontend( cps, &programStats );
\r
6663 [AS] Protect the thinkOutput buffer from overflow... this
\r
6664 is only useful if buf1 hasn't overflowed first!
\r
6666 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6668 (gameMode == TwoMachinesPlay ?
\r
6669 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6670 ((double) curscore) / 100.0,
\r
6671 prefixHint ? lastHint : "",
\r
6672 prefixHint ? " " : "" );
\r
6674 if( buf1[0] != NULLCHAR ) {
\r
6675 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6677 if( strlen(buf1) > max_len ) {
\r
6678 if( appData.debugMode) {
\r
6679 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6681 buf1[max_len+1] = '\0';
\r
6684 strcat( thinkOutput, buf1 );
\r
6687 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6688 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6689 DisplayMove(currentMove - 1);
\r
6690 DisplayAnalysis();
\r
6694 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6695 /* crafty (9.25+) says "(only move) <move>"
\r
6696 * if there is only 1 legal move
\r
6698 sscanf(p, "(only move) %s", buf1);
\r
6699 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6700 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6701 programStats.depth = 1;
\r
6702 programStats.nr_moves = 1;
\r
6703 programStats.moves_left = 1;
\r
6704 programStats.nodes = 1;
\r
6705 programStats.time = 1;
\r
6706 programStats.got_only_move = 1;
\r
6708 /* Not really, but we also use this member to
\r
6709 mean "line isn't going to change" (Crafty
\r
6710 isn't searching, so stats won't change) */
\r
6711 programStats.line_is_book = 1;
\r
6713 SendProgramStatsToFrontend( cps, &programStats );
\r
6715 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6716 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6717 DisplayMove(currentMove - 1);
\r
6718 DisplayAnalysis();
\r
6721 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6722 &time, &nodes, &plylev, &mvleft,
\r
6723 &mvtot, mvname) >= 5) {
\r
6724 /* The stat01: line is from Crafty (9.29+) in response
\r
6725 to the "." command */
\r
6726 programStats.seen_stat = 1;
\r
6727 cps->maybeThinking = TRUE;
\r
6729 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6732 programStats.depth = plylev;
\r
6733 programStats.time = time;
\r
6734 programStats.nodes = nodes;
\r
6735 programStats.moves_left = mvleft;
\r
6736 programStats.nr_moves = mvtot;
\r
6737 strcpy(programStats.move_name, mvname);
\r
6738 programStats.ok_to_send = 1;
\r
6739 programStats.movelist[0] = '\0';
\r
6741 SendProgramStatsToFrontend( cps, &programStats );
\r
6743 DisplayAnalysis();
\r
6746 } else if (strncmp(message,"++",2) == 0) {
\r
6747 /* Crafty 9.29+ outputs this */
\r
6748 programStats.got_fail = 2;
\r
6751 } else if (strncmp(message,"--",2) == 0) {
\r
6752 /* Crafty 9.29+ outputs this */
\r
6753 programStats.got_fail = 1;
\r
6756 } else if (thinkOutput[0] != NULLCHAR &&
\r
6757 strncmp(message, " ", 4) == 0) {
\r
6758 unsigned message_len;
\r
6761 while (*p && *p == ' ') p++;
\r
6763 message_len = strlen( p );
\r
6765 /* [AS] Avoid buffer overflow */
\r
6766 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6767 strcat(thinkOutput, " ");
\r
6768 strcat(thinkOutput, p);
\r
6771 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6772 strcat(programStats.movelist, " ");
\r
6773 strcat(programStats.movelist, p);
\r
6776 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6777 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6778 DisplayMove(currentMove - 1);
\r
6779 DisplayAnalysis();
\r
6785 buf1[0] = NULLCHAR;
\r
6787 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6788 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6790 ChessProgramStats cpstats;
\r
6792 if (plyext != ' ' && plyext != '\t') {
\r
6796 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6797 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6798 curscore = -curscore;
\r
6801 cpstats.depth = plylev;
\r
6802 cpstats.nodes = nodes;
\r
6803 cpstats.time = time;
\r
6804 cpstats.score = curscore;
\r
6805 cpstats.got_only_move = 0;
\r
6806 cpstats.movelist[0] = '\0';
\r
6808 if (buf1[0] != NULLCHAR) {
\r
6809 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6812 cpstats.ok_to_send = 0;
\r
6813 cpstats.line_is_book = 0;
\r
6814 cpstats.nr_moves = 0;
\r
6815 cpstats.moves_left = 0;
\r
6817 SendProgramStatsToFrontend( cps, &cpstats );
\r
6824 /* Parse a game score from the character string "game", and
\r
6825 record it as the history of the current game. The game
\r
6826 score is NOT assumed to start from the standard position.
\r
6827 The display is not updated in any way.
\r
6830 ParseGameHistory(game)
\r
6833 ChessMove moveType;
\r
6834 int fromX, fromY, toX, toY, boardIndex;
\r
6837 char buf[MSG_SIZ];
\r
6839 if (appData.debugMode)
\r
6840 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6842 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6843 gameInfo.site = StrSave(appData.icsHost);
\r
6844 gameInfo.date = PGNDate();
\r
6845 gameInfo.round = StrSave("-");
\r
6847 /* Parse out names of players */
\r
6848 while (*game == ' ') game++;
\r
6850 while (*game != ' ') *p++ = *game++;
\r
6852 gameInfo.white = StrSave(buf);
\r
6853 while (*game == ' ') game++;
\r
6855 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6857 gameInfo.black = StrSave(buf);
\r
6860 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6863 yyboardindex = boardIndex;
\r
6864 moveType = (ChessMove) yylex();
\r
6865 switch (moveType) {
\r
6866 case IllegalMove: /* maybe suicide chess, etc. */
\r
6867 if (appData.debugMode) {
\r
6868 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6869 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6870 setbuf(debugFP, NULL);
\r
6872 case WhitePromotionChancellor:
\r
6873 case BlackPromotionChancellor:
\r
6874 case WhitePromotionArchbishop:
\r
6875 case BlackPromotionArchbishop:
\r
6876 case WhitePromotionQueen:
\r
6877 case BlackPromotionQueen:
\r
6878 case WhitePromotionRook:
\r
6879 case BlackPromotionRook:
\r
6880 case WhitePromotionBishop:
\r
6881 case BlackPromotionBishop:
\r
6882 case WhitePromotionKnight:
\r
6883 case BlackPromotionKnight:
\r
6884 case WhitePromotionKing:
\r
6885 case BlackPromotionKing:
\r
6887 case WhiteCapturesEnPassant:
\r
6888 case BlackCapturesEnPassant:
\r
6889 case WhiteKingSideCastle:
\r
6890 case WhiteQueenSideCastle:
\r
6891 case BlackKingSideCastle:
\r
6892 case BlackQueenSideCastle:
\r
6893 case WhiteKingSideCastleWild:
\r
6894 case WhiteQueenSideCastleWild:
\r
6895 case BlackKingSideCastleWild:
\r
6896 case BlackQueenSideCastleWild:
\r
6898 case WhiteHSideCastleFR:
\r
6899 case WhiteASideCastleFR:
\r
6900 case BlackHSideCastleFR:
\r
6901 case BlackASideCastleFR:
\r
6903 fromX = currentMoveString[0] - AAA;
\r
6904 fromY = currentMoveString[1] - ONE;
\r
6905 toX = currentMoveString[2] - AAA;
\r
6906 toY = currentMoveString[3] - ONE;
\r
6907 promoChar = currentMoveString[4];
\r
6911 fromX = moveType == WhiteDrop ?
\r
6912 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6913 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6914 fromY = DROP_RANK;
\r
6915 toX = currentMoveString[2] - AAA;
\r
6916 toY = currentMoveString[3] - ONE;
\r
6917 promoChar = NULLCHAR;
\r
6919 case AmbiguousMove:
\r
6921 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6922 if (appData.debugMode) {
\r
6923 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6924 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6925 setbuf(debugFP, NULL);
\r
6927 DisplayError(buf, 0);
\r
6929 case ImpossibleMove:
\r
6931 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6932 if (appData.debugMode) {
\r
6933 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6934 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6935 setbuf(debugFP, NULL);
\r
6937 DisplayError(buf, 0);
\r
6939 case (ChessMove) 0: /* end of file */
\r
6940 if (boardIndex < backwardMostMove) {
\r
6941 /* Oops, gap. How did that happen? */
\r
6942 DisplayError(_("Gap in move list"), 0);
\r
6945 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6946 if (boardIndex > forwardMostMove) {
\r
6947 forwardMostMove = boardIndex;
\r
6951 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6952 strcat(parseList[boardIndex-1], " ");
\r
6953 strcat(parseList[boardIndex-1], yy_text);
\r
6965 case GameUnfinished:
\r
6966 if (gameMode == IcsExamining) {
\r
6967 if (boardIndex < backwardMostMove) {
\r
6968 /* Oops, gap. How did that happen? */
\r
6971 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6974 gameInfo.result = moveType;
\r
6975 p = strchr(yy_text, '{');
\r
6976 if (p == NULL) p = strchr(yy_text, '(');
\r
6979 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6981 q = strchr(p, *p == '{' ? '}' : ')');
\r
6982 if (q != NULL) *q = NULLCHAR;
\r
6985 gameInfo.resultDetails = StrSave(p);
\r
6988 if (boardIndex >= forwardMostMove &&
\r
6989 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6990 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6993 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6994 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6995 parseList[boardIndex]);
\r
6996 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6997 /* currentMoveString is set as a side-effect of yylex */
\r
6998 strcpy(moveList[boardIndex], currentMoveString);
\r
6999 strcat(moveList[boardIndex], "\n");
\r
7001 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7002 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7003 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7005 case MT_STALEMATE:
\r
7009 if(gameInfo.variant != VariantShogi)
\r
7010 strcat(parseList[boardIndex - 1], "+");
\r
7012 case MT_CHECKMATE:
\r
7013 case MT_STAINMATE:
\r
7014 strcat(parseList[boardIndex - 1], "#");
\r
7021 /* Apply a move to the given board */
\r
7023 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7024 int fromX, fromY, toX, toY;
\r
7028 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7030 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7031 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7032 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7035 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7036 oldEP = epStatus[p-1];
\r
7037 epStatus[p] = EP_NONE;
\r
7039 if( board[toY][toX] != EmptySquare )
\r
7040 epStatus[p] = EP_CAPTURE;
\r
7042 if( board[fromY][fromX] == WhitePawn ) {
\r
7043 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7044 epStatus[p] = EP_PAWN_MOVE;
\r
7045 if( toY-fromY==2) {
\r
7046 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7047 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7048 epStatus[p] = toX | berolina;
\r
7049 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7050 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7051 epStatus[p] = toX;
\r
7054 if( board[fromY][fromX] == BlackPawn ) {
\r
7055 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7056 epStatus[p] = EP_PAWN_MOVE;
\r
7057 if( toY-fromY== -2) {
\r
7058 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7059 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7060 epStatus[p] = toX | berolina;
\r
7061 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7062 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7063 epStatus[p] = toX;
\r
7067 for(i=0; i<nrCastlingRights; i++) {
\r
7068 castlingRights[p][i] = castlingRights[p-1][i];
\r
7069 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7070 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7071 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7076 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7077 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7078 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7080 if (fromX == toX && fromY == toY) return;
\r
7082 if (fromY == DROP_RANK) {
\r
7083 /* must be first */
\r
7084 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7086 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7087 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7088 if(gameInfo.variant == VariantKnightmate)
\r
7089 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7091 /* Code added by Tord: */
\r
7092 /* FRC castling assumed when king captures friendly rook. */
\r
7093 if (board[fromY][fromX] == WhiteKing &&
\r
7094 board[toY][toX] == WhiteRook) {
\r
7095 board[fromY][fromX] = EmptySquare;
\r
7096 board[toY][toX] = EmptySquare;
\r
7098 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7100 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7102 } else if (board[fromY][fromX] == BlackKing &&
\r
7103 board[toY][toX] == BlackRook) {
\r
7104 board[fromY][fromX] = EmptySquare;
\r
7105 board[toY][toX] = EmptySquare;
\r
7107 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7109 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7111 /* End of code added by Tord */
\r
7113 } else if (board[fromY][fromX] == king
\r
7114 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7115 && toY == fromY && toX > fromX+1) {
\r
7116 board[fromY][fromX] = EmptySquare;
\r
7117 board[toY][toX] = king;
\r
7118 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7119 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7120 } else if (board[fromY][fromX] == king
\r
7121 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7122 && toY == fromY && toX < fromX-1) {
\r
7123 board[fromY][fromX] = EmptySquare;
\r
7124 board[toY][toX] = king;
\r
7125 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7126 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7127 } else if (board[fromY][fromX] == WhitePawn
\r
7128 && toY == BOARD_HEIGHT-1
\r
7129 && gameInfo.variant != VariantXiangqi
\r
7131 /* white pawn promotion */
\r
7132 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7133 if (board[toY][toX] == EmptySquare) {
\r
7134 board[toY][toX] = WhiteQueen;
\r
7136 if(gameInfo.variant==VariantBughouse ||
\r
7137 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7138 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7139 board[fromY][fromX] = EmptySquare;
\r
7140 } else if ((fromY == BOARD_HEIGHT-4)
\r
7142 && gameInfo.variant != VariantXiangqi
\r
7143 && gameInfo.variant != VariantBerolina
\r
7144 && (board[fromY][fromX] == WhitePawn)
\r
7145 && (board[toY][toX] == EmptySquare)) {
\r
7146 board[fromY][fromX] = EmptySquare;
\r
7147 board[toY][toX] = WhitePawn;
\r
7148 captured = board[toY - 1][toX];
\r
7149 board[toY - 1][toX] = EmptySquare;
\r
7150 } else if ((fromY == BOARD_HEIGHT-4)
\r
7152 && gameInfo.variant == VariantBerolina
\r
7153 && (board[fromY][fromX] == WhitePawn)
\r
7154 && (board[toY][toX] == EmptySquare)) {
\r
7155 board[fromY][fromX] = EmptySquare;
\r
7156 board[toY][toX] = WhitePawn;
\r
7157 if(oldEP & EP_BEROLIN_A) {
\r
7158 captured = board[fromY][fromX-1];
\r
7159 board[fromY][fromX-1] = EmptySquare;
\r
7160 }else{ captured = board[fromY][fromX+1];
\r
7161 board[fromY][fromX+1] = EmptySquare;
\r
7163 } else if (board[fromY][fromX] == king
\r
7164 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7165 && toY == fromY && toX > fromX+1) {
\r
7166 board[fromY][fromX] = EmptySquare;
\r
7167 board[toY][toX] = king;
\r
7168 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7169 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7170 } else if (board[fromY][fromX] == king
\r
7171 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7172 && toY == fromY && toX < fromX-1) {
\r
7173 board[fromY][fromX] = EmptySquare;
\r
7174 board[toY][toX] = king;
\r
7175 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7176 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7177 } else if (fromY == 7 && fromX == 3
\r
7178 && board[fromY][fromX] == BlackKing
\r
7179 && toY == 7 && toX == 5) {
\r
7180 board[fromY][fromX] = EmptySquare;
\r
7181 board[toY][toX] = BlackKing;
\r
7182 board[fromY][7] = EmptySquare;
\r
7183 board[toY][4] = BlackRook;
\r
7184 } else if (fromY == 7 && fromX == 3
\r
7185 && board[fromY][fromX] == BlackKing
\r
7186 && toY == 7 && toX == 1) {
\r
7187 board[fromY][fromX] = EmptySquare;
\r
7188 board[toY][toX] = BlackKing;
\r
7189 board[fromY][0] = EmptySquare;
\r
7190 board[toY][2] = BlackRook;
\r
7191 } else if (board[fromY][fromX] == BlackPawn
\r
7193 && gameInfo.variant != VariantXiangqi
\r
7195 /* black pawn promotion */
\r
7196 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7197 if (board[0][toX] == EmptySquare) {
\r
7198 board[0][toX] = BlackQueen;
\r
7200 if(gameInfo.variant==VariantBughouse ||
\r
7201 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7202 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7203 board[fromY][fromX] = EmptySquare;
\r
7204 } else if ((fromY == 3)
\r
7206 && gameInfo.variant != VariantXiangqi
\r
7207 && gameInfo.variant != VariantBerolina
\r
7208 && (board[fromY][fromX] == BlackPawn)
\r
7209 && (board[toY][toX] == EmptySquare)) {
\r
7210 board[fromY][fromX] = EmptySquare;
\r
7211 board[toY][toX] = BlackPawn;
\r
7212 captured = board[toY + 1][toX];
\r
7213 board[toY + 1][toX] = EmptySquare;
\r
7214 } else if ((fromY == 3)
\r
7216 && gameInfo.variant == VariantBerolina
\r
7217 && (board[fromY][fromX] == BlackPawn)
\r
7218 && (board[toY][toX] == EmptySquare)) {
\r
7219 board[fromY][fromX] = EmptySquare;
\r
7220 board[toY][toX] = BlackPawn;
\r
7221 if(oldEP & EP_BEROLIN_A) {
\r
7222 captured = board[fromY][fromX-1];
\r
7223 board[fromY][fromX-1] = EmptySquare;
\r
7224 }else{ captured = board[fromY][fromX+1];
\r
7225 board[fromY][fromX+1] = EmptySquare;
\r
7228 board[toY][toX] = board[fromY][fromX];
\r
7229 board[fromY][fromX] = EmptySquare;
\r
7232 /* [HGM] now we promote for Shogi, if needed */
\r
7233 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7234 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7237 if (gameInfo.holdingsWidth != 0) {
\r
7239 /* !!A lot more code needs to be written to support holdings */
\r
7240 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7241 /* penultimate board files, so they are automaticlly stored */
\r
7242 /* in the game history. */
\r
7243 if (fromY == DROP_RANK) {
\r
7244 /* Delete from holdings, by decreasing count */
\r
7245 /* and erasing image if necessary */
\r
7247 if(p < (int) BlackPawn) { /* white drop */
\r
7248 p -= (int)WhitePawn;
\r
7249 if(p >= gameInfo.holdingsSize) p = 0;
\r
7250 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7251 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7252 } else { /* black drop */
\r
7253 p -= (int)BlackPawn;
\r
7254 if(p >= gameInfo.holdingsSize) p = 0;
\r
7255 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7256 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7259 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7260 && gameInfo.variant != VariantBughouse ) {
\r
7261 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7262 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7263 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7264 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7266 p = (int) captured;
\r
7267 if (p >= (int) BlackPawn) {
\r
7268 p -= (int)BlackPawn;
\r
7269 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7270 /* in Shogi restore piece to its original first */
\r
7271 captured = (ChessSquare) (DEMOTED captured);
\r
7274 p = PieceToNumber((ChessSquare)p);
\r
7275 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7276 board[p][BOARD_WIDTH-2]++;
\r
7277 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7279 p -= (int)WhitePawn;
\r
7280 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7281 captured = (ChessSquare) (DEMOTED captured);
\r
7284 p = PieceToNumber((ChessSquare)p);
\r
7285 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7286 board[BOARD_HEIGHT-1-p][1]++;
\r
7287 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7291 } else if (gameInfo.variant == VariantAtomic) {
\r
7292 if (captured != EmptySquare) {
\r
7294 for (y = toY-1; y <= toY+1; y++) {
\r
7295 for (x = toX-1; x <= toX+1; x++) {
\r
7296 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7297 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7298 board[y][x] = EmptySquare;
\r
7302 board[toY][toX] = EmptySquare;
\r
7305 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7306 /* [HGM] Shogi promotions */
\r
7307 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7312 /* Updates forwardMostMove */
\r
7314 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7315 int fromX, fromY, toX, toY;
\r
7318 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7320 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7321 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7322 piece = boards[forwardMostMove][fromY][fromX];
\r
7323 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7324 if(gameInfo.variant == VariantKnightmate)
\r
7325 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7326 if(forwardMostMove == 0) {
\r
7327 if(blackPlaysFirst)
\r
7328 fprintf(serverMoves, "%s;", second.tidy);
\r
7329 fprintf(serverMoves, "%s;", first.tidy);
\r
7330 if(!blackPlaysFirst)
\r
7331 fprintf(serverMoves, "%s;", second.tidy);
\r
7332 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7333 lastLoadFlag = loadFlag;
\r
7334 // print base move
\r
7335 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7336 // print castling suffix
\r
7337 if( toY == fromY && piece == king ) {
\r
7339 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7341 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7344 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7345 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7346 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7348 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7349 // promotion suffix
\r
7350 if(promoChar != NULLCHAR)
\r
7351 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7353 fprintf(serverMoves, "/%d/%d",
\r
7354 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7355 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7356 else timeLeft = blackTimeRemaining/1000;
\r
7357 fprintf(serverMoves, "/%d", timeLeft);
\r
7359 fflush(serverMoves);
\r
7362 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7363 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7368 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7369 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7370 if (commentList[forwardMostMove+1] != NULL) {
\r
7371 free(commentList[forwardMostMove+1]);
\r
7372 commentList[forwardMostMove+1] = NULL;
\r
7374 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7375 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7376 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7377 gameInfo.result = GameUnfinished;
\r
7378 if (gameInfo.resultDetails != NULL) {
\r
7379 free(gameInfo.resultDetails);
\r
7380 gameInfo.resultDetails = NULL;
\r
7382 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7383 moveList[forwardMostMove - 1]);
\r
7384 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7385 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7386 fromY, fromX, toY, toX, promoChar,
\r
7387 parseList[forwardMostMove - 1]);
\r
7388 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7389 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7390 castlingRights[forwardMostMove]) ) {
\r
7392 case MT_STALEMATE:
\r
7396 if(gameInfo.variant != VariantShogi)
\r
7397 strcat(parseList[forwardMostMove - 1], "+");
\r
7399 case MT_CHECKMATE:
\r
7400 case MT_STAINMATE:
\r
7401 strcat(parseList[forwardMostMove - 1], "#");
\r
7404 if (appData.debugMode) {
\r
7405 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7410 /* Updates currentMove if not pausing */
\r
7412 ShowMove(fromX, fromY, toX, toY)
\r
7414 int instant = (gameMode == PlayFromGameFile) ?
\r
7415 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7416 if(appData.noGUI) return;
\r
7417 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7419 if (forwardMostMove == currentMove + 1) {
\r
7420 AnimateMove(boards[forwardMostMove - 1],
\r
7421 fromX, fromY, toX, toY);
\r
7423 if (appData.highlightLastMove) {
\r
7424 SetHighlights(fromX, fromY, toX, toY);
\r
7427 currentMove = forwardMostMove;
\r
7430 if (instant) return;
\r
7432 DisplayMove(currentMove - 1);
\r
7433 DrawPosition(FALSE, boards[currentMove]);
\r
7434 DisplayBothClocks();
\r
7435 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7438 void SendEgtPath(ChessProgramState *cps)
\r
7439 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7440 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7442 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7445 char c, *q = name+1, *r, *s;
\r
7447 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7448 while(*p && *p != ',') *q++ = *p++;
\r
7449 *q++ = ':'; *q = 0;
\r
7450 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7451 strcmp(name, ",nalimov:") == 0 ) {
\r
7452 // take nalimov path from the menu-changeable option first, if it is defined
\r
7453 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7454 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7456 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7457 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7458 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7459 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7460 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7461 c = *r; *r = 0; // temporarily null-terminate path info
\r
7462 *--q = 0; // strip of trailig ':' from name
\r
7463 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7465 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7467 if(*p == ',') p++; // read away comma to position for next format name
\r
7472 InitChessProgram(cps, setup)
\r
7473 ChessProgramState *cps;
\r
7474 int setup; /* [HGM] needed to setup FRC opening position */
\r
7476 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7477 if (appData.noChessProgram) return;
\r
7478 hintRequested = FALSE;
\r
7479 bookRequested = FALSE;
\r
7481 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7482 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7483 if(cps->memSize) { /* [HGM] memory */
\r
7484 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7485 SendToProgram(buf, cps);
\r
7487 SendEgtPath(cps); /* [HGM] EGT */
\r
7488 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7489 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7490 SendToProgram(buf, cps);
\r
7493 SendToProgram(cps->initString, cps);
\r
7494 if (gameInfo.variant != VariantNormal &&
\r
7495 gameInfo.variant != VariantLoadable
\r
7496 /* [HGM] also send variant if board size non-standard */
\r
7497 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7499 char *v = VariantName(gameInfo.variant);
\r
7500 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7501 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7502 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7503 DisplayFatalError(buf, 0, 1);
\r
7507 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7508 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7509 if( gameInfo.variant == VariantXiangqi )
\r
7510 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7511 if( gameInfo.variant == VariantShogi )
\r
7512 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7513 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7514 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7515 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7516 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7517 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7518 if( gameInfo.variant == VariantCourier )
\r
7519 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7520 if( gameInfo.variant == VariantSuper )
\r
7521 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7522 if( gameInfo.variant == VariantGreat )
\r
7523 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7526 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7527 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7528 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7529 if(StrStr(cps->variants, b) == NULL) {
\r
7530 // specific sized variant not known, check if general sizing allowed
\r
7531 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7532 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7533 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7534 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7535 DisplayFatalError(buf, 0, 1);
\r
7538 /* [HGM] here we really should compare with the maximum supported board size */
\r
7541 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7542 sprintf(buf, "variant %s\n", b);
\r
7543 SendToProgram(buf, cps);
\r
7545 currentlyInitializedVariant = gameInfo.variant;
\r
7547 /* [HGM] send opening position in FRC to first engine */
\r
7549 SendToProgram("force\n", cps);
\r
7550 SendBoard(cps, 0);
\r
7551 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7552 setboardSpoiledMachineBlack = 1;
\r
7555 if (cps->sendICS) {
\r
7556 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7557 SendToProgram(buf, cps);
\r
7559 cps->maybeThinking = FALSE;
\r
7560 cps->offeredDraw = 0;
\r
7561 if (!appData.icsActive) {
\r
7562 SendTimeControl(cps, movesPerSession, timeControl,
\r
7563 timeIncrement, appData.searchDepth,
\r
7566 if (appData.showThinking
\r
7567 // [HGM] thinking: four options require thinking output to be sent
\r
7568 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7570 SendToProgram("post\n", cps);
\r
7572 SendToProgram("hard\n", cps);
\r
7573 if (!appData.ponderNextMove) {
\r
7574 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7575 it without being sure what state we are in first. "hard"
\r
7576 is not a toggle, so that one is OK.
\r
7578 SendToProgram("easy\n", cps);
\r
7580 if (cps->usePing) {
\r
7581 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7582 SendToProgram(buf, cps);
\r
7584 cps->initDone = TRUE;
\r
7589 StartChessProgram(cps)
\r
7590 ChessProgramState *cps;
\r
7592 char buf[MSG_SIZ];
\r
7595 if (appData.noChessProgram) return;
\r
7596 cps->initDone = FALSE;
\r
7598 if (strcmp(cps->host, "localhost") == 0) {
\r
7599 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7600 } else if (*appData.remoteShell == NULLCHAR) {
\r
7601 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7603 if (*appData.remoteUser == NULLCHAR) {
\r
7604 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7607 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7608 cps->host, appData.remoteUser, cps->program);
\r
7610 err = StartChildProcess(buf, "", &cps->pr);
\r
7614 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7615 DisplayFatalError(buf, err, 1);
\r
7621 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7622 if (cps->protocolVersion > 1) {
\r
7623 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7624 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7625 cps->comboCnt = 0; // and values of combo boxes
\r
7626 SendToProgram(buf, cps);
\r
7628 SendToProgram("xboard\n", cps);
\r
7634 TwoMachinesEventIfReady P((void))
\r
7636 if (first.lastPing != first.lastPong) {
\r
7637 DisplayMessage("", _("Waiting for first chess program"));
\r
7638 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7641 if (second.lastPing != second.lastPong) {
\r
7642 DisplayMessage("", _("Waiting for second chess program"));
\r
7643 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7647 TwoMachinesEvent();
\r
7651 NextMatchGame P((void))
\r
7653 int index; /* [HGM] autoinc: step lod index during match */
\r
7654 Reset(FALSE, TRUE);
\r
7655 if (*appData.loadGameFile != NULLCHAR) {
\r
7656 index = appData.loadGameIndex;
\r
7657 if(index < 0) { // [HGM] autoinc
\r
7658 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7659 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7661 LoadGameFromFile(appData.loadGameFile,
\r
7663 appData.loadGameFile, FALSE);
\r
7664 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7665 index = appData.loadPositionIndex;
\r
7666 if(index < 0) { // [HGM] autoinc
\r
7667 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7668 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7670 LoadPositionFromFile(appData.loadPositionFile,
\r
7672 appData.loadPositionFile);
\r
7674 TwoMachinesEventIfReady();
\r
7677 void UserAdjudicationEvent( int result )
\r
7679 ChessMove gameResult = GameIsDrawn;
\r
7681 if( result > 0 ) {
\r
7682 gameResult = WhiteWins;
\r
7684 else if( result < 0 ) {
\r
7685 gameResult = BlackWins;
\r
7688 if( gameMode == TwoMachinesPlay ) {
\r
7689 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7695 GameEnds(result, resultDetails, whosays)
\r
7697 char *resultDetails;
\r
7700 GameMode nextGameMode;
\r
7702 char buf[MSG_SIZ];
\r
7704 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7707 if (appData.debugMode) {
\r
7708 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7709 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7712 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7713 /* If we are playing on ICS, the server decides when the
\r
7714 game is over, but the engine can offer to draw, claim
\r
7715 a draw, or resign.
\r
7718 if (appData.zippyPlay && first.initDone) {
\r
7719 if (result == GameIsDrawn) {
\r
7720 /* In case draw still needs to be claimed */
\r
7721 SendToICS(ics_prefix);
\r
7722 SendToICS("draw\n");
\r
7723 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7724 SendToICS(ics_prefix);
\r
7725 SendToICS("resign\n");
\r
7729 endingGame = 0; /* [HGM] crash */
\r
7733 /* If we're loading the game from a file, stop */
\r
7734 if (whosays == GE_FILE) {
\r
7735 (void) StopLoadGameTimer();
\r
7736 gameFileFP = NULL;
\r
7739 /* Cancel draw offers */
\r
7740 first.offeredDraw = second.offeredDraw = 0;
\r
7742 /* If this is an ICS game, only ICS can really say it's done;
\r
7743 if not, anyone can. */
\r
7744 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7745 gameMode == IcsPlayingBlack ||
\r
7746 gameMode == IcsObserving ||
\r
7747 gameMode == IcsExamining);
\r
7749 if (!isIcsGame || whosays == GE_ICS) {
\r
7750 /* OK -- not an ICS game, or ICS said it was done */
\r
7752 if (!isIcsGame && !appData.noChessProgram)
\r
7753 SetUserThinkingEnables();
\r
7755 /* [HGM] if a machine claims the game end we verify this claim */
\r
7756 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7757 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7759 ChessMove trueResult = (ChessMove) -1;
\r
7761 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7762 first.twoMachinesColor[0] :
\r
7763 second.twoMachinesColor[0] ;
\r
7765 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7766 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7767 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7768 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
\r
7770 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
\r
7771 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7772 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7774 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
\r
7775 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7778 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7779 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7780 || gameInfo.variant == VariantGreat) &&
\r
7781 (result == WhiteWins && claimer == 'w' ||
\r
7782 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7783 if (appData.debugMode) {
\r
7784 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7785 result, epStatus[forwardMostMove], forwardMostMove);
\r
7787 if(result != trueResult) {
\r
7788 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7789 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7790 resultDetails = buf;
\r
7793 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7794 && (forwardMostMove <= backwardMostMove ||
\r
7795 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7796 (claimer=='b')==(forwardMostMove&1))
\r
7798 /* [HGM] verify: draws that were not flagged are false claims */
\r
7799 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7800 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7801 resultDetails = buf;
\r
7803 /* (Claiming a loss is accepted no questions asked!) */
\r
7805 /* [HGM] bare: don't allow bare King to win */
\r
7806 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7807 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7808 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7809 && result != GameIsDrawn)
\r
7810 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7811 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7812 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7813 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7815 if (appData.debugMode) {
\r
7816 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7817 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7820 result = GameIsDrawn;
\r
7821 sprintf(buf, "%s but bare king", resultDetails);
\r
7822 resultDetails = buf;
\r
7828 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7829 if(result==WhiteWins) c = '+';
\r
7830 if(result==BlackWins) c = '-';
\r
7831 if(resultDetails != NULL)
\r
7832 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7834 if (resultDetails != NULL) {
\r
7835 gameInfo.result = result;
\r
7836 gameInfo.resultDetails = StrSave(resultDetails);
\r
7838 /* display last move only if game was not loaded from file */
\r
7839 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7840 DisplayMove(currentMove - 1);
\r
7842 if (forwardMostMove != 0) {
\r
7843 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7844 if (*appData.saveGameFile != NULLCHAR) {
\r
7845 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7846 } else if (appData.autoSaveGames) {
\r
7849 if (*appData.savePositionFile != NULLCHAR) {
\r
7850 SavePositionToFile(appData.savePositionFile);
\r
7855 /* Tell program how game ended in case it is learning */
\r
7856 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7857 /* engine died and we got here through time loss. In that */
\r
7858 /* case we will get a fatal error writing the pipe, which */
\r
7859 /* would otherwise lose us the PGN. */
\r
7860 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7861 /* output during GameEnds should never be fatal anymore */
\r
7862 if (gameMode == MachinePlaysWhite ||
\r
7863 gameMode == MachinePlaysBlack ||
\r
7864 gameMode == TwoMachinesPlay ||
\r
7865 gameMode == IcsPlayingWhite ||
\r
7866 gameMode == IcsPlayingBlack ||
\r
7867 gameMode == BeginningOfGame) {
\r
7868 char buf[MSG_SIZ];
\r
7869 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7871 if (first.pr != NoProc) {
\r
7872 SendToProgram(buf, &first);
\r
7874 if (second.pr != NoProc &&
\r
7875 gameMode == TwoMachinesPlay) {
\r
7876 SendToProgram(buf, &second);
\r
7881 if (appData.icsActive) {
\r
7882 if (appData.quietPlay &&
\r
7883 (gameMode == IcsPlayingWhite ||
\r
7884 gameMode == IcsPlayingBlack)) {
\r
7885 SendToICS(ics_prefix);
\r
7886 SendToICS("set shout 1\n");
\r
7888 nextGameMode = IcsIdle;
\r
7889 ics_user_moved = FALSE;
\r
7890 /* clean up premove. It's ugly when the game has ended and the
\r
7891 * premove highlights are still on the board.
\r
7894 gotPremove = FALSE;
\r
7895 ClearPremoveHighlights();
\r
7896 DrawPosition(FALSE, boards[currentMove]);
\r
7898 if (whosays == GE_ICS) {
\r
7901 if (gameMode == IcsPlayingWhite)
\r
7902 PlayIcsWinSound();
\r
7903 else if(gameMode == IcsPlayingBlack)
\r
7904 PlayIcsLossSound();
\r
7907 if (gameMode == IcsPlayingBlack)
\r
7908 PlayIcsWinSound();
\r
7909 else if(gameMode == IcsPlayingWhite)
\r
7910 PlayIcsLossSound();
\r
7913 PlayIcsDrawSound();
\r
7916 PlayIcsUnfinishedSound();
\r
7919 } else if (gameMode == EditGame ||
\r
7920 gameMode == PlayFromGameFile ||
\r
7921 gameMode == AnalyzeMode ||
\r
7922 gameMode == AnalyzeFile) {
\r
7923 nextGameMode = gameMode;
\r
7925 nextGameMode = EndOfGame;
\r
7930 nextGameMode = gameMode;
\r
7933 if (appData.noChessProgram) {
\r
7934 gameMode = nextGameMode;
\r
7936 endingGame = 0; /* [HGM] crash */
\r
7940 if (first.reuse) {
\r
7941 /* Put first chess program into idle state */
\r
7942 if (first.pr != NoProc &&
\r
7943 (gameMode == MachinePlaysWhite ||
\r
7944 gameMode == MachinePlaysBlack ||
\r
7945 gameMode == TwoMachinesPlay ||
\r
7946 gameMode == IcsPlayingWhite ||
\r
7947 gameMode == IcsPlayingBlack ||
\r
7948 gameMode == BeginningOfGame)) {
\r
7949 SendToProgram("force\n", &first);
\r
7950 if (first.usePing) {
\r
7951 char buf[MSG_SIZ];
\r
7952 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7953 SendToProgram(buf, &first);
\r
7956 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7957 /* Kill off first chess program */
\r
7958 if (first.isr != NULL)
\r
7959 RemoveInputSource(first.isr);
\r
7962 if (first.pr != NoProc) {
\r
7963 ExitAnalyzeMode();
\r
7964 DoSleep( appData.delayBeforeQuit );
\r
7965 SendToProgram("quit\n", &first);
\r
7966 DoSleep( appData.delayAfterQuit );
\r
7967 DestroyChildProcess(first.pr, first.useSigterm);
\r
7969 first.pr = NoProc;
\r
7971 if (second.reuse) {
\r
7972 /* Put second chess program into idle state */
\r
7973 if (second.pr != NoProc &&
\r
7974 gameMode == TwoMachinesPlay) {
\r
7975 SendToProgram("force\n", &second);
\r
7976 if (second.usePing) {
\r
7977 char buf[MSG_SIZ];
\r
7978 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7979 SendToProgram(buf, &second);
\r
7982 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7983 /* Kill off second chess program */
\r
7984 if (second.isr != NULL)
\r
7985 RemoveInputSource(second.isr);
\r
7986 second.isr = NULL;
\r
7988 if (second.pr != NoProc) {
\r
7989 DoSleep( appData.delayBeforeQuit );
\r
7990 SendToProgram("quit\n", &second);
\r
7991 DoSleep( appData.delayAfterQuit );
\r
7992 DestroyChildProcess(second.pr, second.useSigterm);
\r
7994 second.pr = NoProc;
\r
7997 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8000 if (first.twoMachinesColor[0] == 'w') {
\r
8001 first.matchWins++;
\r
8003 second.matchWins++;
\r
8007 if (first.twoMachinesColor[0] == 'b') {
\r
8008 first.matchWins++;
\r
8010 second.matchWins++;
\r
8016 if (matchGame < appData.matchGames) {
\r
8018 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8019 tmp = first.twoMachinesColor;
\r
8020 first.twoMachinesColor = second.twoMachinesColor;
\r
8021 second.twoMachinesColor = tmp;
\r
8023 gameMode = nextGameMode;
\r
8025 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8026 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8027 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8028 endingGame = 0; /* [HGM] crash */
\r
8031 char buf[MSG_SIZ];
\r
8032 gameMode = nextGameMode;
\r
8033 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8034 first.tidy, second.tidy,
\r
8035 first.matchWins, second.matchWins,
\r
8036 appData.matchGames - (first.matchWins + second.matchWins));
\r
8037 DisplayFatalError(buf, 0, 0);
\r
8040 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8041 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8042 ExitAnalyzeMode();
\r
8043 gameMode = nextGameMode;
\r
8045 endingGame = 0; /* [HGM] crash */
\r
8048 /* Assumes program was just initialized (initString sent).
\r
8049 Leaves program in force mode. */
\r
8051 FeedMovesToProgram(cps, upto)
\r
8052 ChessProgramState *cps;
\r
8057 if (appData.debugMode)
\r
8058 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8059 startedFromSetupPosition ? "position and " : "",
\r
8060 backwardMostMove, upto, cps->which);
\r
8061 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8062 // [HGM] variantswitch: make engine aware of new variant
\r
8063 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8064 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8065 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8066 SendToProgram(buf, cps);
\r
8067 currentlyInitializedVariant = gameInfo.variant;
\r
8069 SendToProgram("force\n", cps);
\r
8070 if (startedFromSetupPosition) {
\r
8071 SendBoard(cps, backwardMostMove);
\r
8072 if (appData.debugMode) {
\r
8073 fprintf(debugFP, "feedMoves\n");
\r
8076 for (i = backwardMostMove; i < upto; i++) {
\r
8077 SendMoveToProgram(i, cps);
\r
8083 ResurrectChessProgram()
\r
8085 /* The chess program may have exited.
\r
8086 If so, restart it and feed it all the moves made so far. */
\r
8088 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8090 StartChessProgram(&first);
\r
8091 InitChessProgram(&first, FALSE);
\r
8092 FeedMovesToProgram(&first, currentMove);
\r
8094 if (!first.sendTime) {
\r
8095 /* can't tell gnuchess what its clock should read,
\r
8096 so we bow to its notion. */
\r
8098 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8099 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8102 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8103 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8104 SendToProgram("analyze\n", &first);
\r
8105 first.analyzing = TRUE;
\r
8110 * Button procedures
\r
8113 Reset(redraw, init)
\r
8118 if (appData.debugMode) {
\r
8119 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8120 redraw, init, gameMode);
\r
8122 pausing = pauseExamInvalid = FALSE;
\r
8123 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8125 whiteFlag = blackFlag = FALSE;
\r
8126 userOfferedDraw = FALSE;
\r
8127 hintRequested = bookRequested = FALSE;
\r
8128 first.maybeThinking = FALSE;
\r
8129 second.maybeThinking = FALSE;
\r
8130 first.bookSuspend = FALSE; // [HGM] book
\r
8131 second.bookSuspend = FALSE;
\r
8132 thinkOutput[0] = NULLCHAR;
\r
8133 lastHint[0] = NULLCHAR;
\r
8134 ClearGameInfo(&gameInfo);
\r
8135 gameInfo.variant = StringToVariant(appData.variant);
\r
8136 ics_user_moved = ics_clock_paused = FALSE;
\r
8137 ics_getting_history = H_FALSE;
\r
8139 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8140 ClearProgramStats();
\r
8141 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8144 ClearHighlights();
\r
8145 flipView = appData.flipView;
\r
8146 ClearPremoveHighlights();
\r
8147 gotPremove = FALSE;
\r
8148 alarmSounded = FALSE;
\r
8150 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8151 if(appData.serverMovesName != NULL) {
\r
8152 /* [HGM] prepare to make moves file for broadcasting */
\r
8153 clock_t t = clock();
\r
8154 if(serverMoves != NULL) fclose(serverMoves);
\r
8155 serverMoves = fopen(appData.serverMovesName, "r");
\r
8156 if(serverMoves != NULL) {
\r
8157 fclose(serverMoves);
\r
8158 /* delay 15 sec before overwriting, so all clients can see end */
\r
8159 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8161 serverMoves = fopen(appData.serverMovesName, "w");
\r
8164 ExitAnalyzeMode();
\r
8165 gameMode = BeginningOfGame;
\r
8167 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8168 InitPosition(redraw);
\r
8169 for (i = 0; i < MAX_MOVES; i++) {
\r
8170 if (commentList[i] != NULL) {
\r
8171 free(commentList[i]);
\r
8172 commentList[i] = NULL;
\r
8176 timeRemaining[0][0] = whiteTimeRemaining;
\r
8177 timeRemaining[1][0] = blackTimeRemaining;
\r
8178 if (first.pr == NULL) {
\r
8179 StartChessProgram(&first);
\r
8182 InitChessProgram(&first, startedFromSetupPosition);
\r
8185 DisplayMessage("", "");
\r
8186 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8190 AutoPlayGameLoop()
\r
8193 if (!AutoPlayOneMove())
\r
8195 if (matchMode || appData.timeDelay == 0)
\r
8197 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8199 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8208 int fromX, fromY, toX, toY;
\r
8210 if (appData.debugMode) {
\r
8211 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8214 if (gameMode != PlayFromGameFile)
\r
8217 if (currentMove >= forwardMostMove) {
\r
8218 gameMode = EditGame;
\r
8221 /* [AS] Clear current move marker at the end of a game */
\r
8222 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8227 toX = moveList[currentMove][2] - AAA;
\r
8228 toY = moveList[currentMove][3] - ONE;
\r
8230 if (moveList[currentMove][1] == '@') {
\r
8231 if (appData.highlightLastMove) {
\r
8232 SetHighlights(-1, -1, toX, toY);
\r
8235 fromX = moveList[currentMove][0] - AAA;
\r
8236 fromY = moveList[currentMove][1] - ONE;
\r
8238 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8240 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8242 if (appData.highlightLastMove) {
\r
8243 SetHighlights(fromX, fromY, toX, toY);
\r
8246 DisplayMove(currentMove);
\r
8247 SendMoveToProgram(currentMove++, &first);
\r
8248 DisplayBothClocks();
\r
8249 DrawPosition(FALSE, boards[currentMove]);
\r
8250 // [HGM] PV info: always display, routine tests if empty
\r
8251 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8257 LoadGameOneMove(readAhead)
\r
8258 ChessMove readAhead;
\r
8260 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8261 char promoChar = NULLCHAR;
\r
8262 ChessMove moveType;
\r
8263 char move[MSG_SIZ];
\r
8266 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8267 gameMode != AnalyzeMode && gameMode != Training) {
\r
8268 gameFileFP = NULL;
\r
8272 yyboardindex = forwardMostMove;
\r
8273 if (readAhead != (ChessMove)0) {
\r
8274 moveType = readAhead;
\r
8276 if (gameFileFP == NULL)
\r
8278 moveType = (ChessMove) yylex();
\r
8282 switch (moveType) {
\r
8284 if (appData.debugMode)
\r
8285 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8287 if (*p == '{' || *p == '[' || *p == '(') {
\r
8288 p[strlen(p) - 1] = NULLCHAR;
\r
8292 /* append the comment but don't display it */
\r
8293 while (*p == '\n') p++;
\r
8294 AppendComment(currentMove, p);
\r
8297 case WhiteCapturesEnPassant:
\r
8298 case BlackCapturesEnPassant:
\r
8299 case WhitePromotionChancellor:
\r
8300 case BlackPromotionChancellor:
\r
8301 case WhitePromotionArchbishop:
\r
8302 case BlackPromotionArchbishop:
\r
8303 case WhitePromotionCentaur:
\r
8304 case BlackPromotionCentaur:
\r
8305 case WhitePromotionQueen:
\r
8306 case BlackPromotionQueen:
\r
8307 case WhitePromotionRook:
\r
8308 case BlackPromotionRook:
\r
8309 case WhitePromotionBishop:
\r
8310 case BlackPromotionBishop:
\r
8311 case WhitePromotionKnight:
\r
8312 case BlackPromotionKnight:
\r
8313 case WhitePromotionKing:
\r
8314 case BlackPromotionKing:
\r
8316 case WhiteKingSideCastle:
\r
8317 case WhiteQueenSideCastle:
\r
8318 case BlackKingSideCastle:
\r
8319 case BlackQueenSideCastle:
\r
8320 case WhiteKingSideCastleWild:
\r
8321 case WhiteQueenSideCastleWild:
\r
8322 case BlackKingSideCastleWild:
\r
8323 case BlackQueenSideCastleWild:
\r
8325 case WhiteHSideCastleFR:
\r
8326 case WhiteASideCastleFR:
\r
8327 case BlackHSideCastleFR:
\r
8328 case BlackASideCastleFR:
\r
8330 if (appData.debugMode)
\r
8331 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8332 fromX = currentMoveString[0] - AAA;
\r
8333 fromY = currentMoveString[1] - ONE;
\r
8334 toX = currentMoveString[2] - AAA;
\r
8335 toY = currentMoveString[3] - ONE;
\r
8336 promoChar = currentMoveString[4];
\r
8341 if (appData.debugMode)
\r
8342 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8343 fromX = moveType == WhiteDrop ?
\r
8344 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8345 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8346 fromY = DROP_RANK;
\r
8347 toX = currentMoveString[2] - AAA;
\r
8348 toY = currentMoveString[3] - ONE;
\r
8354 case GameUnfinished:
\r
8355 if (appData.debugMode)
\r
8356 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8357 p = strchr(yy_text, '{');
\r
8358 if (p == NULL) p = strchr(yy_text, '(');
\r
8361 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8363 q = strchr(p, *p == '{' ? '}' : ')');
\r
8364 if (q != NULL) *q = NULLCHAR;
\r
8367 GameEnds(moveType, p, GE_FILE);
\r
8369 if (cmailMsgLoaded) {
\r
8370 ClearHighlights();
\r
8371 flipView = WhiteOnMove(currentMove);
\r
8372 if (moveType == GameUnfinished) flipView = !flipView;
\r
8373 if (appData.debugMode)
\r
8374 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8378 case (ChessMove) 0: /* end of file */
\r
8379 if (appData.debugMode)
\r
8380 fprintf(debugFP, "Parser hit end of file\n");
\r
8381 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8382 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8386 case MT_CHECKMATE:
\r
8387 case MT_STAINMATE:
\r
8388 if (WhiteOnMove(currentMove)) {
\r
8389 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8391 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8394 case MT_STALEMATE:
\r
8395 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8401 case MoveNumberOne:
\r
8402 if (lastLoadGameStart == GNUChessGame) {
\r
8403 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8404 if (appData.debugMode)
\r
8405 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8406 yy_text, (int) moveType);
\r
8407 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8409 /* else fall thru */
\r
8412 case GNUChessGame:
\r
8414 /* Reached start of next game in file */
\r
8415 if (appData.debugMode)
\r
8416 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8417 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8418 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8422 case MT_CHECKMATE:
\r
8423 case MT_STAINMATE:
\r
8424 if (WhiteOnMove(currentMove)) {
\r
8425 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8427 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8430 case MT_STALEMATE:
\r
8431 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8437 case PositionDiagram: /* should not happen; ignore */
\r
8438 case ElapsedTime: /* ignore */
\r
8439 case NAG: /* ignore */
\r
8440 if (appData.debugMode)
\r
8441 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8442 yy_text, (int) moveType);
\r
8443 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8446 if (appData.testLegality) {
\r
8447 if (appData.debugMode)
\r
8448 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8449 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8450 (forwardMostMove / 2) + 1,
\r
8451 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8452 DisplayError(move, 0);
\r
8455 if (appData.debugMode)
\r
8456 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8457 yy_text, currentMoveString);
\r
8458 fromX = currentMoveString[0] - AAA;
\r
8459 fromY = currentMoveString[1] - ONE;
\r
8460 toX = currentMoveString[2] - AAA;
\r
8461 toY = currentMoveString[3] - ONE;
\r
8462 promoChar = currentMoveString[4];
\r
8466 case AmbiguousMove:
\r
8467 if (appData.debugMode)
\r
8468 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8469 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8470 (forwardMostMove / 2) + 1,
\r
8471 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8472 DisplayError(move, 0);
\r
8477 case ImpossibleMove:
\r
8478 if (appData.debugMode)
\r
8479 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8480 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8481 (forwardMostMove / 2) + 1,
\r
8482 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8483 DisplayError(move, 0);
\r
8489 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8490 DrawPosition(FALSE, boards[currentMove]);
\r
8491 DisplayBothClocks();
\r
8492 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8493 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8495 (void) StopLoadGameTimer();
\r
8496 gameFileFP = NULL;
\r
8497 cmailOldMove = forwardMostMove;
\r
8500 /* currentMoveString is set as a side-effect of yylex */
\r
8501 strcat(currentMoveString, "\n");
\r
8502 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8504 thinkOutput[0] = NULLCHAR;
\r
8505 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8506 currentMove = forwardMostMove;
\r
8511 /* Load the nth game from the given file */
\r
8513 LoadGameFromFile(filename, n, title, useList)
\r
8517 /*Boolean*/ int useList;
\r
8520 char buf[MSG_SIZ];
\r
8522 if (strcmp(filename, "-") == 0) {
\r
8526 f = fopen(filename, "rb");
\r
8528 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8529 DisplayError(buf, errno);
\r
8533 if (fseek(f, 0, 0) == -1) {
\r
8534 /* f is not seekable; probably a pipe */
\r
8537 if (useList && n == 0) {
\r
8538 int error = GameListBuild(f);
\r
8540 DisplayError(_("Cannot build game list"), error);
\r
8541 } else if (!ListEmpty(&gameList) &&
\r
8542 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8543 GameListPopUp(f, title);
\r
8546 GameListDestroy();
\r
8549 if (n == 0) n = 1;
\r
8550 return LoadGame(f, n, title, FALSE);
\r
8555 MakeRegisteredMove()
\r
8557 int fromX, fromY, toX, toY;
\r
8559 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8560 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8563 if (appData.debugMode)
\r
8564 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8565 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8567 thinkOutput[0] = NULLCHAR;
\r
8568 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8569 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8570 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8571 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8572 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8573 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8574 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8575 ShowMove(fromX, fromY, toX, toY);
\r
8577 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8578 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8583 case MT_CHECKMATE:
\r
8584 case MT_STAINMATE:
\r
8585 if (WhiteOnMove(currentMove)) {
\r
8586 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8588 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8592 case MT_STALEMATE:
\r
8593 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8599 case CMAIL_RESIGN:
\r
8600 if (WhiteOnMove(currentMove)) {
\r
8601 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8603 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8607 case CMAIL_ACCEPT:
\r
8608 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8619 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8621 CmailLoadGame(f, gameNumber, title, useList)
\r
8629 if (gameNumber > nCmailGames) {
\r
8630 DisplayError(_("No more games in this message"), 0);
\r
8633 if (f == lastLoadGameFP) {
\r
8634 int offset = gameNumber - lastLoadGameNumber;
\r
8635 if (offset == 0) {
\r
8636 cmailMsg[0] = NULLCHAR;
\r
8637 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8638 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8639 nCmailMovesRegistered--;
\r
8641 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8642 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8643 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8646 if (! RegisterMove()) return FALSE;
\r
8650 retVal = LoadGame(f, gameNumber, title, useList);
\r
8652 /* Make move registered during previous look at this game, if any */
\r
8653 MakeRegisteredMove();
\r
8655 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8656 commentList[currentMove]
\r
8657 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8658 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8664 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8666 ReloadGame(offset)
\r
8669 int gameNumber = lastLoadGameNumber + offset;
\r
8670 if (lastLoadGameFP == NULL) {
\r
8671 DisplayError(_("No game has been loaded yet"), 0);
\r
8674 if (gameNumber <= 0) {
\r
8675 DisplayError(_("Can't back up any further"), 0);
\r
8678 if (cmailMsgLoaded) {
\r
8679 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8680 lastLoadGameTitle, lastLoadGameUseList);
\r
8682 return LoadGame(lastLoadGameFP, gameNumber,
\r
8683 lastLoadGameTitle, lastLoadGameUseList);
\r
8689 /* Load the nth game from open file f */
\r
8691 LoadGame(f, gameNumber, title, useList)
\r
8698 char buf[MSG_SIZ];
\r
8699 int gn = gameNumber;
\r
8700 ListGame *lg = NULL;
\r
8701 int numPGNTags = 0;
\r
8703 GameMode oldGameMode;
\r
8704 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8706 if (appData.debugMode)
\r
8707 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8709 if (gameMode == Training )
\r
8710 SetTrainingModeOff();
\r
8712 oldGameMode = gameMode;
\r
8713 if (gameMode != BeginningOfGame) {
\r
8714 Reset(FALSE, TRUE);
\r
8718 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8719 fclose(lastLoadGameFP);
\r
8723 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8726 fseek(f, lg->offset, 0);
\r
8727 GameListHighlight(gameNumber);
\r
8731 DisplayError(_("Game number out of range"), 0);
\r
8735 GameListDestroy();
\r
8736 if (fseek(f, 0, 0) == -1) {
\r
8737 if (f == lastLoadGameFP ?
\r
8738 gameNumber == lastLoadGameNumber + 1 :
\r
8739 gameNumber == 1) {
\r
8742 DisplayError(_("Can't seek on game file"), 0);
\r
8747 lastLoadGameFP = f;
\r
8748 lastLoadGameNumber = gameNumber;
\r
8749 strcpy(lastLoadGameTitle, title);
\r
8750 lastLoadGameUseList = useList;
\r
8754 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8755 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8756 lg->gameInfo.black);
\r
8757 DisplayTitle(buf);
\r
8758 } else if (*title != NULLCHAR) {
\r
8759 if (gameNumber > 1) {
\r
8760 sprintf(buf, "%s %d", title, gameNumber);
\r
8761 DisplayTitle(buf);
\r
8763 DisplayTitle(title);
\r
8767 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8768 gameMode = PlayFromGameFile;
\r
8772 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8773 CopyBoard(boards[0], initialPosition);
\r
8777 * Skip the first gn-1 games in the file.
\r
8778 * Also skip over anything that precedes an identifiable
\r
8779 * start of game marker, to avoid being confused by
\r
8780 * garbage at the start of the file. Currently
\r
8781 * recognized start of game markers are the move number "1",
\r
8782 * the pattern "gnuchess .* game", the pattern
\r
8783 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8784 * A game that starts with one of the latter two patterns
\r
8785 * will also have a move number 1, possibly
\r
8786 * following a position diagram.
\r
8787 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8788 * start with an unnumbered move. Does that break anything?
\r
8790 cm = lastLoadGameStart = (ChessMove) 0;
\r
8792 yyboardindex = forwardMostMove;
\r
8793 cm = (ChessMove) yylex();
\r
8795 case (ChessMove) 0:
\r
8796 if (cmailMsgLoaded) {
\r
8797 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8799 Reset(TRUE, TRUE);
\r
8800 DisplayError(_("Game not found in file"), 0);
\r
8804 case GNUChessGame:
\r
8807 lastLoadGameStart = cm;
\r
8810 case MoveNumberOne:
\r
8811 switch (lastLoadGameStart) {
\r
8812 case GNUChessGame:
\r
8816 case MoveNumberOne:
\r
8817 case (ChessMove) 0:
\r
8818 gn--; /* count this game */
\r
8819 lastLoadGameStart = cm;
\r
8828 switch (lastLoadGameStart) {
\r
8829 case GNUChessGame:
\r
8831 case MoveNumberOne:
\r
8832 case (ChessMove) 0:
\r
8833 gn--; /* count this game */
\r
8834 lastLoadGameStart = cm;
\r
8837 lastLoadGameStart = cm; /* game counted already */
\r
8845 yyboardindex = forwardMostMove;
\r
8846 cm = (ChessMove) yylex();
\r
8847 } while (cm == PGNTag || cm == Comment);
\r
8854 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8855 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8856 != CMAIL_OLD_RESULT) {
\r
8857 nCmailResults ++ ;
\r
8858 cmailResult[ CMAIL_MAX_GAMES
\r
8859 - gn - 1] = CMAIL_OLD_RESULT;
\r
8865 /* Only a NormalMove can be at the start of a game
\r
8866 * without a position diagram. */
\r
8867 if (lastLoadGameStart == (ChessMove) 0) {
\r
8869 lastLoadGameStart = MoveNumberOne;
\r
8878 if (appData.debugMode)
\r
8879 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8881 if (cm == XBoardGame) {
\r
8882 /* Skip any header junk before position diagram and/or move 1 */
\r
8884 yyboardindex = forwardMostMove;
\r
8885 cm = (ChessMove) yylex();
\r
8887 if (cm == (ChessMove) 0 ||
\r
8888 cm == GNUChessGame || cm == XBoardGame) {
\r
8889 /* Empty game; pretend end-of-file and handle later */
\r
8890 cm = (ChessMove) 0;
\r
8894 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8895 cm == PGNTag || cm == Comment)
\r
8898 } else if (cm == GNUChessGame) {
\r
8899 if (gameInfo.event != NULL) {
\r
8900 free(gameInfo.event);
\r
8902 gameInfo.event = StrSave(yy_text);
\r
8905 startedFromSetupPosition = FALSE;
\r
8906 while (cm == PGNTag) {
\r
8907 if (appData.debugMode)
\r
8908 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8909 err = ParsePGNTag(yy_text, &gameInfo);
\r
8910 if (!err) numPGNTags++;
\r
8912 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8913 if(gameInfo.variant != oldVariant) {
\r
8914 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8915 InitPosition(TRUE);
\r
8916 oldVariant = gameInfo.variant;
\r
8917 if (appData.debugMode)
\r
8918 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8922 if (gameInfo.fen != NULL) {
\r
8923 Board initial_position;
\r
8924 startedFromSetupPosition = TRUE;
\r
8925 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8926 Reset(TRUE, TRUE);
\r
8927 DisplayError(_("Bad FEN position in file"), 0);
\r
8930 CopyBoard(boards[0], initial_position);
\r
8931 if (blackPlaysFirst) {
\r
8932 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8933 CopyBoard(boards[1], initial_position);
\r
8934 strcpy(moveList[0], "");
\r
8935 strcpy(parseList[0], "");
\r
8936 timeRemaining[0][1] = whiteTimeRemaining;
\r
8937 timeRemaining[1][1] = blackTimeRemaining;
\r
8938 if (commentList[0] != NULL) {
\r
8939 commentList[1] = commentList[0];
\r
8940 commentList[0] = NULL;
\r
8943 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8945 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8947 initialRulePlies = FENrulePlies;
\r
8948 epStatus[forwardMostMove] = FENepStatus;
\r
8949 for( i=0; i< nrCastlingRights; i++ )
\r
8950 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8952 yyboardindex = forwardMostMove;
\r
8953 free(gameInfo.fen);
\r
8954 gameInfo.fen = NULL;
\r
8957 yyboardindex = forwardMostMove;
\r
8958 cm = (ChessMove) yylex();
\r
8960 /* Handle comments interspersed among the tags */
\r
8961 while (cm == Comment) {
\r
8963 if (appData.debugMode)
\r
8964 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8966 if (*p == '{' || *p == '[' || *p == '(') {
\r
8967 p[strlen(p) - 1] = NULLCHAR;
\r
8970 while (*p == '\n') p++;
\r
8971 AppendComment(currentMove, p);
\r
8972 yyboardindex = forwardMostMove;
\r
8973 cm = (ChessMove) yylex();
\r
8977 /* don't rely on existence of Event tag since if game was
\r
8978 * pasted from clipboard the Event tag may not exist
\r
8980 if (numPGNTags > 0){
\r
8982 if (gameInfo.variant == VariantNormal) {
\r
8983 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8986 if( appData.autoDisplayTags ) {
\r
8987 tags = PGNTags(&gameInfo);
\r
8988 TagsPopUp(tags, CmailMsg());
\r
8993 /* Make something up, but don't display it now */
\r
8998 if (cm == PositionDiagram) {
\r
9001 Board initial_position;
\r
9003 if (appData.debugMode)
\r
9004 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9006 if (!startedFromSetupPosition) {
\r
9008 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9009 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9019 initial_position[i][j++] = CharToPiece(*p);
\r
9022 while (*p == ' ' || *p == '\t' ||
\r
9023 *p == '\n' || *p == '\r') p++;
\r
9025 if (strncmp(p, "black", strlen("black"))==0)
\r
9026 blackPlaysFirst = TRUE;
\r
9028 blackPlaysFirst = FALSE;
\r
9029 startedFromSetupPosition = TRUE;
\r
9031 CopyBoard(boards[0], initial_position);
\r
9032 if (blackPlaysFirst) {
\r
9033 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9034 CopyBoard(boards[1], initial_position);
\r
9035 strcpy(moveList[0], "");
\r
9036 strcpy(parseList[0], "");
\r
9037 timeRemaining[0][1] = whiteTimeRemaining;
\r
9038 timeRemaining[1][1] = blackTimeRemaining;
\r
9039 if (commentList[0] != NULL) {
\r
9040 commentList[1] = commentList[0];
\r
9041 commentList[0] = NULL;
\r
9044 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9047 yyboardindex = forwardMostMove;
\r
9048 cm = (ChessMove) yylex();
\r
9051 if (first.pr == NoProc) {
\r
9052 StartChessProgram(&first);
\r
9054 InitChessProgram(&first, FALSE);
\r
9055 SendToProgram("force\n", &first);
\r
9056 if (startedFromSetupPosition) {
\r
9057 SendBoard(&first, forwardMostMove);
\r
9058 if (appData.debugMode) {
\r
9059 fprintf(debugFP, "Load Game\n");
\r
9061 DisplayBothClocks();
\r
9064 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9065 loadFlag = appData.suppressLoadMoves;
\r
9067 while (cm == Comment) {
\r
9069 if (appData.debugMode)
\r
9070 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9072 if (*p == '{' || *p == '[' || *p == '(') {
\r
9073 p[strlen(p) - 1] = NULLCHAR;
\r
9076 while (*p == '\n') p++;
\r
9077 AppendComment(currentMove, p);
\r
9078 yyboardindex = forwardMostMove;
\r
9079 cm = (ChessMove) yylex();
\r
9082 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9083 cm == WhiteWins || cm == BlackWins ||
\r
9084 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9085 DisplayMessage("", _("No moves in game"));
\r
9086 if (cmailMsgLoaded) {
\r
9087 if (appData.debugMode)
\r
9088 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9089 ClearHighlights();
\r
9092 DrawPosition(FALSE, boards[currentMove]);
\r
9093 DisplayBothClocks();
\r
9094 gameMode = EditGame;
\r
9096 gameFileFP = NULL;
\r
9101 // [HGM] PV info: routine tests if comment empty
\r
9102 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9103 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9105 if (!matchMode && appData.timeDelay != 0)
\r
9106 DrawPosition(FALSE, boards[currentMove]);
\r
9108 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9109 programStats.ok_to_send = 1;
\r
9112 /* if the first token after the PGN tags is a move
\r
9113 * and not move number 1, retrieve it from the parser
\r
9115 if (cm != MoveNumberOne)
\r
9116 LoadGameOneMove(cm);
\r
9118 /* load the remaining moves from the file */
\r
9119 while (LoadGameOneMove((ChessMove)0)) {
\r
9120 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9121 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9124 /* rewind to the start of the game */
\r
9125 currentMove = backwardMostMove;
\r
9127 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9129 if (oldGameMode == AnalyzeFile ||
\r
9130 oldGameMode == AnalyzeMode) {
\r
9131 AnalyzeFileEvent();
\r
9134 if (matchMode || appData.timeDelay == 0) {
\r
9136 gameMode = EditGame;
\r
9138 } else if (appData.timeDelay > 0) {
\r
9139 AutoPlayGameLoop();
\r
9142 if (appData.debugMode)
\r
9143 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9145 loadFlag = 0; /* [HGM] true game starts */
\r
9149 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9151 ReloadPosition(offset)
\r
9154 int positionNumber = lastLoadPositionNumber + offset;
\r
9155 if (lastLoadPositionFP == NULL) {
\r
9156 DisplayError(_("No position has been loaded yet"), 0);
\r
9159 if (positionNumber <= 0) {
\r
9160 DisplayError(_("Can't back up any further"), 0);
\r
9163 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9164 lastLoadPositionTitle);
\r
9167 /* Load the nth position from the given file */
\r
9169 LoadPositionFromFile(filename, n, title)
\r
9175 char buf[MSG_SIZ];
\r
9177 if (strcmp(filename, "-") == 0) {
\r
9178 return LoadPosition(stdin, n, "stdin");
\r
9180 f = fopen(filename, "rb");
\r
9182 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9183 DisplayError(buf, errno);
\r
9186 return LoadPosition(f, n, title);
\r
9191 /* Load the nth position from the given open file, and close it */
\r
9193 LoadPosition(f, positionNumber, title)
\r
9195 int positionNumber;
\r
9198 char *p, line[MSG_SIZ];
\r
9199 Board initial_position;
\r
9200 int i, j, fenMode, pn;
\r
9202 if (gameMode == Training )
\r
9203 SetTrainingModeOff();
\r
9205 if (gameMode != BeginningOfGame) {
\r
9206 Reset(FALSE, TRUE);
\r
9208 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9209 fclose(lastLoadPositionFP);
\r
9211 if (positionNumber == 0) positionNumber = 1;
\r
9212 lastLoadPositionFP = f;
\r
9213 lastLoadPositionNumber = positionNumber;
\r
9214 strcpy(lastLoadPositionTitle, title);
\r
9215 if (first.pr == NoProc) {
\r
9216 StartChessProgram(&first);
\r
9217 InitChessProgram(&first, FALSE);
\r
9219 pn = positionNumber;
\r
9220 if (positionNumber < 0) {
\r
9221 /* Negative position number means to seek to that byte offset */
\r
9222 if (fseek(f, -positionNumber, 0) == -1) {
\r
9223 DisplayError(_("Can't seek on position file"), 0);
\r
9228 if (fseek(f, 0, 0) == -1) {
\r
9229 if (f == lastLoadPositionFP ?
\r
9230 positionNumber == lastLoadPositionNumber + 1 :
\r
9231 positionNumber == 1) {
\r
9234 DisplayError(_("Can't seek on position file"), 0);
\r
9239 /* See if this file is FEN or old-style xboard */
\r
9240 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9241 DisplayError(_("Position not found in file"), 0);
\r
9245 switch (line[0]) {
\r
9246 case '#': case 'x':
\r
9250 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9251 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9252 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9253 case '7': case '8': case '9':
\r
9254 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9255 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9256 case 'C': case 'W': case 'c': case 'w':
\r
9261 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9262 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9266 if (fenMode || line[0] == '#') pn--;
\r
9268 /* skip positions before number pn */
\r
9269 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9270 Reset(TRUE, TRUE);
\r
9271 DisplayError(_("Position not found in file"), 0);
\r
9274 if (fenMode || line[0] == '#') pn--;
\r
9279 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9280 DisplayError(_("Bad FEN position in file"), 0);
\r
9284 (void) fgets(line, MSG_SIZ, f);
\r
9285 (void) fgets(line, MSG_SIZ, f);
\r
9287 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9288 (void) fgets(line, MSG_SIZ, f);
\r
9289 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9292 initial_position[i][j++] = CharToPiece(*p);
\r
9296 blackPlaysFirst = FALSE;
\r
9298 (void) fgets(line, MSG_SIZ, f);
\r
9299 if (strncmp(line, "black", strlen("black"))==0)
\r
9300 blackPlaysFirst = TRUE;
\r
9303 startedFromSetupPosition = TRUE;
\r
9305 SendToProgram("force\n", &first);
\r
9306 CopyBoard(boards[0], initial_position);
\r
9307 if (blackPlaysFirst) {
\r
9308 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9309 strcpy(moveList[0], "");
\r
9310 strcpy(parseList[0], "");
\r
9311 CopyBoard(boards[1], initial_position);
\r
9312 DisplayMessage("", _("Black to play"));
\r
9314 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9315 DisplayMessage("", _("White to play"));
\r
9317 /* [HGM] copy FEN attributes as well */
\r
9319 initialRulePlies = FENrulePlies;
\r
9320 epStatus[forwardMostMove] = FENepStatus;
\r
9321 for( i=0; i< nrCastlingRights; i++ )
\r
9322 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9324 SendBoard(&first, forwardMostMove);
\r
9325 if (appData.debugMode) {
\r
9327 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9328 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9329 fprintf(debugFP, "Load Position\n");
\r
9332 if (positionNumber > 1) {
\r
9333 sprintf(line, "%s %d", title, positionNumber);
\r
9334 DisplayTitle(line);
\r
9336 DisplayTitle(title);
\r
9338 gameMode = EditGame;
\r
9341 timeRemaining[0][1] = whiteTimeRemaining;
\r
9342 timeRemaining[1][1] = blackTimeRemaining;
\r
9343 DrawPosition(FALSE, boards[currentMove]);
\r
9350 CopyPlayerNameIntoFileName(dest, src)
\r
9351 char **dest, *src;
\r
9353 while (*src != NULLCHAR && *src != ',') {
\r
9354 if (*src == ' ') {
\r
9358 *(*dest)++ = *src++;
\r
9363 char *DefaultFileName(ext)
\r
9366 static char def[MSG_SIZ];
\r
9369 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9371 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9373 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9377 def[0] = NULLCHAR;
\r
9382 /* Save the current game to the given file */
\r
9384 SaveGameToFile(filename, append)
\r
9389 char buf[MSG_SIZ];
\r
9391 if (strcmp(filename, "-") == 0) {
\r
9392 return SaveGame(stdout, 0, NULL);
\r
9394 f = fopen(filename, append ? "a" : "w");
\r
9396 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9397 DisplayError(buf, errno);
\r
9400 return SaveGame(f, 0, NULL);
\r
9409 static char buf[MSG_SIZ];
\r
9412 p = strchr(str, ' ');
\r
9413 if (p == NULL) return str;
\r
9414 strncpy(buf, str, p - str);
\r
9415 buf[p - str] = NULLCHAR;
\r
9419 #define PGN_MAX_LINE 75
\r
9421 #define PGN_SIDE_WHITE 0
\r
9422 #define PGN_SIDE_BLACK 1
\r
9425 static int FindFirstMoveOutOfBook( int side )
\r
9429 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9430 int index = backwardMostMove;
\r
9431 int has_book_hit = 0;
\r
9433 if( (index % 2) != side ) {
\r
9437 while( index < forwardMostMove ) {
\r
9438 /* Check to see if engine is in book */
\r
9439 int depth = pvInfoList[index].depth;
\r
9440 int score = pvInfoList[index].score;
\r
9443 if( depth <= 2 ) {
\r
9446 else if( score == 0 && depth == 63 ) {
\r
9447 in_book = 1; /* Zappa */
\r
9449 else if( score == 2 && depth == 99 ) {
\r
9450 in_book = 1; /* Abrok */
\r
9453 has_book_hit += in_book;
\r
9469 void GetOutOfBookInfo( char * buf )
\r
9473 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9475 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9476 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9480 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9481 for( i=0; i<2; i++ ) {
\r
9485 if( i > 0 && oob[0] >= 0 ) {
\r
9486 strcat( buf, " " );
\r
9489 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9490 sprintf( buf+strlen(buf), "%s%.2f",
\r
9491 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9492 pvInfoList[idx].score / 100.0 );
\r
9498 /* Save game in PGN style and close the file */
\r
9503 int i, offset, linelen, newblock;
\r
9505 // char *movetext;
\r
9507 int movelen, numlen, blank;
\r
9508 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9510 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9512 tm = time((time_t *) NULL);
\r
9514 PrintPGNTags(f, &gameInfo);
\r
9516 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9517 char *fen = PositionToFEN(backwardMostMove, NULL);
\r
9518 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9519 fprintf(f, "\n{--------------\n");
\r
9520 PrintPosition(f, backwardMostMove);
\r
9521 fprintf(f, "--------------}\n");
\r
9525 /* [AS] Out of book annotation */
\r
9526 if( appData.saveOutOfBookInfo ) {
\r
9529 GetOutOfBookInfo( buf );
\r
9531 if( buf[0] != '\0' ) {
\r
9532 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9539 i = backwardMostMove;
\r
9543 while (i < forwardMostMove) {
\r
9544 /* Print comments preceding this move */
\r
9545 if (commentList[i] != NULL) {
\r
9546 if (linelen > 0) fprintf(f, "\n");
\r
9547 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9552 /* Format move number */
\r
9553 if ((i % 2) == 0) {
\r
9554 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9557 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9559 numtext[0] = NULLCHAR;
\r
9562 numlen = strlen(numtext);
\r
9565 /* Print move number */
\r
9566 blank = linelen > 0 && numlen > 0;
\r
9567 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9576 fprintf(f, numtext);
\r
9577 linelen += numlen;
\r
9580 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
\r
9581 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
\r
9582 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9583 int p = movelen - 1;
\r
9584 if(move_buffer[p] == ' ') p--;
\r
9585 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
\r
9586 while(p && move_buffer[--p] != '(');
\r
9587 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
\r
9592 blank = linelen > 0 && movelen > 0;
\r
9593 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9602 fprintf(f, move_buffer);
\r
9603 linelen += movelen;
\r
9605 /* [AS] Add PV info if present */
\r
9606 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9607 /* [HGM] add time */
\r
9608 char buf[MSG_SIZ]; int seconds = 0;
\r
9611 if(i >= backwardMostMove) {
\r
9612 if(WhiteOnMove(i))
\r
9613 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9614 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
\r
9616 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9617 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
\r
9619 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9621 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9624 if( seconds <= 0) buf[0] = 0; else
\r
9625 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9626 seconds = (seconds + 4)/10; // round to full seconds
\r
9627 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9628 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9631 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9632 pvInfoList[i].score >= 0 ? "+" : "",
\r
9633 pvInfoList[i].score / 100.0,
\r
9634 pvInfoList[i].depth,
\r
9637 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9639 /* Print score/depth */
\r
9640 blank = linelen > 0 && movelen > 0;
\r
9641 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9650 fprintf(f, move_buffer);
\r
9651 linelen += movelen;
\r
9657 /* Start a new line */
\r
9658 if (linelen > 0) fprintf(f, "\n");
\r
9660 /* Print comments after last move */
\r
9661 if (commentList[i] != NULL) {
\r
9662 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9665 /* Print result */
\r
9666 if (gameInfo.resultDetails != NULL &&
\r
9667 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9668 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9669 PGNResult(gameInfo.result));
\r
9671 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9678 /* Save game in old style and close the file */
\r
9680 SaveGameOldStyle(f)
\r
9686 tm = time((time_t *) NULL);
\r
9688 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9689 PrintOpponents(f);
\r
9691 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9692 fprintf(f, "\n[--------------\n");
\r
9693 PrintPosition(f, backwardMostMove);
\r
9694 fprintf(f, "--------------]\n");
\r
9699 i = backwardMostMove;
\r
9700 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9702 while (i < forwardMostMove) {
\r
9703 if (commentList[i] != NULL) {
\r
9704 fprintf(f, "[%s]\n", commentList[i]);
\r
9707 if ((i % 2) == 1) {
\r
9708 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9711 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9713 if (commentList[i] != NULL) {
\r
9717 if (i >= forwardMostMove) {
\r
9721 fprintf(f, "%s\n", parseList[i]);
\r
9726 if (commentList[i] != NULL) {
\r
9727 fprintf(f, "[%s]\n", commentList[i]);
\r
9730 /* This isn't really the old style, but it's close enough */
\r
9731 if (gameInfo.resultDetails != NULL &&
\r
9732 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9733 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9734 gameInfo.resultDetails);
\r
9736 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9743 /* Save the current game to open file f and close the file */
\r
9745 SaveGame(f, dummy, dummy2)
\r
9750 if (gameMode == EditPosition) EditPositionDone();
\r
9751 if (appData.oldSaveStyle)
\r
9752 return SaveGameOldStyle(f);
\r
9754 return SaveGamePGN(f);
\r
9757 /* Save the current position to the given file */
\r
9759 SavePositionToFile(filename)
\r
9763 char buf[MSG_SIZ];
\r
9765 if (strcmp(filename, "-") == 0) {
\r
9766 return SavePosition(stdout, 0, NULL);
\r
9768 f = fopen(filename, "a");
\r
9770 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9771 DisplayError(buf, errno);
\r
9774 SavePosition(f, 0, NULL);
\r
9780 /* Save the current position to the given open file and close the file */
\r
9782 SavePosition(f, dummy, dummy2)
\r
9790 if (appData.oldSaveStyle) {
\r
9791 tm = time((time_t *) NULL);
\r
9793 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9794 PrintOpponents(f);
\r
9795 fprintf(f, "[--------------\n");
\r
9796 PrintPosition(f, currentMove);
\r
9797 fprintf(f, "--------------]\n");
\r
9799 fen = PositionToFEN(currentMove, NULL);
\r
9800 fprintf(f, "%s\n", fen);
\r
9808 ReloadCmailMsgEvent(unregister)
\r
9812 static char *inFilename = NULL;
\r
9813 static char *outFilename;
\r
9815 struct stat inbuf, outbuf;
\r
9818 /* Any registered moves are unregistered if unregister is set, */
\r
9819 /* i.e. invoked by the signal handler */
\r
9821 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9822 cmailMoveRegistered[i] = FALSE;
\r
9823 if (cmailCommentList[i] != NULL) {
\r
9824 free(cmailCommentList[i]);
\r
9825 cmailCommentList[i] = NULL;
\r
9828 nCmailMovesRegistered = 0;
\r
9831 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9832 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9834 nCmailResults = 0;
\r
9836 if (inFilename == NULL) {
\r
9837 /* Because the filenames are static they only get malloced once */
\r
9838 /* and they never get freed */
\r
9839 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9840 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9842 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9843 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9846 status = stat(outFilename, &outbuf);
\r
9848 cmailMailedMove = FALSE;
\r
9850 status = stat(inFilename, &inbuf);
\r
9851 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9854 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9855 counts the games, notes how each one terminated, etc.
\r
9857 It would be nice to remove this kludge and instead gather all
\r
9858 the information while building the game list. (And to keep it
\r
9859 in the game list nodes instead of having a bunch of fixed-size
\r
9860 parallel arrays.) Note this will require getting each game's
\r
9861 termination from the PGN tags, as the game list builder does
\r
9862 not process the game moves. --mann
\r
9864 cmailMsgLoaded = TRUE;
\r
9865 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9867 /* Load first game in the file or popup game menu */
\r
9868 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9870 #endif /* !WIN32 */
\r
9878 char string[MSG_SIZ];
\r
9880 if ( cmailMailedMove
\r
9881 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9882 return TRUE; /* Allow free viewing */
\r
9885 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9886 /* with the move registered when the conditions for registering no */
\r
9888 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9889 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9890 nCmailMovesRegistered --;
\r
9892 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9894 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9895 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9899 if (cmailOldMove == -1) {
\r
9900 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9904 if (currentMove > cmailOldMove + 1) {
\r
9905 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9909 if (currentMove < cmailOldMove) {
\r
9910 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9914 if (forwardMostMove > currentMove) {
\r
9915 /* Silently truncate extra moves */
\r
9919 if ( (currentMove == cmailOldMove + 1)
\r
9920 || ( (currentMove == cmailOldMove)
\r
9921 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9922 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9923 if (gameInfo.result != GameUnfinished) {
\r
9924 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9927 if (commentList[currentMove] != NULL) {
\r
9928 cmailCommentList[lastLoadGameNumber - 1]
\r
9929 = StrSave(commentList[currentMove]);
\r
9931 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9933 if (appData.debugMode)
\r
9934 fprintf(debugFP, "Saving %s for game %d\n",
\r
9935 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9938 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9940 f = fopen(string, "w");
\r
9941 if (appData.oldSaveStyle) {
\r
9942 SaveGameOldStyle(f); /* also closes the file */
\r
9944 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9945 f = fopen(string, "w");
\r
9946 SavePosition(f, 0, NULL); /* also closes the file */
\r
9948 fprintf(f, "{--------------\n");
\r
9949 PrintPosition(f, currentMove);
\r
9950 fprintf(f, "--------------}\n\n");
\r
9952 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9955 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9956 nCmailMovesRegistered ++;
\r
9957 } else if (nCmailGames == 1) {
\r
9958 DisplayError(_("You have not made a move yet"), 0);
\r
9969 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9970 FILE *commandOutput;
\r
9971 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9972 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9978 if (! cmailMsgLoaded) {
\r
9979 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9983 if (nCmailGames == nCmailResults) {
\r
9984 DisplayError(_("No unfinished games"), 0);
\r
9988 #if CMAIL_PROHIBIT_REMAIL
\r
9989 if (cmailMailedMove) {
\r
9990 sprintf(msg, _("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);
\r
9991 DisplayError(msg, 0);
\r
9996 if (! (cmailMailedMove || RegisterMove())) return;
\r
9998 if ( cmailMailedMove
\r
9999 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10000 sprintf(string, partCommandString,
\r
10001 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10002 commandOutput = popen(string, "r");
\r
10004 if (commandOutput == NULL) {
\r
10005 DisplayError(_("Failed to invoke cmail"), 0);
\r
10007 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10008 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10010 if (nBuffers > 1) {
\r
10011 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10012 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10013 nBytes = MSG_SIZ - 1;
\r
10015 (void) memcpy(msg, buffer, nBytes);
\r
10017 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10019 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10020 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10023 for (i = 0; i < nCmailGames; i ++) {
\r
10024 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10025 archived = FALSE;
\r
10029 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10031 sprintf(buffer, "%s/%s.%s.archive",
\r
10033 appData.cmailGameName,
\r
10035 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10036 cmailMsgLoaded = FALSE;
\r
10040 DisplayInformation(msg);
\r
10041 pclose(commandOutput);
\r
10044 if ((*cmailMsg) != '\0') {
\r
10045 DisplayInformation(cmailMsg);
\r
10050 #endif /* !WIN32 */
\r
10059 int prependComma = 0;
\r
10061 char string[MSG_SIZ]; /* Space for game-list */
\r
10064 if (!cmailMsgLoaded) return "";
\r
10066 if (cmailMailedMove) {
\r
10067 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10069 /* Create a list of games left */
\r
10070 sprintf(string, "[");
\r
10071 for (i = 0; i < nCmailGames; i ++) {
\r
10072 if (! ( cmailMoveRegistered[i]
\r
10073 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10074 if (prependComma) {
\r
10075 sprintf(number, ",%d", i + 1);
\r
10077 sprintf(number, "%d", i + 1);
\r
10078 prependComma = 1;
\r
10081 strcat(string, number);
\r
10084 strcat(string, "]");
\r
10086 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10087 switch (nCmailGames) {
\r
10089 sprintf(cmailMsg,
\r
10090 _("Still need to make move for game\n"));
\r
10094 sprintf(cmailMsg,
\r
10095 _("Still need to make moves for both games\n"));
\r
10099 sprintf(cmailMsg,
\r
10100 _("Still need to make moves for all %d games\n"),
\r
10105 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10107 sprintf(cmailMsg,
\r
10108 _("Still need to make a move for game %s\n"),
\r
10113 if (nCmailResults == nCmailGames) {
\r
10114 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10116 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10121 sprintf(cmailMsg,
\r
10122 _("Still need to make moves for games %s\n"),
\r
10128 #endif /* WIN32 */
\r
10134 if (gameMode == Training)
\r
10135 SetTrainingModeOff();
\r
10137 Reset(TRUE, TRUE);
\r
10138 cmailMsgLoaded = FALSE;
\r
10139 if (appData.icsActive) {
\r
10140 SendToICS(ics_prefix);
\r
10141 SendToICS("refresh\n");
\r
10146 ExitEvent(status)
\r
10150 if (exiting > 2) {
\r
10151 /* Give up on clean exit */
\r
10154 if (exiting > 1) {
\r
10155 /* Keep trying for clean exit */
\r
10159 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10161 if (telnetISR != NULL) {
\r
10162 RemoveInputSource(telnetISR);
\r
10164 if (icsPR != NoProc) {
\r
10165 DestroyChildProcess(icsPR, TRUE);
\r
10168 /* Save game if resource set and not already saved by GameEnds() */
\r
10169 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10170 && forwardMostMove > 0) {
\r
10171 if (*appData.saveGameFile != NULLCHAR) {
\r
10172 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10173 } else if (appData.autoSaveGames) {
\r
10176 if (*appData.savePositionFile != NULLCHAR) {
\r
10177 SavePositionToFile(appData.savePositionFile);
\r
10180 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10182 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10183 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10185 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10186 /* make sure this other one finishes before killing it! */
\r
10187 if(endingGame) { int count = 0;
\r
10188 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10189 while(endingGame && count++ < 10) DoSleep(1);
\r
10190 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10193 /* Kill off chess programs */
\r
10194 if (first.pr != NoProc) {
\r
10195 ExitAnalyzeMode();
\r
10197 DoSleep( appData.delayBeforeQuit );
\r
10198 SendToProgram("quit\n", &first);
\r
10199 DoSleep( appData.delayAfterQuit );
\r
10200 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10202 if (second.pr != NoProc) {
\r
10203 DoSleep( appData.delayBeforeQuit );
\r
10204 SendToProgram("quit\n", &second);
\r
10205 DoSleep( appData.delayAfterQuit );
\r
10206 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10208 if (first.isr != NULL) {
\r
10209 RemoveInputSource(first.isr);
\r
10211 if (second.isr != NULL) {
\r
10212 RemoveInputSource(second.isr);
\r
10215 ShutDownFrontEnd();
\r
10222 if (appData.debugMode)
\r
10223 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10227 if (gameMode == MachinePlaysWhite ||
\r
10228 gameMode == MachinePlaysBlack) {
\r
10231 DisplayBothClocks();
\r
10233 if (gameMode == PlayFromGameFile) {
\r
10234 if (appData.timeDelay >= 0)
\r
10235 AutoPlayGameLoop();
\r
10236 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10237 Reset(FALSE, TRUE);
\r
10238 SendToICS(ics_prefix);
\r
10239 SendToICS("refresh\n");
\r
10240 } else if (currentMove < forwardMostMove) {
\r
10241 ForwardInner(forwardMostMove);
\r
10243 pauseExamInvalid = FALSE;
\r
10245 switch (gameMode) {
\r
10248 case IcsExamining:
\r
10249 pauseExamForwardMostMove = forwardMostMove;
\r
10250 pauseExamInvalid = FALSE;
\r
10251 /* fall through */
\r
10252 case IcsObserving:
\r
10253 case IcsPlayingWhite:
\r
10254 case IcsPlayingBlack:
\r
10258 case PlayFromGameFile:
\r
10259 (void) StopLoadGameTimer();
\r
10263 case BeginningOfGame:
\r
10264 if (appData.icsActive) return;
\r
10265 /* else fall through */
\r
10266 case MachinePlaysWhite:
\r
10267 case MachinePlaysBlack:
\r
10268 case TwoMachinesPlay:
\r
10269 if (forwardMostMove == 0)
\r
10270 return; /* don't pause if no one has moved */
\r
10271 if ((gameMode == MachinePlaysWhite &&
\r
10272 !WhiteOnMove(forwardMostMove)) ||
\r
10273 (gameMode == MachinePlaysBlack &&
\r
10274 WhiteOnMove(forwardMostMove))) {
\r
10285 EditCommentEvent()
\r
10287 char title[MSG_SIZ];
\r
10289 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10290 strcpy(title, _("Edit comment"));
\r
10292 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10293 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10294 parseList[currentMove - 1]);
\r
10297 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10304 char *tags = PGNTags(&gameInfo);
\r
10305 EditTagsPopUp(tags);
\r
10310 AnalyzeModeEvent()
\r
10312 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10315 if (gameMode != AnalyzeFile) {
\r
10316 if (!appData.icsEngineAnalyze) {
\r
10318 if (gameMode != EditGame) return;
\r
10320 ResurrectChessProgram();
\r
10321 SendToProgram("analyze\n", &first);
\r
10322 first.analyzing = TRUE;
\r
10323 /*first.maybeThinking = TRUE;*/
\r
10324 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10325 AnalysisPopUp(_("Analysis"),
\r
10326 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10328 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10333 StartAnalysisClock();
\r
10334 GetTimeMark(&lastNodeCountTime);
\r
10335 lastNodeCount = 0;
\r
10339 AnalyzeFileEvent()
\r
10341 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10344 if (gameMode != AnalyzeMode) {
\r
10346 if (gameMode != EditGame) return;
\r
10347 ResurrectChessProgram();
\r
10348 SendToProgram("analyze\n", &first);
\r
10349 first.analyzing = TRUE;
\r
10350 /*first.maybeThinking = TRUE;*/
\r
10351 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10352 AnalysisPopUp(_("Analysis"),
\r
10353 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10355 gameMode = AnalyzeFile;
\r
10360 StartAnalysisClock();
\r
10361 GetTimeMark(&lastNodeCountTime);
\r
10362 lastNodeCount = 0;
\r
10366 MachineWhiteEvent()
\r
10368 char buf[MSG_SIZ];
\r
10369 char *bookHit = NULL;
\r
10371 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10375 if (gameMode == PlayFromGameFile ||
\r
10376 gameMode == TwoMachinesPlay ||
\r
10377 gameMode == Training ||
\r
10378 gameMode == AnalyzeMode ||
\r
10379 gameMode == EndOfGame)
\r
10382 if (gameMode == EditPosition)
\r
10383 EditPositionDone();
\r
10385 if (!WhiteOnMove(currentMove)) {
\r
10386 DisplayError(_("It is not White's turn"), 0);
\r
10390 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10391 ExitAnalyzeMode();
\r
10393 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10394 gameMode == AnalyzeFile)
\r
10397 ResurrectChessProgram(); /* in case it isn't running */
\r
10398 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10399 gameMode = MachinePlaysWhite;
\r
10402 gameMode = MachinePlaysWhite;
\r
10406 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10407 DisplayTitle(buf);
\r
10408 if (first.sendName) {
\r
10409 sprintf(buf, "name %s\n", gameInfo.black);
\r
10410 SendToProgram(buf, &first);
\r
10412 if (first.sendTime) {
\r
10413 if (first.useColors) {
\r
10414 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10416 SendTimeRemaining(&first, TRUE);
\r
10418 if (first.useColors) {
\r
10419 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10421 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10422 SetMachineThinkingEnables();
\r
10423 first.maybeThinking = TRUE;
\r
10426 if (appData.autoFlipView && !flipView) {
\r
10427 flipView = !flipView;
\r
10428 DrawPosition(FALSE, NULL);
\r
10429 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10432 if(bookHit) { // [HGM] book: simulate book reply
\r
10433 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10435 programStats.nodes = programStats.depth = programStats.time =
\r
10436 programStats.score = programStats.got_only_move = 0;
\r
10437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10439 strcpy(bookMove, "move ");
\r
10440 strcat(bookMove, bookHit);
\r
10441 HandleMachineMove(bookMove, &first);
\r
10446 MachineBlackEvent()
\r
10448 char buf[MSG_SIZ];
\r
10449 char *bookHit = NULL;
\r
10451 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10455 if (gameMode == PlayFromGameFile ||
\r
10456 gameMode == TwoMachinesPlay ||
\r
10457 gameMode == Training ||
\r
10458 gameMode == AnalyzeMode ||
\r
10459 gameMode == EndOfGame)
\r
10462 if (gameMode == EditPosition)
\r
10463 EditPositionDone();
\r
10465 if (WhiteOnMove(currentMove)) {
\r
10466 DisplayError(_("It is not Black's turn"), 0);
\r
10470 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10471 ExitAnalyzeMode();
\r
10473 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10474 gameMode == AnalyzeFile)
\r
10477 ResurrectChessProgram(); /* in case it isn't running */
\r
10478 gameMode = MachinePlaysBlack;
\r
10482 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10483 DisplayTitle(buf);
\r
10484 if (first.sendName) {
\r
10485 sprintf(buf, "name %s\n", gameInfo.white);
\r
10486 SendToProgram(buf, &first);
\r
10488 if (first.sendTime) {
\r
10489 if (first.useColors) {
\r
10490 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10492 SendTimeRemaining(&first, FALSE);
\r
10494 if (first.useColors) {
\r
10495 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10498 SetMachineThinkingEnables();
\r
10499 first.maybeThinking = TRUE;
\r
10502 if (appData.autoFlipView && flipView) {
\r
10503 flipView = !flipView;
\r
10504 DrawPosition(FALSE, NULL);
\r
10505 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10507 if(bookHit) { // [HGM] book: simulate book reply
\r
10508 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10510 programStats.nodes = programStats.depth = programStats.time =
\r
10511 programStats.score = programStats.got_only_move = 0;
\r
10512 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10514 strcpy(bookMove, "move ");
\r
10515 strcat(bookMove, bookHit);
\r
10516 HandleMachineMove(bookMove, &first);
\r
10522 DisplayTwoMachinesTitle()
\r
10524 char buf[MSG_SIZ];
\r
10525 if (appData.matchGames > 0) {
\r
10526 if (first.twoMachinesColor[0] == 'w') {
\r
10527 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10528 gameInfo.white, gameInfo.black,
\r
10529 first.matchWins, second.matchWins,
\r
10530 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10532 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10533 gameInfo.white, gameInfo.black,
\r
10534 second.matchWins, first.matchWins,
\r
10535 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10538 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10540 DisplayTitle(buf);
\r
10544 TwoMachinesEvent P((void))
\r
10547 char buf[MSG_SIZ];
\r
10548 ChessProgramState *onmove;
\r
10549 char *bookHit = NULL;
\r
10551 if (appData.noChessProgram) return;
\r
10553 switch (gameMode) {
\r
10554 case TwoMachinesPlay:
\r
10556 case MachinePlaysWhite:
\r
10557 case MachinePlaysBlack:
\r
10558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10562 /* fall through */
\r
10563 case BeginningOfGame:
\r
10564 case PlayFromGameFile:
\r
10567 if (gameMode != EditGame) return;
\r
10569 case EditPosition:
\r
10570 EditPositionDone();
\r
10572 case AnalyzeMode:
\r
10573 case AnalyzeFile:
\r
10574 ExitAnalyzeMode();
\r
10581 forwardMostMove = currentMove;
\r
10582 ResurrectChessProgram(); /* in case first program isn't running */
\r
10584 if (second.pr == NULL) {
\r
10585 StartChessProgram(&second);
\r
10586 if (second.protocolVersion == 1) {
\r
10587 TwoMachinesEventIfReady();
\r
10589 /* kludge: allow timeout for initial "feature" command */
\r
10591 DisplayMessage("", _("Starting second chess program"));
\r
10592 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10596 DisplayMessage("", "");
\r
10597 InitChessProgram(&second, FALSE);
\r
10598 SendToProgram("force\n", &second);
\r
10599 if (startedFromSetupPosition) {
\r
10600 SendBoard(&second, backwardMostMove);
\r
10601 if (appData.debugMode) {
\r
10602 fprintf(debugFP, "Two Machines\n");
\r
10605 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10606 SendMoveToProgram(i, &second);
\r
10609 gameMode = TwoMachinesPlay;
\r
10613 DisplayTwoMachinesTitle();
\r
10614 firstMove = TRUE;
\r
10615 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10618 onmove = &second;
\r
10621 SendToProgram(first.computerString, &first);
\r
10622 if (first.sendName) {
\r
10623 sprintf(buf, "name %s\n", second.tidy);
\r
10624 SendToProgram(buf, &first);
\r
10626 SendToProgram(second.computerString, &second);
\r
10627 if (second.sendName) {
\r
10628 sprintf(buf, "name %s\n", first.tidy);
\r
10629 SendToProgram(buf, &second);
\r
10633 if (!first.sendTime || !second.sendTime) {
\r
10634 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10635 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10637 if (onmove->sendTime) {
\r
10638 if (onmove->useColors) {
\r
10639 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10641 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10643 if (onmove->useColors) {
\r
10644 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10646 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10647 // SendToProgram("go\n", onmove);
\r
10648 onmove->maybeThinking = TRUE;
\r
10649 SetMachineThinkingEnables();
\r
10653 if(bookHit) { // [HGM] book: simulate book reply
\r
10654 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10656 programStats.nodes = programStats.depth = programStats.time =
\r
10657 programStats.score = programStats.got_only_move = 0;
\r
10658 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10660 strcpy(bookMove, "move ");
\r
10661 strcat(bookMove, bookHit);
\r
10662 HandleMachineMove(bookMove, &first);
\r
10669 if (gameMode == Training) {
\r
10670 SetTrainingModeOff();
\r
10671 gameMode = PlayFromGameFile;
\r
10672 DisplayMessage("", _("Training mode off"));
\r
10674 gameMode = Training;
\r
10675 animateTraining = appData.animate;
\r
10677 /* make sure we are not already at the end of the game */
\r
10678 if (currentMove < forwardMostMove) {
\r
10679 SetTrainingModeOn();
\r
10680 DisplayMessage("", _("Training mode on"));
\r
10682 gameMode = PlayFromGameFile;
\r
10683 DisplayError(_("Already at end of game"), 0);
\r
10692 if (!appData.icsActive) return;
\r
10693 switch (gameMode) {
\r
10694 case IcsPlayingWhite:
\r
10695 case IcsPlayingBlack:
\r
10696 case IcsObserving:
\r
10698 case BeginningOfGame:
\r
10699 case IcsExamining:
\r
10705 case EditPosition:
\r
10706 EditPositionDone();
\r
10709 case AnalyzeMode:
\r
10710 case AnalyzeFile:
\r
10711 ExitAnalyzeMode();
\r
10719 gameMode = IcsIdle;
\r
10730 switch (gameMode) {
\r
10732 SetTrainingModeOff();
\r
10734 case MachinePlaysWhite:
\r
10735 case MachinePlaysBlack:
\r
10736 case BeginningOfGame:
\r
10737 SendToProgram("force\n", &first);
\r
10738 SetUserThinkingEnables();
\r
10740 case PlayFromGameFile:
\r
10741 (void) StopLoadGameTimer();
\r
10742 if (gameFileFP != NULL) {
\r
10743 gameFileFP = NULL;
\r
10746 case EditPosition:
\r
10747 EditPositionDone();
\r
10749 case AnalyzeMode:
\r
10750 case AnalyzeFile:
\r
10751 ExitAnalyzeMode();
\r
10752 SendToProgram("force\n", &first);
\r
10754 case TwoMachinesPlay:
\r
10755 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10756 ResurrectChessProgram();
\r
10757 SetUserThinkingEnables();
\r
10760 ResurrectChessProgram();
\r
10762 case IcsPlayingBlack:
\r
10763 case IcsPlayingWhite:
\r
10764 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10766 case IcsObserving:
\r
10767 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10769 case IcsExamining:
\r
10770 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10781 first.offeredDraw = second.offeredDraw = 0;
\r
10783 if (gameMode == PlayFromGameFile) {
\r
10784 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10785 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10786 DisplayTitle("");
\r
10789 if (gameMode == MachinePlaysWhite ||
\r
10790 gameMode == MachinePlaysBlack ||
\r
10791 gameMode == TwoMachinesPlay ||
\r
10792 gameMode == EndOfGame) {
\r
10793 i = forwardMostMove;
\r
10794 while (i > currentMove) {
\r
10795 SendToProgram("undo\n", &first);
\r
10798 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10799 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10800 DisplayBothClocks();
\r
10801 if (whiteFlag || blackFlag) {
\r
10802 whiteFlag = blackFlag = 0;
\r
10804 DisplayTitle("");
\r
10807 gameMode = EditGame;
\r
10814 EditPositionEvent()
\r
10816 if (gameMode == EditPosition) {
\r
10822 if (gameMode != EditGame) return;
\r
10824 gameMode = EditPosition;
\r
10827 if (currentMove > 0)
\r
10828 CopyBoard(boards[0], boards[currentMove]);
\r
10830 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10832 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10833 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10838 ExitAnalyzeMode()
\r
10840 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10841 if (appData.icsEngineAnalyze) {
\r
10842 appData.icsEngineAnalyze = FALSE;
\r
10844 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10846 if (first.analysisSupport && first.analyzing) {
\r
10847 SendToProgram("exit\n", &first);
\r
10848 first.analyzing = FALSE;
\r
10850 AnalysisPopDown();
\r
10851 thinkOutput[0] = NULLCHAR;
\r
10855 EditPositionDone()
\r
10857 startedFromSetupPosition = TRUE;
\r
10858 InitChessProgram(&first, FALSE);
\r
10859 SendToProgram("force\n", &first);
\r
10860 if (blackPlaysFirst) {
\r
10861 strcpy(moveList[0], "");
\r
10862 strcpy(parseList[0], "");
\r
10863 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10864 CopyBoard(boards[1], boards[0]);
\r
10865 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10867 epStatus[1] = epStatus[0];
\r
10868 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10871 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10873 SendBoard(&first, forwardMostMove);
\r
10874 if (appData.debugMode) {
\r
10875 fprintf(debugFP, "EditPosDone\n");
\r
10877 DisplayTitle("");
\r
10878 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10879 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10880 gameMode = EditGame;
\r
10882 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10883 ClearHighlights(); /* [AS] */
\r
10886 /* Pause for `ms' milliseconds */
\r
10887 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10894 GetTimeMark(&m1);
\r
10896 GetTimeMark(&m2);
\r
10897 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10900 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10902 SendMultiLineToICS(buf)
\r
10905 char temp[MSG_SIZ+1], *p;
\r
10908 len = strlen(buf);
\r
10909 if (len > MSG_SIZ)
\r
10912 strncpy(temp, buf, len);
\r
10917 if (*p == '\n' || *p == '\r')
\r
10922 strcat(temp, "\n");
\r
10924 SendToPlayer(temp, strlen(temp));
\r
10928 SetWhiteToPlayEvent()
\r
10930 if (gameMode == EditPosition) {
\r
10931 blackPlaysFirst = FALSE;
\r
10932 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10933 } else if (gameMode == IcsExamining) {
\r
10934 SendToICS(ics_prefix);
\r
10935 SendToICS("tomove white\n");
\r
10940 SetBlackToPlayEvent()
\r
10942 if (gameMode == EditPosition) {
\r
10943 blackPlaysFirst = TRUE;
\r
10944 currentMove = 1; /* kludge */
\r
10945 DisplayBothClocks();
\r
10947 } else if (gameMode == IcsExamining) {
\r
10948 SendToICS(ics_prefix);
\r
10949 SendToICS("tomove black\n");
\r
10954 EditPositionMenuEvent(selection, x, y)
\r
10955 ChessSquare selection;
\r
10958 char buf[MSG_SIZ];
\r
10959 ChessSquare piece = boards[0][y][x];
\r
10961 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10963 switch (selection) {
\r
10965 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10966 SendToICS(ics_prefix);
\r
10967 SendToICS("bsetup clear\n");
\r
10968 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10969 SendToICS(ics_prefix);
\r
10970 SendToICS("clearboard\n");
\r
10972 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10973 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10974 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10975 if (gameMode == IcsExamining) {
\r
10976 if (boards[currentMove][y][x] != EmptySquare) {
\r
10977 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10978 AAA + x, ONE + y);
\r
10982 boards[0][y][x] = p;
\r
10987 if (gameMode == EditPosition) {
\r
10988 DrawPosition(FALSE, boards[0]);
\r
10993 SetWhiteToPlayEvent();
\r
10997 SetBlackToPlayEvent();
\r
11000 case EmptySquare:
\r
11001 if (gameMode == IcsExamining) {
\r
11002 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11005 boards[0][y][x] = EmptySquare;
\r
11006 DrawPosition(FALSE, boards[0]);
\r
11010 case PromotePiece:
\r
11011 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11012 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11013 selection = (ChessSquare) (PROMOTED piece);
\r
11014 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11015 else selection = (ChessSquare)((int)piece - 1);
\r
11016 goto defaultlabel;
\r
11018 case DemotePiece:
\r
11019 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11020 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11021 selection = (ChessSquare) (DEMOTED piece);
\r
11022 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11023 else selection = (ChessSquare)((int)piece + 1);
\r
11024 goto defaultlabel;
\r
11028 if(gameInfo.variant == VariantShatranj ||
\r
11029 gameInfo.variant == VariantXiangqi ||
\r
11030 gameInfo.variant == VariantCourier )
\r
11031 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11032 goto defaultlabel;
\r
11036 if(gameInfo.variant == VariantXiangqi)
\r
11037 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11038 if(gameInfo.variant == VariantKnightmate)
\r
11039 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11042 if (gameMode == IcsExamining) {
\r
11043 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11044 PieceToChar(selection), AAA + x, ONE + y);
\r
11047 boards[0][y][x] = selection;
\r
11048 DrawPosition(FALSE, boards[0]);
\r
11056 DropMenuEvent(selection, x, y)
\r
11057 ChessSquare selection;
\r
11060 ChessMove moveType;
\r
11062 switch (gameMode) {
\r
11063 case IcsPlayingWhite:
\r
11064 case MachinePlaysBlack:
\r
11065 if (!WhiteOnMove(currentMove)) {
\r
11066 DisplayMoveError(_("It is Black's turn"));
\r
11069 moveType = WhiteDrop;
\r
11071 case IcsPlayingBlack:
\r
11072 case MachinePlaysWhite:
\r
11073 if (WhiteOnMove(currentMove)) {
\r
11074 DisplayMoveError(_("It is White's turn"));
\r
11077 moveType = BlackDrop;
\r
11080 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11086 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11087 selection = (ChessSquare) ((int) selection
\r
11088 + (int) BlackPawn - (int) WhitePawn);
\r
11090 if (boards[currentMove][y][x] != EmptySquare) {
\r
11091 DisplayMoveError(_("That square is occupied"));
\r
11095 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11101 /* Accept a pending offer of any kind from opponent */
\r
11103 if (appData.icsActive) {
\r
11104 SendToICS(ics_prefix);
\r
11105 SendToICS("accept\n");
\r
11106 } else if (cmailMsgLoaded) {
\r
11107 if (currentMove == cmailOldMove &&
\r
11108 commentList[cmailOldMove] != NULL &&
\r
11109 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11110 "Black offers a draw" : "White offers a draw")) {
\r
11112 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11113 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11115 DisplayError(_("There is no pending offer on this move"), 0);
\r
11116 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11119 /* Not used for offers from chess program */
\r
11126 /* Decline a pending offer of any kind from opponent */
\r
11128 if (appData.icsActive) {
\r
11129 SendToICS(ics_prefix);
\r
11130 SendToICS("decline\n");
\r
11131 } else if (cmailMsgLoaded) {
\r
11132 if (currentMove == cmailOldMove &&
\r
11133 commentList[cmailOldMove] != NULL &&
\r
11134 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11135 "Black offers a draw" : "White offers a draw")) {
\r
11137 AppendComment(cmailOldMove, "Draw declined");
\r
11138 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11139 #endif /*NOTDEF*/
\r
11141 DisplayError(_("There is no pending offer on this move"), 0);
\r
11144 /* Not used for offers from chess program */
\r
11151 /* Issue ICS rematch command */
\r
11152 if (appData.icsActive) {
\r
11153 SendToICS(ics_prefix);
\r
11154 SendToICS("rematch\n");
\r
11161 /* Call your opponent's flag (claim a win on time) */
\r
11162 if (appData.icsActive) {
\r
11163 SendToICS(ics_prefix);
\r
11164 SendToICS("flag\n");
\r
11166 switch (gameMode) {
\r
11169 case MachinePlaysWhite:
\r
11172 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11175 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11177 DisplayError(_("Your opponent is not out of time"), 0);
\r
11180 case MachinePlaysBlack:
\r
11183 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11186 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11188 DisplayError(_("Your opponent is not out of time"), 0);
\r
11198 /* Offer draw or accept pending draw offer from opponent */
\r
11200 if (appData.icsActive) {
\r
11201 /* Note: tournament rules require draw offers to be
\r
11202 made after you make your move but before you punch
\r
11203 your clock. Currently ICS doesn't let you do that;
\r
11204 instead, you immediately punch your clock after making
\r
11205 a move, but you can offer a draw at any time. */
\r
11207 SendToICS(ics_prefix);
\r
11208 SendToICS("draw\n");
\r
11209 } else if (cmailMsgLoaded) {
\r
11210 if (currentMove == cmailOldMove &&
\r
11211 commentList[cmailOldMove] != NULL &&
\r
11212 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11213 "Black offers a draw" : "White offers a draw")) {
\r
11214 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11215 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11216 } else if (currentMove == cmailOldMove + 1) {
\r
11217 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11218 "White offers a draw" : "Black offers a draw";
\r
11219 AppendComment(currentMove, offer);
\r
11220 DisplayComment(currentMove - 1, offer);
\r
11221 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11223 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11224 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11226 } else if (first.offeredDraw) {
\r
11227 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11229 if (first.sendDrawOffers) {
\r
11230 SendToProgram("draw\n", &first);
\r
11231 userOfferedDraw = TRUE;
\r
11239 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11241 if (appData.icsActive) {
\r
11242 SendToICS(ics_prefix);
\r
11243 SendToICS("adjourn\n");
\r
11245 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11253 /* Offer Abort or accept pending Abort offer from opponent */
\r
11255 if (appData.icsActive) {
\r
11256 SendToICS(ics_prefix);
\r
11257 SendToICS("abort\n");
\r
11259 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11266 /* Resign. You can do this even if it's not your turn. */
\r
11268 if (appData.icsActive) {
\r
11269 SendToICS(ics_prefix);
\r
11270 SendToICS("resign\n");
\r
11272 switch (gameMode) {
\r
11273 case MachinePlaysWhite:
\r
11274 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11276 case MachinePlaysBlack:
\r
11277 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11280 if (cmailMsgLoaded) {
\r
11282 if (WhiteOnMove(cmailOldMove)) {
\r
11283 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11285 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11287 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11298 StopObservingEvent()
\r
11300 /* Stop observing current games */
\r
11301 SendToICS(ics_prefix);
\r
11302 SendToICS("unobserve\n");
\r
11306 StopExaminingEvent()
\r
11308 /* Stop observing current game */
\r
11309 SendToICS(ics_prefix);
\r
11310 SendToICS("unexamine\n");
\r
11314 ForwardInner(target)
\r
11319 if (appData.debugMode)
\r
11320 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11321 target, currentMove, forwardMostMove);
\r
11323 if (gameMode == EditPosition)
\r
11326 if (gameMode == PlayFromGameFile && !pausing)
\r
11329 if (gameMode == IcsExamining && pausing)
\r
11330 limit = pauseExamForwardMostMove;
\r
11332 limit = forwardMostMove;
\r
11334 if (target > limit) target = limit;
\r
11336 if (target > 0 && moveList[target - 1][0]) {
\r
11337 int fromX, fromY, toX, toY;
\r
11338 toX = moveList[target - 1][2] - AAA;
\r
11339 toY = moveList[target - 1][3] - ONE;
\r
11340 if (moveList[target - 1][1] == '@') {
\r
11341 if (appData.highlightLastMove) {
\r
11342 SetHighlights(-1, -1, toX, toY);
\r
11345 fromX = moveList[target - 1][0] - AAA;
\r
11346 fromY = moveList[target - 1][1] - ONE;
\r
11347 if (target == currentMove + 1) {
\r
11348 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11350 if (appData.highlightLastMove) {
\r
11351 SetHighlights(fromX, fromY, toX, toY);
\r
11355 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11356 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11357 gameMode == AnalyzeFile) {
\r
11358 while (currentMove < target) {
\r
11359 SendMoveToProgram(currentMove++, &first);
\r
11362 currentMove = target;
\r
11365 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11366 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11367 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11369 DisplayBothClocks();
\r
11370 DisplayMove(currentMove - 1);
\r
11371 DrawPosition(FALSE, boards[currentMove]);
\r
11372 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11373 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11374 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11382 if (gameMode == IcsExamining && !pausing) {
\r
11383 SendToICS(ics_prefix);
\r
11384 SendToICS("forward\n");
\r
11386 ForwardInner(currentMove + 1);
\r
11393 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11394 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11395 * the remaining moves to the engine. Otherwise we get analysis output
\r
11396 * after each move.
\r
11398 if (first.analysisSupport) {
\r
11399 SendToProgram("exit\nforce\n", &first);
\r
11400 first.analyzing = FALSE;
\r
11404 if (gameMode == IcsExamining && !pausing) {
\r
11405 SendToICS(ics_prefix);
\r
11406 SendToICS("forward 999999\n");
\r
11408 ForwardInner(forwardMostMove);
\r
11411 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11412 /* we have fed all the moves, so reactivate analysis mode */
\r
11413 SendToProgram("analyze\n", &first);
\r
11414 first.analyzing = TRUE;
\r
11415 /*first.maybeThinking = TRUE;*/
\r
11416 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11421 BackwardInner(target)
\r
11424 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11426 if (appData.debugMode)
\r
11427 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11428 target, currentMove, forwardMostMove);
\r
11430 if (gameMode == EditPosition) return;
\r
11431 if (currentMove <= backwardMostMove) {
\r
11432 ClearHighlights();
\r
11433 DrawPosition(full_redraw, boards[currentMove]);
\r
11436 if (gameMode == PlayFromGameFile && !pausing)
\r
11439 if (moveList[target][0]) {
\r
11440 int fromX, fromY, toX, toY;
\r
11441 toX = moveList[target][2] - AAA;
\r
11442 toY = moveList[target][3] - ONE;
\r
11443 if (moveList[target][1] == '@') {
\r
11444 if (appData.highlightLastMove) {
\r
11445 SetHighlights(-1, -1, toX, toY);
\r
11448 fromX = moveList[target][0] - AAA;
\r
11449 fromY = moveList[target][1] - ONE;
\r
11450 if (target == currentMove - 1) {
\r
11451 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11453 if (appData.highlightLastMove) {
\r
11454 SetHighlights(fromX, fromY, toX, toY);
\r
11458 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11459 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11460 while (currentMove > target) {
\r
11461 SendToProgram("undo\n", &first);
\r
11465 currentMove = target;
\r
11468 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11469 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11470 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11472 DisplayBothClocks();
\r
11473 DisplayMove(currentMove - 1);
\r
11474 DrawPosition(full_redraw, boards[currentMove]);
\r
11475 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11476 // [HGM] PV info: routine tests if comment empty
\r
11477 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11483 if (gameMode == IcsExamining && !pausing) {
\r
11484 SendToICS(ics_prefix);
\r
11485 SendToICS("backward\n");
\r
11487 BackwardInner(currentMove - 1);
\r
11494 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11495 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11496 * all the moves. Otherwise we get analysis output after each undo.
\r
11498 if (first.analysisSupport) {
\r
11499 SendToProgram("exit\nforce\n", &first);
\r
11500 first.analyzing = FALSE;
\r
11504 if (gameMode == IcsExamining && !pausing) {
\r
11505 SendToICS(ics_prefix);
\r
11506 SendToICS("backward 999999\n");
\r
11508 BackwardInner(backwardMostMove);
\r
11511 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11512 /* we have fed all the moves, so reactivate analysis mode */
\r
11513 SendToProgram("analyze\n", &first);
\r
11514 first.analyzing = TRUE;
\r
11515 /*first.maybeThinking = TRUE;*/
\r
11516 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11521 ToNrEvent(int to)
\r
11523 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11524 if (to >= forwardMostMove) to = forwardMostMove;
\r
11525 if (to <= backwardMostMove) to = backwardMostMove;
\r
11526 if (to < currentMove) {
\r
11527 BackwardInner(to);
\r
11529 ForwardInner(to);
\r
11536 if (gameMode != IcsExamining) {
\r
11537 DisplayError(_("You are not examining a game"), 0);
\r
11541 DisplayError(_("You can't revert while pausing"), 0);
\r
11544 SendToICS(ics_prefix);
\r
11545 SendToICS("revert\n");
\r
11549 RetractMoveEvent()
\r
11551 switch (gameMode) {
\r
11552 case MachinePlaysWhite:
\r
11553 case MachinePlaysBlack:
\r
11554 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11555 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11558 if (forwardMostMove < 2) return;
\r
11559 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11560 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11561 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11562 DisplayBothClocks();
\r
11563 DisplayMove(currentMove - 1);
\r
11564 ClearHighlights();/*!! could figure this out*/
\r
11565 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11566 SendToProgram("remove\n", &first);
\r
11567 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11570 case BeginningOfGame:
\r
11574 case IcsPlayingWhite:
\r
11575 case IcsPlayingBlack:
\r
11576 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11577 SendToICS(ics_prefix);
\r
11578 SendToICS("takeback 2\n");
\r
11580 SendToICS(ics_prefix);
\r
11581 SendToICS("takeback 1\n");
\r
11590 ChessProgramState *cps;
\r
11592 switch (gameMode) {
\r
11593 case MachinePlaysWhite:
\r
11594 if (!WhiteOnMove(forwardMostMove)) {
\r
11595 DisplayError(_("It is your turn"), 0);
\r
11600 case MachinePlaysBlack:
\r
11601 if (WhiteOnMove(forwardMostMove)) {
\r
11602 DisplayError(_("It is your turn"), 0);
\r
11607 case TwoMachinesPlay:
\r
11608 if (WhiteOnMove(forwardMostMove) ==
\r
11609 (first.twoMachinesColor[0] == 'w')) {
\r
11615 case BeginningOfGame:
\r
11619 SendToProgram("?\n", cps);
\r
11623 TruncateGameEvent()
\r
11626 if (gameMode != EditGame) return;
\r
11633 if (forwardMostMove > currentMove) {
\r
11634 if (gameInfo.resultDetails != NULL) {
\r
11635 free(gameInfo.resultDetails);
\r
11636 gameInfo.resultDetails = NULL;
\r
11637 gameInfo.result = GameUnfinished;
\r
11639 forwardMostMove = currentMove;
\r
11640 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11648 if (appData.noChessProgram) return;
\r
11649 switch (gameMode) {
\r
11650 case MachinePlaysWhite:
\r
11651 if (WhiteOnMove(forwardMostMove)) {
\r
11652 DisplayError(_("Wait until your turn"), 0);
\r
11656 case BeginningOfGame:
\r
11657 case MachinePlaysBlack:
\r
11658 if (!WhiteOnMove(forwardMostMove)) {
\r
11659 DisplayError(_("Wait until your turn"), 0);
\r
11664 DisplayError(_("No hint available"), 0);
\r
11667 SendToProgram("hint\n", &first);
\r
11668 hintRequested = TRUE;
\r
11674 if (appData.noChessProgram) return;
\r
11675 switch (gameMode) {
\r
11676 case MachinePlaysWhite:
\r
11677 if (WhiteOnMove(forwardMostMove)) {
\r
11678 DisplayError(_("Wait until your turn"), 0);
\r
11682 case BeginningOfGame:
\r
11683 case MachinePlaysBlack:
\r
11684 if (!WhiteOnMove(forwardMostMove)) {
\r
11685 DisplayError(_("Wait until your turn"), 0);
\r
11689 case EditPosition:
\r
11690 EditPositionDone();
\r
11692 case TwoMachinesPlay:
\r
11697 SendToProgram("bk\n", &first);
\r
11698 bookOutput[0] = NULLCHAR;
\r
11699 bookRequested = TRUE;
\r
11705 char *tags = PGNTags(&gameInfo);
\r
11706 TagsPopUp(tags, CmailMsg());
\r
11710 /* end button procedures */
\r
11713 PrintPosition(fp, move)
\r
11719 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11720 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11721 char c = PieceToChar(boards[move][i][j]);
\r
11722 fputc(c == 'x' ? '.' : c, fp);
\r
11723 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11726 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11727 fprintf(fp, "white to play\n");
\r
11729 fprintf(fp, "black to play\n");
\r
11733 PrintOpponents(fp)
\r
11736 if (gameInfo.white != NULL) {
\r
11737 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11739 fprintf(fp, "\n");
\r
11743 /* Find last component of program's own name, using some heuristics */
\r
11745 TidyProgramName(prog, host, buf)
\r
11746 char *prog, *host, buf[MSG_SIZ];
\r
11749 int local = (strcmp(host, "localhost") == 0);
\r
11750 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11752 while (*p == ' ') p++;
\r
11755 if (*prog == '"' || *prog == '\'') {
\r
11756 q = strchr(prog + 1, *prog);
\r
11758 q = strchr(prog, ' ');
\r
11760 if (q == NULL) q = prog + strlen(prog);
\r
11762 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11764 if(p == prog && *p == '"') p++;
\r
11765 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11766 memcpy(buf, p, q - p);
\r
11767 buf[q - p] = NULLCHAR;
\r
11769 strcat(buf, "@");
\r
11770 strcat(buf, host);
\r
11775 TimeControlTagValue()
\r
11777 char buf[MSG_SIZ];
\r
11778 if (!appData.clockMode) {
\r
11779 strcpy(buf, "-");
\r
11780 } else if (movesPerSession > 0) {
\r
11781 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11782 } else if (timeIncrement == 0) {
\r
11783 sprintf(buf, "%ld", timeControl/1000);
\r
11785 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11787 return StrSave(buf);
\r
11793 /* This routine is used only for certain modes */
\r
11794 VariantClass v = gameInfo.variant;
\r
11795 ClearGameInfo(&gameInfo);
\r
11796 gameInfo.variant = v;
\r
11798 switch (gameMode) {
\r
11799 case MachinePlaysWhite:
\r
11800 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11801 gameInfo.site = StrSave(HostName());
\r
11802 gameInfo.date = PGNDate();
\r
11803 gameInfo.round = StrSave("-");
\r
11804 gameInfo.white = StrSave(first.tidy);
\r
11805 gameInfo.black = StrSave(UserName());
\r
11806 gameInfo.timeControl = TimeControlTagValue();
\r
11809 case MachinePlaysBlack:
\r
11810 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11811 gameInfo.site = StrSave(HostName());
\r
11812 gameInfo.date = PGNDate();
\r
11813 gameInfo.round = StrSave("-");
\r
11814 gameInfo.white = StrSave(UserName());
\r
11815 gameInfo.black = StrSave(first.tidy);
\r
11816 gameInfo.timeControl = TimeControlTagValue();
\r
11819 case TwoMachinesPlay:
\r
11820 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11821 gameInfo.site = StrSave(HostName());
\r
11822 gameInfo.date = PGNDate();
\r
11823 if (matchGame > 0) {
\r
11824 char buf[MSG_SIZ];
\r
11825 sprintf(buf, "%d", matchGame);
\r
11826 gameInfo.round = StrSave(buf);
\r
11828 gameInfo.round = StrSave("-");
\r
11830 if (first.twoMachinesColor[0] == 'w') {
\r
11831 gameInfo.white = StrSave(first.tidy);
\r
11832 gameInfo.black = StrSave(second.tidy);
\r
11834 gameInfo.white = StrSave(second.tidy);
\r
11835 gameInfo.black = StrSave(first.tidy);
\r
11837 gameInfo.timeControl = TimeControlTagValue();
\r
11841 gameInfo.event = StrSave("Edited game");
\r
11842 gameInfo.site = StrSave(HostName());
\r
11843 gameInfo.date = PGNDate();
\r
11844 gameInfo.round = StrSave("-");
\r
11845 gameInfo.white = StrSave("-");
\r
11846 gameInfo.black = StrSave("-");
\r
11849 case EditPosition:
\r
11850 gameInfo.event = StrSave("Edited position");
\r
11851 gameInfo.site = StrSave(HostName());
\r
11852 gameInfo.date = PGNDate();
\r
11853 gameInfo.round = StrSave("-");
\r
11854 gameInfo.white = StrSave("-");
\r
11855 gameInfo.black = StrSave("-");
\r
11858 case IcsPlayingWhite:
\r
11859 case IcsPlayingBlack:
\r
11860 case IcsObserving:
\r
11861 case IcsExamining:
\r
11864 case PlayFromGameFile:
\r
11865 gameInfo.event = StrSave("Game from non-PGN file");
\r
11866 gameInfo.site = StrSave(HostName());
\r
11867 gameInfo.date = PGNDate();
\r
11868 gameInfo.round = StrSave("-");
\r
11869 gameInfo.white = StrSave("?");
\r
11870 gameInfo.black = StrSave("?");
\r
11879 ReplaceComment(index, text)
\r
11885 while (*text == '\n') text++;
\r
11886 len = strlen(text);
\r
11887 while (len > 0 && text[len - 1] == '\n') len--;
\r
11889 if (commentList[index] != NULL)
\r
11890 free(commentList[index]);
\r
11893 commentList[index] = NULL;
\r
11896 commentList[index] = (char *) malloc(len + 2);
\r
11897 strncpy(commentList[index], text, len);
\r
11898 commentList[index][len] = '\n';
\r
11899 commentList[index][len + 1] = NULLCHAR;
\r
11912 if (ch == '\r') continue;
\r
11914 } while (ch != '\0');
\r
11918 AppendComment(index, text)
\r
11925 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11928 while (*text == '\n') text++;
\r
11929 len = strlen(text);
\r
11930 while (len > 0 && text[len - 1] == '\n') len--;
\r
11932 if (len == 0) return;
\r
11934 if (commentList[index] != NULL) {
\r
11935 old = commentList[index];
\r
11936 oldlen = strlen(old);
\r
11937 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11938 strcpy(commentList[index], old);
\r
11940 strncpy(&commentList[index][oldlen], text, len);
\r
11941 commentList[index][oldlen + len] = '\n';
\r
11942 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11944 commentList[index] = (char *) malloc(len + 2);
\r
11945 strncpy(commentList[index], text, len);
\r
11946 commentList[index][len] = '\n';
\r
11947 commentList[index][len + 1] = NULLCHAR;
\r
11951 static char * FindStr( char * text, char * sub_text )
\r
11953 char * result = strstr( text, sub_text );
\r
11955 if( result != NULL ) {
\r
11956 result += strlen( sub_text );
\r
11962 /* [AS] Try to extract PV info from PGN comment */
\r
11963 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11964 char *GetInfoFromComment( int index, char * text )
\r
11966 char * sep = text;
\r
11968 if( text != NULL && index > 0 ) {
\r
11971 int time = -1, sec = 0, deci;
\r
11972 char * s_eval = FindStr( text, "[%eval " );
\r
11973 char * s_emt = FindStr( text, "[%emt " );
\r
11975 if( s_eval != NULL || s_emt != NULL ) {
\r
11979 if( s_eval != NULL ) {
\r
11980 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11984 if( delim != ']' ) {
\r
11989 if( s_emt != NULL ) {
\r
11993 /* We expect something like: [+|-]nnn.nn/dd */
\r
11994 int score_lo = 0;
\r
11996 sep = strchr( text, '/' );
\r
11997 if( sep == NULL || sep < (text+4) ) {
\r
12001 time = -1; sec = -1; deci = -1;
\r
12002 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12003 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12004 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12005 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12009 if( score_lo < 0 || score_lo >= 100 ) {
\r
12013 if(sec >= 0) time = 600*time + 10*sec; else
\r
12014 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12016 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12018 /* [HGM] PV time: now locate end of PV info */
\r
12019 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12021 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12023 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12025 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12026 while(*sep == ' ') sep++;
\r
12029 if( depth <= 0 ) {
\r
12037 pvInfoList[index-1].depth = depth;
\r
12038 pvInfoList[index-1].score = score;
\r
12039 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12045 SendToProgram(message, cps)
\r
12047 ChessProgramState *cps;
\r
12049 int count, outCount, error;
\r
12050 char buf[MSG_SIZ];
\r
12052 if (cps->pr == NULL) return;
\r
12055 if (appData.debugMode) {
\r
12057 GetTimeMark(&now);
\r
12058 fprintf(debugFP, "%ld >%-6s: %s",
\r
12059 SubtractTimeMarks(&now, &programStartTime),
\r
12060 cps->which, message);
\r
12063 count = strlen(message);
\r
12064 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12065 if (outCount < count && !exiting
\r
12066 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12067 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12068 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12069 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12070 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12071 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12073 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12075 gameInfo.resultDetails = buf;
\r
12077 DisplayFatalError(buf, error, 1);
\r
12082 ReceiveFromProgram(isr, closure, message, count, error)
\r
12083 InputSourceRef isr;
\r
12084 VOIDSTAR closure;
\r
12090 char buf[MSG_SIZ];
\r
12091 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12093 if (isr != cps->isr) return; /* Killed intentionally */
\r
12094 if (count <= 0) {
\r
12095 if (count == 0) {
\r
12097 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12098 cps->which, cps->program);
\r
12099 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12100 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12101 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12102 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12104 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12106 gameInfo.resultDetails = buf;
\r
12108 RemoveInputSource(cps->isr);
\r
12109 DisplayFatalError(buf, 0, 1);
\r
12112 _("Error reading from %s chess program (%s)"),
\r
12113 cps->which, cps->program);
\r
12114 RemoveInputSource(cps->isr);
\r
12116 /* [AS] Program is misbehaving badly... kill it */
\r
12117 if( count == -2 ) {
\r
12118 DestroyChildProcess( cps->pr, 9 );
\r
12119 cps->pr = NoProc;
\r
12122 DisplayFatalError(buf, error, 1);
\r
12127 if ((end_str = strchr(message, '\r')) != NULL)
\r
12128 *end_str = NULLCHAR;
\r
12129 if ((end_str = strchr(message, '\n')) != NULL)
\r
12130 *end_str = NULLCHAR;
\r
12132 if (appData.debugMode) {
\r
12133 TimeMark now; int print = 1;
\r
12134 char *quote = ""; char c; int i;
\r
12136 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12137 char start = message[0];
\r
12138 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12139 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12140 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12141 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12142 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12143 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12144 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12145 { quote = "# "; print = (appData.engineComments == 2); }
\r
12146 message[0] = start; // restore original message
\r
12149 GetTimeMark(&now);
\r
12150 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12151 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12157 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12158 if (appData.icsEngineAnalyze) {
\r
12159 if (strstr(message, "whisper") != NULL ||
\r
12160 strstr(message, "kibitz") != NULL ||
\r
12161 strstr(message, "tellics") != NULL) return;
\r
12164 HandleMachineMove(message, cps);
\r
12169 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12170 ChessProgramState *cps;
\r
12171 int mps, inc, sd, st;
\r
12174 char buf[MSG_SIZ];
\r
12177 if( timeControl_2 > 0 ) {
\r
12178 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12179 tc = timeControl_2;
\r
12182 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12183 inc /= cps->timeOdds;
\r
12184 st /= cps->timeOdds;
\r
12186 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12189 /* Set exact time per move, normally using st command */
\r
12190 if (cps->stKludge) {
\r
12191 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12192 seconds = st % 60;
\r
12193 if (seconds == 0) {
\r
12194 sprintf(buf, "level 1 %d\n", st/60);
\r
12196 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12199 sprintf(buf, "st %d\n", st);
\r
12202 /* Set conventional or incremental time control, using level command */
\r
12203 if (seconds == 0) {
\r
12204 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12205 Fixed in later versions, but still avoid :seconds
\r
12206 when seconds is 0. */
\r
12207 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12209 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12210 seconds, inc/1000);
\r
12213 SendToProgram(buf, cps);
\r
12215 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12216 /* Orthogonally, limit search to given depth */
\r
12218 if (cps->sdKludge) {
\r
12219 sprintf(buf, "depth\n%d\n", sd);
\r
12221 sprintf(buf, "sd %d\n", sd);
\r
12223 SendToProgram(buf, cps);
\r
12226 if(cps->nps > 0) { /* [HGM] nps */
\r
12227 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12229 sprintf(buf, "nps %d\n", cps->nps);
\r
12230 SendToProgram(buf, cps);
\r
12235 ChessProgramState *WhitePlayer()
\r
12236 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12238 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12239 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12245 SendTimeRemaining(cps, machineWhite)
\r
12246 ChessProgramState *cps;
\r
12247 int /*boolean*/ machineWhite;
\r
12249 char message[MSG_SIZ];
\r
12250 long time, otime;
\r
12252 /* Note: this routine must be called when the clocks are stopped
\r
12253 or when they have *just* been set or switched; otherwise
\r
12254 it will be off by the time since the current tick started.
\r
12256 if (machineWhite) {
\r
12257 time = whiteTimeRemaining / 10;
\r
12258 otime = blackTimeRemaining / 10;
\r
12260 time = blackTimeRemaining / 10;
\r
12261 otime = whiteTimeRemaining / 10;
\r
12263 /* [HGM] translate opponent's time by time-odds factor */
\r
12264 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12265 if (appData.debugMode) {
\r
12266 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12269 if (time <= 0) time = 1;
\r
12270 if (otime <= 0) otime = 1;
\r
12272 sprintf(message, "time %ld\n", time);
\r
12273 SendToProgram(message, cps);
\r
12275 sprintf(message, "otim %ld\n", otime);
\r
12276 SendToProgram(message, cps);
\r
12280 BoolFeature(p, name, loc, cps)
\r
12284 ChessProgramState *cps;
\r
12286 char buf[MSG_SIZ];
\r
12287 int len = strlen(name);
\r
12289 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12291 sscanf(*p, "%d", &val);
\r
12292 *loc = (val != 0);
\r
12293 while (**p && **p != ' ') (*p)++;
\r
12294 sprintf(buf, "accepted %s\n", name);
\r
12295 SendToProgram(buf, cps);
\r
12302 IntFeature(p, name, loc, cps)
\r
12306 ChessProgramState *cps;
\r
12308 char buf[MSG_SIZ];
\r
12309 int len = strlen(name);
\r
12310 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12312 sscanf(*p, "%d", loc);
\r
12313 while (**p && **p != ' ') (*p)++;
\r
12314 sprintf(buf, "accepted %s\n", name);
\r
12315 SendToProgram(buf, cps);
\r
12322 StringFeature(p, name, loc, cps)
\r
12326 ChessProgramState *cps;
\r
12328 char buf[MSG_SIZ];
\r
12329 int len = strlen(name);
\r
12330 if (strncmp((*p), name, len) == 0
\r
12331 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12333 sscanf(*p, "%[^\"]", loc);
\r
12334 while (**p && **p != '\"') (*p)++;
\r
12335 if (**p == '\"') (*p)++;
\r
12336 sprintf(buf, "accepted %s\n", name);
\r
12337 SendToProgram(buf, cps);
\r
12344 ParseOption(Option *opt, ChessProgramState *cps)
\r
12345 // [HGM] options: process the string that defines an engine option, and determine
\r
12346 // name, type, default value, and allowed value range
\r
12348 char *p, *q, buf[MSG_SIZ];
\r
12349 int n, min = (-1)<<31, max = 1<<31, def;
\r
12351 if(p = strstr(opt->name, " -spin ")) {
\r
12352 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12353 if(max < min) max = min; // enforce consistency
\r
12354 if(def < min) def = min;
\r
12355 if(def > max) def = max;
\r
12356 opt->value = def;
\r
12359 opt->type = Spin;
\r
12360 } else if(p = strstr(opt->name, " -string ")) {
\r
12361 opt->textValue = p+9;
\r
12362 opt->type = TextBox;
\r
12363 } else if(p = strstr(opt->name, " -check ")) {
\r
12364 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12365 opt->value = (def != 0);
\r
12366 opt->type = CheckBox;
\r
12367 } else if(p = strstr(opt->name, " -combo ")) {
\r
12368 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12369 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12370 opt->value = n = 0;
\r
12371 while(q = StrStr(q, " /// ")) {
\r
12372 n++; *q = 0; // count choices, and null-terminate each of them
\r
12374 if(*q == '*') { // remember default, which is marked with * prefix
\r
12378 cps->comboList[cps->comboCnt++] = q;
\r
12380 cps->comboList[cps->comboCnt++] = NULL;
\r
12381 opt->max = n + 1;
\r
12382 opt->type = ComboBox;
\r
12383 } else if(p = strstr(opt->name, " -button")) {
\r
12384 opt->type = Button;
\r
12385 } else if(p = strstr(opt->name, " -save")) {
\r
12386 opt->type = SaveButton;
\r
12387 } else return FALSE;
\r
12388 *p = 0; // terminate option name
\r
12389 // now look if the command-line options define a setting for this engine option.
\r
12390 if(cps->optionSettings && cps->optionSettings[0])
\r
12391 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12392 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12393 sprintf(buf, "option %s", p);
\r
12394 if(p = strstr(buf, ",")) *p = 0;
\r
12395 strcat(buf, "\n");
\r
12396 SendToProgram(buf, cps);
\r
12402 FeatureDone(cps, val)
\r
12403 ChessProgramState* cps;
\r
12406 DelayedEventCallback cb = GetDelayedEvent();
\r
12407 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12408 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12409 CancelDelayedEvent();
\r
12410 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12412 cps->initDone = val;
\r
12415 /* Parse feature command from engine */
\r
12417 ParseFeatures(args, cps)
\r
12419 ChessProgramState *cps;
\r
12424 char buf[MSG_SIZ];
\r
12427 while (*p == ' ') p++;
\r
12428 if (*p == NULLCHAR) return;
\r
12430 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12431 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12432 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12433 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12434 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12435 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12436 /* Engine can disable reuse, but can't enable it if user said no */
\r
12437 if (!val) cps->reuse = FALSE;
\r
12440 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12441 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12442 if (gameMode == TwoMachinesPlay) {
\r
12443 DisplayTwoMachinesTitle();
\r
12445 DisplayTitle("");
\r
12449 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12450 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12451 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12452 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12453 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12454 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12455 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12456 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12457 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12458 if (IntFeature(&p, "done", &val, cps)) {
\r
12459 FeatureDone(cps, val);
\r
12462 /* Added by Tord: */
\r
12463 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12464 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12465 /* End of additions by Tord */
\r
12467 /* [HGM] added features: */
\r
12468 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12469 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12470 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12471 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12472 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12473 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12474 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12475 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12476 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12477 cps->nrOptions--;
\r
12478 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12479 DisplayError(buf, 0);
\r
12483 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12484 /* End of additions by HGM */
\r
12486 /* unknown feature: complain and skip */
\r
12488 while (*q && *q != '=') q++;
\r
12489 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12490 SendToProgram(buf, cps);
\r
12494 if (*p == '\"') {
\r
12496 while (*p && *p != '\"') p++;
\r
12497 if (*p == '\"') p++;
\r
12499 while (*p && *p != ' ') p++;
\r
12507 PeriodicUpdatesEvent(newState)
\r
12510 if (newState == appData.periodicUpdates)
\r
12513 appData.periodicUpdates=newState;
\r
12515 /* Display type changes, so update it now */
\r
12516 DisplayAnalysis();
\r
12518 /* Get the ball rolling again... */
\r
12520 AnalysisPeriodicEvent(1);
\r
12521 StartAnalysisClock();
\r
12526 PonderNextMoveEvent(newState)
\r
12529 if (newState == appData.ponderNextMove) return;
\r
12530 if (gameMode == EditPosition) EditPositionDone();
\r
12532 SendToProgram("hard\n", &first);
\r
12533 if (gameMode == TwoMachinesPlay) {
\r
12534 SendToProgram("hard\n", &second);
\r
12537 SendToProgram("easy\n", &first);
\r
12538 thinkOutput[0] = NULLCHAR;
\r
12539 if (gameMode == TwoMachinesPlay) {
\r
12540 SendToProgram("easy\n", &second);
\r
12543 appData.ponderNextMove = newState;
\r
12547 NewSettingEvent(option, command, value)
\r
12549 int option, value;
\r
12551 char buf[MSG_SIZ];
\r
12553 if (gameMode == EditPosition) EditPositionDone();
\r
12554 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12555 SendToProgram(buf, &first);
\r
12556 if (gameMode == TwoMachinesPlay) {
\r
12557 SendToProgram(buf, &second);
\r
12562 ShowThinkingEvent()
\r
12563 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12565 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12566 int newState = appData.showThinking
\r
12567 // [HGM] thinking: other features now need thinking output as well
\r
12568 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12570 if (oldState == newState) return;
\r
12571 oldState = newState;
\r
12572 if (gameMode == EditPosition) EditPositionDone();
\r
12574 SendToProgram("post\n", &first);
\r
12575 if (gameMode == TwoMachinesPlay) {
\r
12576 SendToProgram("post\n", &second);
\r
12579 SendToProgram("nopost\n", &first);
\r
12580 thinkOutput[0] = NULLCHAR;
\r
12581 if (gameMode == TwoMachinesPlay) {
\r
12582 SendToProgram("nopost\n", &second);
\r
12585 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12589 AskQuestionEvent(title, question, replyPrefix, which)
\r
12590 char *title; char *question; char *replyPrefix; char *which;
\r
12592 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12593 if (pr == NoProc) return;
\r
12594 AskQuestion(title, question, replyPrefix, pr);
\r
12598 DisplayMove(moveNumber)
\r
12601 char message[MSG_SIZ];
\r
12602 char res[MSG_SIZ];
\r
12603 char cpThinkOutput[MSG_SIZ];
\r
12605 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12607 if (moveNumber == forwardMostMove - 1 ||
\r
12608 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12610 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12612 if (strchr(cpThinkOutput, '\n')) {
\r
12613 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12616 *cpThinkOutput = NULLCHAR;
\r
12619 /* [AS] Hide thinking from human user */
\r
12620 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12621 *cpThinkOutput = NULLCHAR;
\r
12622 if( thinkOutput[0] != NULLCHAR ) {
\r
12625 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12626 cpThinkOutput[i] = '.';
\r
12628 cpThinkOutput[i] = NULLCHAR;
\r
12629 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12633 if (moveNumber == forwardMostMove - 1 &&
\r
12634 gameInfo.resultDetails != NULL) {
\r
12635 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12636 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12638 sprintf(res, " {%s} %s",
\r
12639 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12642 res[0] = NULLCHAR;
\r
12645 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12646 DisplayMessage(res, cpThinkOutput);
\r
12648 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12649 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12650 parseList[moveNumber], res);
\r
12651 DisplayMessage(message, cpThinkOutput);
\r
12656 DisplayAnalysisText(text)
\r
12659 char buf[MSG_SIZ];
\r
12661 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12662 || appData.icsEngineAnalyze) {
\r
12663 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12664 AnalysisPopUp(buf, text);
\r
12669 only_one_move(str)
\r
12672 while (*str && isspace(*str)) ++str;
\r
12673 while (*str && !isspace(*str)) ++str;
\r
12674 if (!*str) return 1;
\r
12675 while (*str && isspace(*str)) ++str;
\r
12676 if (!*str) return 1;
\r
12681 DisplayAnalysis()
\r
12683 char buf[MSG_SIZ];
\r
12684 char lst[MSG_SIZ / 2];
\r
12686 static char *xtra[] = { "", " (--)", " (++)" };
\r
12689 if (programStats.time == 0) {
\r
12690 programStats.time = 1;
\r
12693 if (programStats.got_only_move) {
\r
12694 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12696 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12698 nps = (u64ToDouble(programStats.nodes) /
\r
12699 ((double)programStats.time /100.0));
\r
12701 cs = programStats.time % 100;
\r
12702 s = programStats.time / 100;
\r
12703 h = (s / (60*60));
\r
12708 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12709 if (programStats.move_name[0] != NULLCHAR) {
\r
12710 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12711 programStats.depth,
\r
12712 programStats.nr_moves-programStats.moves_left,
\r
12713 programStats.nr_moves, programStats.move_name,
\r
12714 ((float)programStats.score)/100.0, lst,
\r
12715 only_one_move(lst)?
\r
12716 xtra[programStats.got_fail] : "",
\r
12717 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12719 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12720 programStats.depth,
\r
12721 programStats.nr_moves-programStats.moves_left,
\r
12722 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12724 only_one_move(lst)?
\r
12725 xtra[programStats.got_fail] : "",
\r
12726 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12729 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12730 programStats.depth,
\r
12731 ((float)programStats.score)/100.0,
\r
12733 only_one_move(lst)?
\r
12734 xtra[programStats.got_fail] : "",
\r
12735 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12738 DisplayAnalysisText(buf);
\r
12742 DisplayComment(moveNumber, text)
\r
12746 char title[MSG_SIZ];
\r
12747 char buf[8000]; // comment can be long!
\r
12748 int score, depth;
\r
12750 if( appData.autoDisplayComment ) {
\r
12751 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12752 strcpy(title, "Comment");
\r
12754 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12755 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12756 parseList[moveNumber]);
\r
12758 } else title[0] = 0;
\r
12760 // [HGM] PV info: display PV info together with (or as) comment
\r
12761 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12762 if(text == NULL) text = "";
\r
12763 score = pvInfoList[moveNumber].score;
\r
12764 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12765 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12766 CommentPopUp(title, buf);
\r
12768 if (text != NULL)
\r
12769 CommentPopUp(title, text);
\r
12772 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12773 * might be busy thinking or pondering. It can be omitted if your
\r
12774 * gnuchess is configured to stop thinking immediately on any user
\r
12775 * input. However, that gnuchess feature depends on the FIONREAD
\r
12776 * ioctl, which does not work properly on some flavors of Unix.
\r
12780 ChessProgramState *cps;
\r
12783 if (!cps->useSigint) return;
\r
12784 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12785 switch (gameMode) {
\r
12786 case MachinePlaysWhite:
\r
12787 case MachinePlaysBlack:
\r
12788 case TwoMachinesPlay:
\r
12789 case IcsPlayingWhite:
\r
12790 case IcsPlayingBlack:
\r
12791 case AnalyzeMode:
\r
12792 case AnalyzeFile:
\r
12793 /* Skip if we know it isn't thinking */
\r
12794 if (!cps->maybeThinking) return;
\r
12795 if (appData.debugMode)
\r
12796 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12797 InterruptChildProcess(cps->pr);
\r
12798 cps->maybeThinking = FALSE;
\r
12803 #endif /*ATTENTION*/
\r
12809 if (whiteTimeRemaining <= 0) {
\r
12810 if (!whiteFlag) {
\r
12811 whiteFlag = TRUE;
\r
12812 if (appData.icsActive) {
\r
12813 if (appData.autoCallFlag &&
\r
12814 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12815 SendToICS(ics_prefix);
\r
12816 SendToICS("flag\n");
\r
12820 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12822 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12823 if (appData.autoCallFlag) {
\r
12824 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12831 if (blackTimeRemaining <= 0) {
\r
12832 if (!blackFlag) {
\r
12833 blackFlag = TRUE;
\r
12834 if (appData.icsActive) {
\r
12835 if (appData.autoCallFlag &&
\r
12836 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12837 SendToICS(ics_prefix);
\r
12838 SendToICS("flag\n");
\r
12842 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12844 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12845 if (appData.autoCallFlag) {
\r
12846 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12857 CheckTimeControl()
\r
12859 if (!appData.clockMode || appData.icsActive ||
\r
12860 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12863 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12865 if ( !WhiteOnMove(forwardMostMove) )
\r
12866 /* White made time control */
\r
12867 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12868 /* [HGM] time odds: correct new time quota for time odds! */
\r
12869 / WhitePlayer()->timeOdds;
\r
12871 /* Black made time control */
\r
12872 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12873 / WhitePlayer()->other->timeOdds;
\r
12877 DisplayBothClocks()
\r
12879 int wom = gameMode == EditPosition ?
\r
12880 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12881 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12882 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12886 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12887 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12888 to use other calls if you don't. Clocks will be less accurate if
\r
12889 you have neither ftime nor gettimeofday.
\r
12892 /* VS 2008 requires the #include outside of the function */
\r
12893 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12894 #include <sys/timeb.h>
\r
12897 /* Get the current time as a TimeMark */
\r
12902 #if HAVE_GETTIMEOFDAY
\r
12904 struct timeval timeVal;
\r
12905 struct timezone timeZone;
\r
12907 gettimeofday(&timeVal, &timeZone);
\r
12908 tm->sec = (long) timeVal.tv_sec;
\r
12909 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12911 #else /*!HAVE_GETTIMEOFDAY*/
\r
12914 // include <sys/timeb.h> / moved to just above start of function
\r
12915 struct timeb timeB;
\r
12918 tm->sec = (long) timeB.time;
\r
12919 tm->ms = (int) timeB.millitm;
\r
12921 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12922 tm->sec = (long) time(NULL);
\r
12928 /* Return the difference in milliseconds between two
\r
12929 time marks. We assume the difference will fit in a long!
\r
12932 SubtractTimeMarks(tm2, tm1)
\r
12933 TimeMark *tm2, *tm1;
\r
12935 return 1000L*(tm2->sec - tm1->sec) +
\r
12936 (long) (tm2->ms - tm1->ms);
\r
12941 * Code to manage the game clocks.
\r
12943 * In tournament play, black starts the clock and then white makes a move.
\r
12944 * We give the human user a slight advantage if he is playing white---the
\r
12945 * clocks don't run until he makes his first move, so it takes zero time.
\r
12946 * Also, we don't account for network lag, so we could get out of sync
\r
12947 * with GNU Chess's clock -- but then, referees are always right.
\r
12950 static TimeMark tickStartTM;
\r
12951 static long intendedTickLength;
\r
12954 NextTickLength(timeRemaining)
\r
12955 long timeRemaining;
\r
12957 long nominalTickLength, nextTickLength;
\r
12959 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12960 nominalTickLength = 100L;
\r
12962 nominalTickLength = 1000L;
\r
12963 nextTickLength = timeRemaining % nominalTickLength;
\r
12964 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12966 return nextTickLength;
\r
12969 /* Adjust clock one minute up or down */
\r
12971 AdjustClock(Boolean which, int dir)
\r
12973 if(which) blackTimeRemaining += 60000*dir;
\r
12974 else whiteTimeRemaining += 60000*dir;
\r
12975 DisplayBothClocks();
\r
12978 /* Stop clocks and reset to a fresh time control */
\r
12982 (void) StopClockTimer();
\r
12983 if (appData.icsActive) {
\r
12984 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12985 } else { /* [HGM] correct new time quote for time odds */
\r
12986 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12987 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12989 if (whiteFlag || blackFlag) {
\r
12990 DisplayTitle("");
\r
12991 whiteFlag = blackFlag = FALSE;
\r
12993 DisplayBothClocks();
\r
12996 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12998 /* Decrement running clock by amount of time that has passed */
\r
13000 DecrementClocks()
\r
13002 long timeRemaining;
\r
13003 long lastTickLength, fudge;
\r
13006 if (!appData.clockMode) return;
\r
13007 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13009 GetTimeMark(&now);
\r
13011 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13013 /* Fudge if we woke up a little too soon */
\r
13014 fudge = intendedTickLength - lastTickLength;
\r
13015 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13017 if (WhiteOnMove(forwardMostMove)) {
\r
13018 if(whiteNPS >= 0) lastTickLength = 0;
\r
13019 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13020 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13021 WhiteOnMove(currentMove));
\r
13023 if(blackNPS >= 0) lastTickLength = 0;
\r
13024 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13025 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13026 !WhiteOnMove(currentMove));
\r
13029 if (CheckFlags()) return;
\r
13031 tickStartTM = now;
\r
13032 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13033 StartClockTimer(intendedTickLength);
\r
13035 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13036 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13037 * with increment) the time remaining has increased to a level above the
\r
13038 * threshold, reset the alarm so it can sound again.
\r
13041 if (appData.icsActive && appData.icsAlarm) {
\r
13043 /* make sure we are dealing with the user's clock */
\r
13044 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13045 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13048 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13049 alarmSounded = FALSE;
\r
13050 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13051 PlayAlarmSound();
\r
13052 alarmSounded = TRUE;
\r
13058 /* A player has just moved, so stop the previously running
\r
13059 clock and (if in clock mode) start the other one.
\r
13060 We redisplay both clocks in case we're in ICS mode, because
\r
13061 ICS gives us an update to both clocks after every move.
\r
13062 Note that this routine is called *after* forwardMostMove
\r
13063 is updated, so the last fractional tick must be subtracted
\r
13064 from the color that is *not* on move now.
\r
13069 long lastTickLength;
\r
13071 int flagged = FALSE;
\r
13073 GetTimeMark(&now);
\r
13075 if (StopClockTimer() && appData.clockMode) {
\r
13076 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13077 if (WhiteOnMove(forwardMostMove)) {
\r
13078 if(blackNPS >= 0) lastTickLength = 0;
\r
13079 blackTimeRemaining -= lastTickLength;
\r
13080 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13081 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13082 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13083 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13085 if(whiteNPS >= 0) lastTickLength = 0;
\r
13086 whiteTimeRemaining -= lastTickLength;
\r
13087 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13088 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13089 pvInfoList[forwardMostMove-1].time =
\r
13090 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13092 flagged = CheckFlags();
\r
13094 CheckTimeControl();
\r
13096 if (flagged || !appData.clockMode) return;
\r
13098 switch (gameMode) {
\r
13099 case MachinePlaysBlack:
\r
13100 case MachinePlaysWhite:
\r
13101 case BeginningOfGame:
\r
13102 if (pausing) return;
\r
13106 case PlayFromGameFile:
\r
13107 case IcsExamining:
\r
13114 tickStartTM = now;
\r
13115 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13116 whiteTimeRemaining : blackTimeRemaining);
\r
13117 StartClockTimer(intendedTickLength);
\r
13121 /* Stop both clocks */
\r
13125 long lastTickLength;
\r
13128 if (!StopClockTimer()) return;
\r
13129 if (!appData.clockMode) return;
\r
13131 GetTimeMark(&now);
\r
13133 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13134 if (WhiteOnMove(forwardMostMove)) {
\r
13135 if(whiteNPS >= 0) lastTickLength = 0;
\r
13136 whiteTimeRemaining -= lastTickLength;
\r
13137 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13139 if(blackNPS >= 0) lastTickLength = 0;
\r
13140 blackTimeRemaining -= lastTickLength;
\r
13141 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13146 /* Start clock of player on move. Time may have been reset, so
\r
13147 if clock is already running, stop and restart it. */
\r
13151 (void) StopClockTimer(); /* in case it was running already */
\r
13152 DisplayBothClocks();
\r
13153 if (CheckFlags()) return;
\r
13155 if (!appData.clockMode) return;
\r
13156 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13158 GetTimeMark(&tickStartTM);
\r
13159 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13160 whiteTimeRemaining : blackTimeRemaining);
\r
13162 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13163 whiteNPS = blackNPS = -1;
\r
13164 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13165 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13166 whiteNPS = first.nps;
\r
13167 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13168 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13169 blackNPS = first.nps;
\r
13170 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13171 whiteNPS = second.nps;
\r
13172 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13173 blackNPS = second.nps;
\r
13174 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13176 StartClockTimer(intendedTickLength);
\r
13183 long second, minute, hour, day;
\r
13185 static char buf[32];
\r
13187 if (ms > 0 && ms <= 9900) {
\r
13188 /* convert milliseconds to tenths, rounding up */
\r
13189 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13191 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13195 /* convert milliseconds to seconds, rounding up */
\r
13196 /* use floating point to avoid strangeness of integer division
\r
13197 with negative dividends on many machines */
\r
13198 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13200 if (second < 0) {
\r
13202 second = -second;
\r
13205 day = second / (60 * 60 * 24);
\r
13206 second = second % (60 * 60 * 24);
\r
13207 hour = second / (60 * 60);
\r
13208 second = second % (60 * 60);
\r
13209 minute = second / 60;
\r
13210 second = second % 60;
\r
13213 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13214 sign, day, hour, minute, second);
\r
13215 else if (hour > 0)
\r
13216 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13218 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13225 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13228 StrStr(string, match)
\r
13229 char *string, *match;
\r
13233 length = strlen(match);
\r
13235 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13236 if (!strncmp(match, string, length))
\r
13243 StrCaseStr(string, match)
\r
13244 char *string, *match;
\r
13246 int i, j, length;
\r
13248 length = strlen(match);
\r
13250 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13251 for (j = 0; j < length; j++) {
\r
13252 if (ToLower(match[j]) != ToLower(string[j]))
\r
13255 if (j == length) return string;
\r
13261 #ifndef _amigados
\r
13263 StrCaseCmp(s1, s2)
\r
13269 c1 = ToLower(*s1++);
\r
13270 c2 = ToLower(*s2++);
\r
13271 if (c1 > c2) return 1;
\r
13272 if (c1 < c2) return -1;
\r
13273 if (c1 == NULLCHAR) return 0;
\r
13282 return isupper(c) ? tolower(c) : c;
\r
13290 return islower(c) ? toupper(c) : c;
\r
13292 #endif /* !_amigados */
\r
13300 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13307 StrSavePtr(s, savePtr)
\r
13308 char *s, **savePtr;
\r
13313 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13314 strcpy(*savePtr, s);
\r
13316 return(*savePtr);
\r
13324 char buf[MSG_SIZ];
\r
13326 clock = time((time_t *)NULL);
\r
13327 tm = localtime(&clock);
\r
13328 sprintf(buf, "%04d.%02d.%02d",
\r
13329 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13330 return StrSave(buf);
\r
13335 PositionToFEN(move, overrideCastling)
\r
13337 char *overrideCastling;
\r
13339 int i, j, fromX, fromY, toX, toY;
\r
13344 ChessSquare piece;
\r
13346 whiteToPlay = (gameMode == EditPosition) ?
\r
13347 !blackPlaysFirst : (move % 2 == 0);
\r
13350 /* Piece placement data */
\r
13351 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13353 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13354 if (boards[move][i][j] == EmptySquare) {
\r
13356 } else { ChessSquare piece = boards[move][i][j];
\r
13357 if (emptycount > 0) {
\r
13358 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13359 *p++ = '0' + emptycount;
\r
13360 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13363 if(PieceToChar(piece) == '+') {
\r
13364 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13366 piece = (ChessSquare)(DEMOTED piece);
\r
13368 *p++ = PieceToChar(piece);
\r
13369 if(p[-1] == '~') {
\r
13370 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13371 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13376 if (emptycount > 0) {
\r
13377 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13378 *p++ = '0' + emptycount;
\r
13379 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13386 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13387 if( gameInfo.holdingsWidth ) {
\r
13388 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13390 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13391 piece = boards[move][i][BOARD_WIDTH-1];
\r
13392 if( piece != EmptySquare )
\r
13393 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13394 *p++ = PieceToChar(piece);
\r
13396 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13397 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13398 if( piece != EmptySquare )
\r
13399 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13400 *p++ = PieceToChar(piece);
\r
13403 if( q == p ) *p++ = '-';
\r
13408 /* Active color */
\r
13409 *p++ = whiteToPlay ? 'w' : 'b';
\r
13412 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
\r
13413 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
\r
13415 if(nrCastlingRights) {
\r
13417 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13418 /* [HGM] write directly from rights */
\r
13419 if(castlingRights[move][2] >= 0 &&
\r
13420 castlingRights[move][0] >= 0 )
\r
13421 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13422 if(castlingRights[move][2] >= 0 &&
\r
13423 castlingRights[move][1] >= 0 )
\r
13424 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13425 if(castlingRights[move][5] >= 0 &&
\r
13426 castlingRights[move][3] >= 0 )
\r
13427 *p++ = castlingRights[move][3] + AAA;
\r
13428 if(castlingRights[move][5] >= 0 &&
\r
13429 castlingRights[move][4] >= 0 )
\r
13430 *p++ = castlingRights[move][4] + AAA;
\r
13433 /* [HGM] write true castling rights */
\r
13434 if( nrCastlingRights == 6 ) {
\r
13435 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13436 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13437 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13438 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13439 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13440 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13441 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13442 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13445 if (q == p) *p++ = '-'; /* No castling rights */
\r
13449 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13450 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13451 /* En passant target square */
\r
13452 if (move > backwardMostMove) {
\r
13453 fromX = moveList[move - 1][0] - AAA;
\r
13454 fromY = moveList[move - 1][1] - ONE;
\r
13455 toX = moveList[move - 1][2] - AAA;
\r
13456 toY = moveList[move - 1][3] - ONE;
\r
13457 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13458 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13459 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13461 /* 2-square pawn move just happened */
\r
13462 *p++ = toX + AAA;
\r
13463 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13474 /* [HGM] find reversible plies */
\r
13475 { int i = 0, j=move;
\r
13477 if (appData.debugMode) { int k;
\r
13478 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13479 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13480 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13484 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13485 if( j == backwardMostMove ) i += initialRulePlies;
\r
13486 sprintf(p, "%d ", i);
\r
13487 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13489 /* Fullmove number */
\r
13490 sprintf(p, "%d", (move / 2) + 1);
\r
13492 return StrSave(buf);
\r
13496 ParseFEN(board, blackPlaysFirst, fen)
\r
13498 int *blackPlaysFirst;
\r
13504 ChessSquare piece;
\r
13508 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13509 if(gameInfo.holdingsWidth) {
\r
13510 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13511 board[i][0] = EmptySquare; /* black holdings */
\r
13512 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13513 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13514 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13518 /* Piece placement data */
\r
13519 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13522 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13523 if (*p == '/') p++;
\r
13524 emptycount = gameInfo.boardWidth - j;
\r
13525 while (emptycount--)
\r
13526 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13528 #if(BOARD_SIZE >= 10)
\r
13529 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13530 p++; emptycount=10;
\r
13531 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13532 while (emptycount--)
\r
13533 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13535 } else if (isdigit(*p)) {
\r
13536 emptycount = *p++ - '0';
\r
13537 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13538 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13539 while (emptycount--)
\r
13540 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13541 } else if (*p == '+' || isalpha(*p)) {
\r
13542 if (j >= gameInfo.boardWidth) return FALSE;
\r
13544 piece = CharToPiece(*++p);
\r
13545 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13546 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13547 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13548 } else piece = CharToPiece(*p++);
\r
13550 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13551 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13552 piece = (ChessSquare) (PROMOTED piece);
\r
13553 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13556 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13562 while (*p == '/' || *p == ' ') p++;
\r
13564 /* [HGM] look for Crazyhouse holdings here */
\r
13565 while(*p==' ') p++;
\r
13566 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13567 if(*p == '[') p++;
\r
13568 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13569 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13570 /* if we would allow FEN reading to set board size, we would */
\r
13571 /* have to add holdings and shift the board read so far here */
\r
13572 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13574 if((int) piece >= (int) BlackPawn ) {
\r
13575 i = (int)piece - (int)BlackPawn;
\r
13576 i = PieceToNumber((ChessSquare)i);
\r
13577 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13578 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13579 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13581 i = (int)piece - (int)WhitePawn;
\r
13582 i = PieceToNumber((ChessSquare)i);
\r
13583 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13584 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13585 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13589 if(*p == ']') *p++;
\r
13592 while(*p == ' ') p++;
\r
13594 /* Active color */
\r
13597 *blackPlaysFirst = FALSE;
\r
13600 *blackPlaysFirst = TRUE;
\r
13606 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13607 /* return the extra info in global variiables */
\r
13609 /* set defaults in case FEN is incomplete */
\r
13610 FENepStatus = EP_UNKNOWN;
\r
13611 for(i=0; i<nrCastlingRights; i++ ) {
\r
13612 FENcastlingRights[i] =
\r
13613 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13614 } /* assume possible unless obviously impossible */
\r
13615 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13616 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13617 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13618 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13619 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13620 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13621 FENrulePlies = 0;
\r
13623 while(*p==' ') p++;
\r
13624 if(nrCastlingRights) {
\r
13625 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13626 /* castling indicator present, so default becomes no castlings */
\r
13627 for(i=0; i<nrCastlingRights; i++ ) {
\r
13628 FENcastlingRights[i] = -1;
\r
13631 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13632 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13633 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13634 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13635 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13637 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13638 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13639 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13643 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13644 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13645 FENcastlingRights[2] = whiteKingFile;
\r
13648 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13649 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13650 FENcastlingRights[2] = whiteKingFile;
\r
13653 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13654 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13655 FENcastlingRights[5] = blackKingFile;
\r
13658 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13659 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13660 FENcastlingRights[5] = blackKingFile;
\r
13663 default: /* FRC castlings */
\r
13664 if(c >= 'a') { /* black rights */
\r
13665 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13666 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13667 if(i == BOARD_RGHT) break;
\r
13668 FENcastlingRights[5] = i;
\r
13670 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13671 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13673 FENcastlingRights[3] = c;
\r
13675 FENcastlingRights[4] = c;
\r
13676 } else { /* white rights */
\r
13677 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13678 if(board[0][i] == WhiteKing) break;
\r
13679 if(i == BOARD_RGHT) break;
\r
13680 FENcastlingRights[2] = i;
\r
13681 c -= AAA - 'a' + 'A';
\r
13682 if(board[0][c] >= WhiteKing) break;
\r
13684 FENcastlingRights[0] = c;
\r
13686 FENcastlingRights[1] = c;
\r
13690 if (appData.debugMode) {
\r
13691 fprintf(debugFP, "FEN castling rights:");
\r
13692 for(i=0; i<nrCastlingRights; i++)
\r
13693 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13694 fprintf(debugFP, "\n");
\r
13697 while(*p==' ') p++;
\r
13700 /* read e.p. field in games that know e.p. capture */
\r
13701 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13702 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13704 p++; FENepStatus = EP_NONE;
\r
13706 char c = *p++ - AAA;
\r
13708 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13709 if(*p >= '0' && *p <='9') *p++;
\r
13715 if(sscanf(p, "%d", &i) == 1) {
\r
13716 FENrulePlies = i; /* 50-move ply counter */
\r
13717 /* (The move number is still ignored) */
\r
13724 EditPositionPasteFEN(char *fen)
\r
13726 if (fen != NULL) {
\r
13727 Board initial_position;
\r
13729 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13730 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13733 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13734 EditPositionEvent();
\r
13735 blackPlaysFirst = savedBlackPlaysFirst;
\r
13736 CopyBoard(boards[0], initial_position);
\r
13737 /* [HGM] copy FEN attributes as well */
\r
13739 initialRulePlies = FENrulePlies;
\r
13740 epStatus[0] = FENepStatus;
\r
13741 for( i=0; i<nrCastlingRights; i++ )
\r
13742 castlingRights[0][i] = FENcastlingRights[i];
\r
13744 EditPositionDone();
\r
13745 DisplayBothClocks();
\r
13746 DrawPosition(FALSE, boards[currentMove]);
\r