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
160 Board board, char *castle, char *ep));
\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 && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
\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
5220 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
\r
5221 CopyBoard(testBoard, boards[currentMove]);
\r
5222 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
\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 (gameMode == BeginningOfGame) {
\r
5267 if (appData.noChessProgram) {
\r
5268 gameMode = EditGame;
\r
5271 char buf[MSG_SIZ];
\r
5272 gameMode = MachinePlaysBlack;
\r
5275 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5276 DisplayTitle(buf);
\r
5277 if (first.sendName) {
\r
5278 sprintf(buf, "name %s\n", gameInfo.white);
\r
5279 SendToProgram(buf, &first);
\r
5285 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5286 /* Relay move to ICS or chess engine */
\r
5287 if (appData.icsActive) {
\r
5288 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5289 gameMode == IcsExamining) {
\r
5290 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5291 ics_user_moved = 1;
\r
5294 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5295 gameMode == MachinePlaysWhite ||
\r
5296 gameMode == MachinePlaysBlack)) {
\r
5297 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5299 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5300 // [HGM] book: if program might be playing, let it use book
\r
5301 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5302 first.maybeThinking = TRUE;
\r
5303 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5304 if (currentMove == cmailOldMove + 1) {
\r
5305 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5309 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5311 switch (gameMode) {
\r
5313 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5314 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5318 case MT_CHECKMATE:
\r
5319 case MT_STAINMATE:
\r
5320 if (WhiteOnMove(currentMove)) {
\r
5321 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5323 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5326 case MT_STALEMATE:
\r
5327 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5332 case MachinePlaysBlack:
\r
5333 case MachinePlaysWhite:
\r
5334 /* disable certain menu options while machine is thinking */
\r
5335 SetMachineThinkingEnables();
\r
5342 if(bookHit) { // [HGM] book: simulate book reply
\r
5343 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5345 programStats.nodes = programStats.depth = programStats.time =
\r
5346 programStats.score = programStats.got_only_move = 0;
\r
5347 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5349 strcpy(bookMove, "move ");
\r
5350 strcat(bookMove, bookHit);
\r
5351 HandleMachineMove(bookMove, &first);
\r
5357 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5358 int fromX, fromY, toX, toY;
\r
5361 /* [HGM] This routine was added to allow calling of its two logical
\r
5362 parts from other modules in the old way. Before, UserMoveEvent()
\r
5363 automatically called FinishMove() if the move was OK, and returned
\r
5364 otherwise. I separated the two, in order to make it possible to
\r
5365 slip a promotion popup in between. But that it always needs two
\r
5366 calls, to the first part, (now called UserMoveTest() ), and to
\r
5367 FinishMove if the first part succeeded. Calls that do not need
\r
5368 to do anything in between, can call this routine the old way.
\r
5370 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5371 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5372 if(moveType != ImpossibleMove)
\r
5373 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5376 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5378 // char * hint = lastHint;
\r
5379 FrontEndProgramStats stats;
\r
5381 stats.which = cps == &first ? 0 : 1;
\r
5382 stats.depth = cpstats->depth;
\r
5383 stats.nodes = cpstats->nodes;
\r
5384 stats.score = cpstats->score;
\r
5385 stats.time = cpstats->time;
\r
5386 stats.pv = cpstats->movelist;
\r
5387 stats.hint = lastHint;
\r
5388 stats.an_move_index = 0;
\r
5389 stats.an_move_count = 0;
\r
5391 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5392 stats.hint = cpstats->move_name;
\r
5393 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5394 stats.an_move_count = cpstats->nr_moves;
\r
5397 SetProgramStats( &stats );
\r
5400 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5401 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5402 char *bookHit = NULL;
\r
5404 //first determine if the incoming move brings opponent into his book
\r
5405 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5406 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5407 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5408 if(bookHit != NULL && !cps->bookSuspend) {
\r
5409 // make sure opponent is not going to reply after receiving move to book position
\r
5410 SendToProgram("force\n", cps);
\r
5411 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5413 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5414 // now arrange restart after book miss
\r
5416 // after a book hit we never send 'go', and the code after the call to this routine
\r
5417 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5418 char buf[MSG_SIZ];
\r
5419 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5420 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5421 SendToProgram(buf, cps);
\r
5422 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5423 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5424 SendToProgram("go\n", cps);
\r
5425 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5426 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5427 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5428 SendToProgram("go\n", cps);
\r
5429 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5431 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5434 char *savedMessage;
\r
5435 ChessProgramState *savedState;
\r
5436 void DeferredBookMove(void)
\r
5438 if(savedState->lastPing != savedState->lastPong)
\r
5439 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5441 HandleMachineMove(savedMessage, savedState);
\r
5445 HandleMachineMove(message, cps)
\r
5447 ChessProgramState *cps;
\r
5449 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5450 char realname[MSG_SIZ];
\r
5451 int fromX, fromY, toX, toY;
\r
5452 ChessMove moveType;
\r
5458 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5460 * Kludge to ignore BEL characters
\r
5462 while (*message == '\007') message++;
\r
5465 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5467 if(cps->debug && *message == '#') return;
\r
5470 * Look for book output
\r
5472 if (cps == &first && bookRequested) {
\r
5473 if (message[0] == '\t' || message[0] == ' ') {
\r
5474 /* Part of the book output is here; append it */
\r
5475 strcat(bookOutput, message);
\r
5476 strcat(bookOutput, " \n");
\r
5478 } else if (bookOutput[0] != NULLCHAR) {
\r
5479 /* All of book output has arrived; display it */
\r
5480 char *p = bookOutput;
\r
5481 while (*p != NULLCHAR) {
\r
5482 if (*p == '\t') *p = ' ';
\r
5485 DisplayInformation(bookOutput);
\r
5486 bookRequested = FALSE;
\r
5487 /* Fall through to parse the current output */
\r
5492 * Look for machine move.
\r
5494 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5495 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5497 /* This method is only useful on engines that support ping */
\r
5498 if (cps->lastPing != cps->lastPong) {
\r
5499 if (gameMode == BeginningOfGame) {
\r
5500 /* Extra move from before last new; ignore */
\r
5501 if (appData.debugMode) {
\r
5502 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5505 if (appData.debugMode) {
\r
5506 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5507 cps->which, gameMode);
\r
5510 SendToProgram("undo\n", cps);
\r
5515 switch (gameMode) {
\r
5516 case BeginningOfGame:
\r
5517 /* Extra move from before last reset; ignore */
\r
5518 if (appData.debugMode) {
\r
5519 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5526 /* Extra move after we tried to stop. The mode test is
\r
5527 not a reliable way of detecting this problem, but it's
\r
5528 the best we can do on engines that don't support ping.
\r
5530 if (appData.debugMode) {
\r
5531 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5532 cps->which, gameMode);
\r
5534 SendToProgram("undo\n", cps);
\r
5537 case MachinePlaysWhite:
\r
5538 case IcsPlayingWhite:
\r
5539 machineWhite = TRUE;
\r
5542 case MachinePlaysBlack:
\r
5543 case IcsPlayingBlack:
\r
5544 machineWhite = FALSE;
\r
5547 case TwoMachinesPlay:
\r
5548 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5551 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5552 if (appData.debugMode) {
\r
5554 "Ignoring move out of turn by %s, gameMode %d"
\r
5555 ", forwardMost %d\n",
\r
5556 cps->which, gameMode, forwardMostMove);
\r
5561 if (appData.debugMode) { int f = forwardMostMove;
\r
5562 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5563 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5565 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5566 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5567 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5568 /* Machine move could not be parsed; ignore it. */
\r
5569 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5570 machineMove, cps->which);
\r
5571 DisplayError(buf1, 0);
\r
5572 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5573 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5574 if (gameMode == TwoMachinesPlay) {
\r
5575 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5581 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5582 /* So we have to redo legality test with true e.p. status here, */
\r
5583 /* to make sure an illegal e.p. capture does not slip through, */
\r
5584 /* to cause a forfeit on a justified illegal-move complaint */
\r
5585 /* of the opponent. */
\r
5586 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5587 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5589 ChessMove moveType;
\r
5590 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5591 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5592 fromY, fromX, toY, toX, promoChar);
\r
5593 if (appData.debugMode) {
\r
5595 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5596 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5597 fprintf(debugFP, "castling rights\n");
\r
5599 if(moveType == IllegalMove) {
\r
5600 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5601 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5602 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5605 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5606 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5607 when they shouldn't (like TSCP-Gothic) */
\r
5608 switch(moveType) {
\r
5609 case WhiteASideCastleFR:
\r
5610 case BlackASideCastleFR:
\r
5612 currentMoveString[2]++;
\r
5614 case WhiteHSideCastleFR:
\r
5615 case BlackHSideCastleFR:
\r
5617 currentMoveString[2]--;
\r
5619 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5622 hintRequested = FALSE;
\r
5623 lastHint[0] = NULLCHAR;
\r
5624 bookRequested = FALSE;
\r
5625 /* Program may be pondering now */
\r
5626 cps->maybeThinking = TRUE;
\r
5627 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5628 if (cps->offeredDraw) cps->offeredDraw--;
\r
5631 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5633 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5634 ics_user_moved = 1;
\r
5635 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5636 char buf[3*MSG_SIZ];
\r
5638 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
\r
5639 programStats.score / 100.,
\r
5640 programStats.depth,
\r
5641 programStats.time / 100.,
\r
5642 u64ToDouble(programStats.nodes),
\r
5643 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5644 programStats.movelist);
\r
5649 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5650 strcpy(machineMove, currentMoveString);
\r
5651 strcat(machineMove, "\n");
\r
5652 strcpy(moveList[forwardMostMove], machineMove);
\r
5654 /* [AS] Save move info and clear stats for next move */
\r
5655 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5656 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5657 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5658 ClearProgramStats();
\r
5659 thinkOutput[0] = NULLCHAR;
\r
5660 hiddenThinkOutputState = 0;
\r
5662 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5664 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5665 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5668 while( count < adjudicateLossPlies ) {
\r
5669 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5672 score = -score; /* Flip score for winning side */
\r
5675 if( score > adjudicateLossThreshold ) {
\r
5682 if( count >= adjudicateLossPlies ) {
\r
5683 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5685 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5686 "Xboard adjudication",
\r
5693 if( gameMode == TwoMachinesPlay ) {
\r
5694 // [HGM] some adjudications useful with buggy engines
\r
5695 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5696 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5699 if( appData.testLegality )
\r
5700 { /* [HGM] Some more adjudications for obstinate engines */
\r
5701 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5702 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5703 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5704 static int moveCount = 6;
\r
5706 char *reason = NULL;
\r
5708 /* Count what is on board. */
\r
5709 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5710 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5714 { /* count B,N,R and other of each side */
\r
5717 NrK++; break; // [HGM] atomic: count Kings
\r
5721 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5722 bishopsColor |= 1 << ((i^j)&1);
\r
5727 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5728 bishopsColor |= 1 << ((i^j)&1);
\r
5738 case EmptySquare:
\r
5743 PawnAdvance += m; NrPawns++;
\r
5745 NrPieces += (p != EmptySquare);
\r
5746 NrW += ((int)p < (int)BlackPawn);
\r
5747 if(gameInfo.variant == VariantXiangqi &&
\r
5748 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5749 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5750 NrW -= ((int)p < (int)BlackPawn);
\r
5754 /* Some material-based adjudications that have to be made before stalemate test */
\r
5755 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5756 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5757 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5758 if(appData.checkMates) {
\r
5759 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5760 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5761 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5762 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5767 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5768 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5769 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5770 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
\r
5771 if(appData.checkMates) {
\r
5772 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
\r
5773 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5774 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5775 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5779 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5781 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
\r
5782 if(appData.checkMates) {
\r
5783 /* but only adjudicate if adjudication enabled */
\r
5784 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5785 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5786 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5787 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5794 // don't wait for engine to announce game end if we can judge ourselves
\r
5795 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
\r
5796 castlingRights[forwardMostMove]) ) {
\r
5801 case MT_STALEMATE:
\r
5802 case MT_STAINMATE:
\r
5803 reason = "Xboard adjudication: Stalemate";
\r
5804 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
\r
5805 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
\r
5806 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5807 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
\r
5808 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
\r
5809 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
\r
5810 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
\r
5811 EP_CHECKMATE : EP_WINS);
\r
5812 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
\r
5813 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
\r
5816 case MT_CHECKMATE:
\r
5817 reason = "Xboard adjudication: Checkmate";
\r
5818 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
\r
5822 switch(i = epStatus[forwardMostMove]) {
\r
5823 case EP_STALEMATE:
\r
5824 result = GameIsDrawn; break;
\r
5825 case EP_CHECKMATE:
\r
5826 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
\r
5828 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
\r
5830 result = (ChessMove) 0;
\r
5832 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
\r
5833 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5834 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5835 GameEnds( result, reason, GE_XBOARD );
\r
5839 /* Next absolutely insufficient mating material. */
\r
5840 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5841 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5842 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5843 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5844 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5846 /* always flag draws, for judging claims */
\r
5847 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5849 if(appData.materialDraws) {
\r
5850 /* but only adjudicate them if adjudication enabled */
\r
5851 SendToProgram("force\n", cps->other); // suppress reply
\r
5852 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5853 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5854 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5859 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5860 if(NrPieces == 4 &&
\r
5861 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5862 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5863 || NrWN==2 || NrBN==2 /* KNNK */
\r
5864 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5866 if(--moveCount < 0 && appData.trivialDraws)
\r
5867 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5868 SendToProgram("force\n", cps->other); // suppress reply
\r
5869 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5871 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5874 } else moveCount = 6;
\r
5878 if (appData.debugMode) { int i;
\r
5879 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5880 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5881 appData.drawRepeats);
\r
5882 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5883 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5887 /* Check for rep-draws */
\r
5889 for(k = forwardMostMove-2;
\r
5890 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5891 epStatus[k] < EP_UNKNOWN &&
\r
5892 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5896 if (appData.debugMode) {
\r
5897 fprintf(debugFP, " loop\n");
\r
5900 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5902 if (appData.debugMode) {
\r
5903 fprintf(debugFP, "match\n");
\r
5906 /* compare castling rights */
\r
5907 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5908 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5909 rights++; /* King lost rights, while rook still had them */
\r
5910 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5911 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5912 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5913 rights++; /* but at least one rook lost them */
\r
5915 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5916 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5918 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5919 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5920 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5924 if (appData.debugMode) {
\r
5925 for(i=0; i<nrCastlingRights; i++)
\r
5926 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5929 if (appData.debugMode) {
\r
5930 fprintf(debugFP, " %d %d\n", rights, k);
\r
5933 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5934 && appData.drawRepeats > 1) {
\r
5935 /* adjudicate after user-specified nr of repeats */
\r
5936 SendToProgram("force\n", cps->other); // suppress reply
\r
5937 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5938 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5939 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5940 // [HGM] xiangqi: check for forbidden perpetuals
\r
5941 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5942 for(m=forwardMostMove; m>k; m-=2) {
\r
5943 if(MateTest(boards[m], PosFlags(m),
\r
5944 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5945 ourPerpetual = 0; // the current mover did not always check
\r
5946 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5947 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5948 hisPerpetual = 0; // the opponent did not always check
\r
5950 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5951 ourPerpetual, hisPerpetual);
\r
5952 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5953 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5954 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5957 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5958 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5959 // Now check for perpetual chases
\r
5960 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5961 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5962 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5963 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
\r
5964 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5965 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5968 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5969 break; // Abort repetition-checking loop.
\r
5971 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5973 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5976 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5977 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5981 /* Now we test for 50-move draws. Determine ply count */
\r
5982 count = forwardMostMove;
\r
5983 /* look for last irreversble move */
\r
5984 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5986 /* if we hit starting position, add initial plies */
\r
5987 if( count == backwardMostMove )
\r
5988 count -= initialRulePlies;
\r
5989 count = forwardMostMove - count;
\r
5991 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5992 /* this is used to judge if draw claims are legal */
\r
5993 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5994 SendToProgram("force\n", cps->other); // suppress reply
\r
5995 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5997 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6001 /* if draw offer is pending, treat it as a draw claim
\r
6002 * when draw condition present, to allow engines a way to
\r
6003 * claim draws before making their move to avoid a race
\r
6004 * condition occurring after their move
\r
6006 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6008 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6009 p = "Draw claim: 50-move rule";
\r
6010 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6011 p = "Draw claim: 3-fold repetition";
\r
6012 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6013 p = "Draw claim: insufficient mating material";
\r
6015 SendToProgram("force\n", cps->other); // suppress reply
\r
6016 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6017 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6024 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6025 SendToProgram("force\n", cps->other); // suppress reply
\r
6026 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6027 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6029 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6036 if (gameMode == TwoMachinesPlay) {
\r
6037 /* [HGM] relaying draw offers moved to after reception of move */
\r
6038 /* and interpreting offer as claim if it brings draw condition */
\r
6039 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6040 SendToProgram("draw\n", cps->other);
\r
6042 if (cps->other->sendTime) {
\r
6043 SendTimeRemaining(cps->other,
\r
6044 cps->other->twoMachinesColor[0] == 'w');
\r
6046 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6047 if (firstMove && !bookHit) {
\r
6048 firstMove = FALSE;
\r
6049 if (cps->other->useColors) {
\r
6050 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6052 SendToProgram("go\n", cps->other);
\r
6054 cps->other->maybeThinking = TRUE;
\r
6057 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6059 if (!pausing && appData.ringBellAfterMoves) {
\r
6064 * Reenable menu items that were disabled while
\r
6065 * machine was thinking
\r
6067 if (gameMode != TwoMachinesPlay)
\r
6068 SetUserThinkingEnables();
\r
6070 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6071 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6072 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6074 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6076 strcpy(bookMove, "move ");
\r
6077 strcat(bookMove, bookHit);
\r
6078 message = bookMove;
\r
6080 programStats.nodes = programStats.depth = programStats.time =
\r
6081 programStats.score = programStats.got_only_move = 0;
\r
6082 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6084 if(cps->lastPing != cps->lastPong) {
\r
6085 savedMessage = message; // args for deferred call
\r
6087 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6090 goto FakeBookMove;
\r
6096 /* Set special modes for chess engines. Later something general
\r
6097 * could be added here; for now there is just one kludge feature,
\r
6098 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6099 * when "xboard" is given as an interactive command.
\r
6101 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6102 cps->useSigint = FALSE;
\r
6103 cps->useSigterm = FALSE;
\r
6106 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6107 * want this, I was asked to put it in, and obliged.
\r
6109 if (!strncmp(message, "setboard ", 9)) {
\r
6110 Board initial_position; int i;
\r
6112 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6114 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6115 DisplayError(_("Bad FEN received from engine"), 0);
\r
6118 Reset(FALSE, FALSE);
\r
6119 CopyBoard(boards[0], initial_position);
\r
6120 initialRulePlies = FENrulePlies;
\r
6121 epStatus[0] = FENepStatus;
\r
6122 for( i=0; i<nrCastlingRights; i++ )
\r
6123 castlingRights[0][i] = FENcastlingRights[i];
\r
6124 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6125 else gameMode = MachinePlaysBlack;
\r
6126 DrawPosition(FALSE, boards[currentMove]);
\r
6132 * Look for communication commands
\r
6134 if (!strncmp(message, "telluser ", 9)) {
\r
6135 DisplayNote(message + 9);
\r
6138 if (!strncmp(message, "tellusererror ", 14)) {
\r
6139 DisplayError(message + 14, 0);
\r
6142 if (!strncmp(message, "tellopponent ", 13)) {
\r
6143 if (appData.icsActive) {
\r
6145 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6149 DisplayNote(message + 13);
\r
6153 if (!strncmp(message, "tellothers ", 11)) {
\r
6154 if (appData.icsActive) {
\r
6156 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6162 if (!strncmp(message, "tellall ", 8)) {
\r
6163 if (appData.icsActive) {
\r
6165 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6169 DisplayNote(message + 8);
\r
6173 if (strncmp(message, "warning", 7) == 0) {
\r
6174 /* Undocumented feature, use tellusererror in new code */
\r
6175 DisplayError(message, 0);
\r
6178 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6179 strcpy(realname, cps->tidy);
\r
6180 strcat(realname, " query");
\r
6181 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6184 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6185 * sent until we are logged on. Crafty kibitzes have been known to
\r
6186 * interfere with the login process.
\r
6189 if (!strncmp(message, "tellics ", 8)) {
\r
6190 SendToICS(message + 8);
\r
6194 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6195 SendToICS(ics_prefix);
\r
6196 SendToICS(message + 15);
\r
6200 /* The following are for backward compatibility only */
\r
6201 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6202 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6203 SendToICS(ics_prefix);
\r
6204 SendToICS(message);
\r
6209 if (strncmp(message, "feature ", 8) == 0) {
\r
6210 ParseFeatures(message+8, cps);
\r
6212 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6216 * If the move is illegal, cancel it and redraw the board.
\r
6217 * Also deal with other error cases. Matching is rather loose
\r
6218 * here to accommodate engines written before the spec.
\r
6220 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6221 strncmp(message, "Error", 5) == 0) {
\r
6222 if (StrStr(message, "name") ||
\r
6223 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6224 StrStr(message, "result") || StrStr(message, "board") ||
\r
6225 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6226 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6227 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6228 StrStr(message, "accepted")) {
\r
6231 if (StrStr(message, "protover")) {
\r
6232 /* Program is responding to input, so it's apparently done
\r
6233 initializing, and this error message indicates it is
\r
6234 protocol version 1. So we don't need to wait any longer
\r
6235 for it to initialize and send feature commands. */
\r
6236 FeatureDone(cps, 1);
\r
6237 cps->protocolVersion = 1;
\r
6240 cps->maybeThinking = FALSE;
\r
6242 if (StrStr(message, "draw")) {
\r
6243 /* Program doesn't have "draw" command */
\r
6244 cps->sendDrawOffers = 0;
\r
6247 if (cps->sendTime != 1 &&
\r
6248 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6249 /* Program apparently doesn't have "time" or "otim" command */
\r
6250 cps->sendTime = 0;
\r
6253 if (StrStr(message, "analyze")) {
\r
6254 cps->analysisSupport = FALSE;
\r
6255 cps->analyzing = FALSE;
\r
6256 Reset(FALSE, TRUE);
\r
6257 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6258 DisplayError(buf2, 0);
\r
6261 if (StrStr(message, "(no matching move)st")) {
\r
6262 /* Special kludge for GNU Chess 4 only */
\r
6263 cps->stKludge = TRUE;
\r
6264 SendTimeControl(cps, movesPerSession, timeControl,
\r
6265 timeIncrement, appData.searchDepth,
\r
6269 if (StrStr(message, "(no matching move)sd")) {
\r
6270 /* Special kludge for GNU Chess 4 only */
\r
6271 cps->sdKludge = TRUE;
\r
6272 SendTimeControl(cps, movesPerSession, timeControl,
\r
6273 timeIncrement, appData.searchDepth,
\r
6277 if (!StrStr(message, "llegal")) {
\r
6280 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6281 gameMode == IcsIdle) return;
\r
6282 if (forwardMostMove <= backwardMostMove) return;
\r
6284 /* Following removed: it caused a bug where a real illegal move
\r
6285 message in analyze mored would be ignored. */
\r
6286 if (cps == &first && programStats.ok_to_send == 0) {
\r
6287 /* Bogus message from Crafty responding to "." This filtering
\r
6288 can miss some of the bad messages, but fortunately the bug
\r
6289 is fixed in current Crafty versions, so it doesn't matter. */
\r
6293 if (pausing) PauseEvent();
\r
6294 if (gameMode == PlayFromGameFile) {
\r
6295 /* Stop reading this game file */
\r
6296 gameMode = EditGame;
\r
6299 currentMove = --forwardMostMove;
\r
6300 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6302 DisplayBothClocks();
\r
6303 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6304 parseList[currentMove], cps->which);
\r
6305 DisplayMoveError(buf1);
\r
6306 DrawPosition(FALSE, boards[currentMove]);
\r
6308 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6309 /* only passes fully legal moves */
\r
6310 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6311 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6312 "False illegal-move claim", GE_XBOARD );
\r
6316 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6317 /* Program has a broken "time" command that
\r
6318 outputs a string not ending in newline.
\r
6320 cps->sendTime = 0;
\r
6324 * If chess program startup fails, exit with an error message.
\r
6325 * Attempts to recover here are futile.
\r
6327 if ((StrStr(message, "unknown host") != NULL)
\r
6328 || (StrStr(message, "No remote directory") != NULL)
\r
6329 || (StrStr(message, "not found") != NULL)
\r
6330 || (StrStr(message, "No such file") != NULL)
\r
6331 || (StrStr(message, "can't alloc") != NULL)
\r
6332 || (StrStr(message, "Permission denied") != NULL)) {
\r
6334 cps->maybeThinking = FALSE;
\r
6335 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6336 cps->which, cps->program, cps->host, message);
\r
6337 RemoveInputSource(cps->isr);
\r
6338 DisplayFatalError(buf1, 0, 1);
\r
6343 * Look for hint output
\r
6345 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6346 if (cps == &first && hintRequested) {
\r
6347 hintRequested = FALSE;
\r
6348 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6349 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6350 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6351 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6352 fromY, fromX, toY, toX, promoChar, buf1);
\r
6353 sprintf(buf2, _("Hint: %s"), buf1);
\r
6354 DisplayInformation(buf2);
\r
6356 /* Hint move could not be parsed!? */
\r
6358 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6359 buf1, cps->which);
\r
6360 DisplayError(buf2, 0);
\r
6363 strcpy(lastHint, buf1);
\r
6369 * Ignore other messages if game is not in progress
\r
6371 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6372 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6375 * look for win, lose, draw, or draw offer
\r
6377 if (strncmp(message, "1-0", 3) == 0) {
\r
6378 char *p, *q, *r = "";
\r
6379 p = strchr(message, '{');
\r
6381 q = strchr(p, '}');
\r
6387 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6389 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6390 char *p, *q, *r = "";
\r
6391 p = strchr(message, '{');
\r
6393 q = strchr(p, '}');
\r
6399 /* Kludge for Arasan 4.1 bug */
\r
6400 if (strcmp(r, "Black resigns") == 0) {
\r
6401 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6404 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6406 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6407 char *p, *q, *r = "";
\r
6408 p = strchr(message, '{');
\r
6410 q = strchr(p, '}');
\r
6417 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6420 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6421 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6423 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6424 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6426 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6427 strncmp(message, "Black matches", 13) == 0 ) {
\r
6428 /* [HGM] ignore GNUShogi noises */
\r
6430 } else if (strncmp(message, "White", 5) == 0 &&
\r
6431 message[5] != '(' &&
\r
6432 StrStr(message, "Black") == NULL) {
\r
6433 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6435 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6436 message[5] != '(') {
\r
6437 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6439 } else if (strcmp(message, "resign") == 0 ||
\r
6440 strcmp(message, "computer resigns") == 0) {
\r
6441 switch (gameMode) {
\r
6442 case MachinePlaysBlack:
\r
6443 case IcsPlayingBlack:
\r
6444 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6446 case MachinePlaysWhite:
\r
6447 case IcsPlayingWhite:
\r
6448 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6450 case TwoMachinesPlay:
\r
6451 if (cps->twoMachinesColor[0] == 'w')
\r
6452 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6454 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6457 /* can't happen */
\r
6461 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6462 switch (gameMode) {
\r
6463 case MachinePlaysBlack:
\r
6464 case IcsPlayingBlack:
\r
6465 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6467 case MachinePlaysWhite:
\r
6468 case IcsPlayingWhite:
\r
6469 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6471 case TwoMachinesPlay:
\r
6472 if (cps->twoMachinesColor[0] == 'w')
\r
6473 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6475 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6478 /* can't happen */
\r
6482 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6483 switch (gameMode) {
\r
6484 case MachinePlaysBlack:
\r
6485 case IcsPlayingBlack:
\r
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6488 case MachinePlaysWhite:
\r
6489 case IcsPlayingWhite:
\r
6490 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6492 case TwoMachinesPlay:
\r
6493 if (cps->twoMachinesColor[0] == 'w')
\r
6494 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6496 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6499 /* can't happen */
\r
6503 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6504 if (WhiteOnMove(forwardMostMove)) {
\r
6505 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6507 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6510 } else if (strstr(message, "Draw") != NULL ||
\r
6511 strstr(message, "game is a draw") != NULL) {
\r
6512 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6514 } else if (strstr(message, "offer") != NULL &&
\r
6515 strstr(message, "draw") != NULL) {
\r
6517 if (appData.zippyPlay && first.initDone) {
\r
6518 /* Relay offer to ICS */
\r
6519 SendToICS(ics_prefix);
\r
6520 SendToICS("draw\n");
\r
6523 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6524 if (gameMode == TwoMachinesPlay) {
\r
6525 if (cps->other->offeredDraw) {
\r
6526 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6527 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6528 /* until after we also have move, to see if it is really claim */
\r
6532 if (cps->other->sendDrawOffers) {
\r
6533 SendToProgram("draw\n", cps->other);
\r
6537 } else if (gameMode == MachinePlaysWhite ||
\r
6538 gameMode == MachinePlaysBlack) {
\r
6539 if (userOfferedDraw) {
\r
6540 DisplayInformation(_("Machine accepts your draw offer"));
\r
6541 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6543 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6550 * Look for thinking output
\r
6552 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6553 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6555 int plylev, mvleft, mvtot, curscore, time;
\r
6556 char mvname[MOVE_LEN];
\r
6557 u64 nodes; // [DM]
\r
6559 int ignore = FALSE;
\r
6560 int prefixHint = FALSE;
\r
6561 mvname[0] = NULLCHAR;
\r
6563 switch (gameMode) {
\r
6564 case MachinePlaysBlack:
\r
6565 case IcsPlayingBlack:
\r
6566 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6568 case MachinePlaysWhite:
\r
6569 case IcsPlayingWhite:
\r
6570 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6575 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6576 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6578 case TwoMachinesPlay:
\r
6579 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6589 buf1[0] = NULLCHAR;
\r
6590 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6591 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6593 if (plyext != ' ' && plyext != '\t') {
\r
6597 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6598 if( cps->scoreIsAbsolute &&
\r
6599 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6601 curscore = -curscore;
\r
6605 programStats.depth = plylev;
\r
6606 programStats.nodes = nodes;
\r
6607 programStats.time = time;
\r
6608 programStats.score = curscore;
\r
6609 programStats.got_only_move = 0;
\r
6611 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6614 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6615 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6616 if(WhiteOnMove(forwardMostMove))
\r
6617 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6618 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6621 /* Buffer overflow protection */
\r
6622 if (buf1[0] != NULLCHAR) {
\r
6623 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6624 && appData.debugMode) {
\r
6626 "PV is too long; using the first %d bytes.\n",
\r
6627 sizeof(programStats.movelist) - 1);
\r
6630 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6632 sprintf(programStats.movelist, " no PV\n");
\r
6635 if (programStats.seen_stat) {
\r
6636 programStats.ok_to_send = 1;
\r
6639 if (strchr(programStats.movelist, '(') != NULL) {
\r
6640 programStats.line_is_book = 1;
\r
6641 programStats.nr_moves = 0;
\r
6642 programStats.moves_left = 0;
\r
6644 programStats.line_is_book = 0;
\r
6647 SendProgramStatsToFrontend( cps, &programStats );
\r
6650 [AS] Protect the thinkOutput buffer from overflow... this
\r
6651 is only useful if buf1 hasn't overflowed first!
\r
6653 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6655 (gameMode == TwoMachinesPlay ?
\r
6656 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6657 ((double) curscore) / 100.0,
\r
6658 prefixHint ? lastHint : "",
\r
6659 prefixHint ? " " : "" );
\r
6661 if( buf1[0] != NULLCHAR ) {
\r
6662 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6664 if( strlen(buf1) > max_len ) {
\r
6665 if( appData.debugMode) {
\r
6666 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6668 buf1[max_len+1] = '\0';
\r
6671 strcat( thinkOutput, buf1 );
\r
6674 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6675 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6676 DisplayMove(currentMove - 1);
\r
6677 DisplayAnalysis();
\r
6681 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6682 /* crafty (9.25+) says "(only move) <move>"
\r
6683 * if there is only 1 legal move
\r
6685 sscanf(p, "(only move) %s", buf1);
\r
6686 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6687 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6688 programStats.depth = 1;
\r
6689 programStats.nr_moves = 1;
\r
6690 programStats.moves_left = 1;
\r
6691 programStats.nodes = 1;
\r
6692 programStats.time = 1;
\r
6693 programStats.got_only_move = 1;
\r
6695 /* Not really, but we also use this member to
\r
6696 mean "line isn't going to change" (Crafty
\r
6697 isn't searching, so stats won't change) */
\r
6698 programStats.line_is_book = 1;
\r
6700 SendProgramStatsToFrontend( cps, &programStats );
\r
6702 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6703 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6704 DisplayMove(currentMove - 1);
\r
6705 DisplayAnalysis();
\r
6708 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6709 &time, &nodes, &plylev, &mvleft,
\r
6710 &mvtot, mvname) >= 5) {
\r
6711 /* The stat01: line is from Crafty (9.29+) in response
\r
6712 to the "." command */
\r
6713 programStats.seen_stat = 1;
\r
6714 cps->maybeThinking = TRUE;
\r
6716 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6719 programStats.depth = plylev;
\r
6720 programStats.time = time;
\r
6721 programStats.nodes = nodes;
\r
6722 programStats.moves_left = mvleft;
\r
6723 programStats.nr_moves = mvtot;
\r
6724 strcpy(programStats.move_name, mvname);
\r
6725 programStats.ok_to_send = 1;
\r
6726 programStats.movelist[0] = '\0';
\r
6728 SendProgramStatsToFrontend( cps, &programStats );
\r
6730 DisplayAnalysis();
\r
6733 } else if (strncmp(message,"++",2) == 0) {
\r
6734 /* Crafty 9.29+ outputs this */
\r
6735 programStats.got_fail = 2;
\r
6738 } else if (strncmp(message,"--",2) == 0) {
\r
6739 /* Crafty 9.29+ outputs this */
\r
6740 programStats.got_fail = 1;
\r
6743 } else if (thinkOutput[0] != NULLCHAR &&
\r
6744 strncmp(message, " ", 4) == 0) {
\r
6745 unsigned message_len;
\r
6748 while (*p && *p == ' ') p++;
\r
6750 message_len = strlen( p );
\r
6752 /* [AS] Avoid buffer overflow */
\r
6753 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6754 strcat(thinkOutput, " ");
\r
6755 strcat(thinkOutput, p);
\r
6758 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6759 strcat(programStats.movelist, " ");
\r
6760 strcat(programStats.movelist, p);
\r
6763 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6764 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6765 DisplayMove(currentMove - 1);
\r
6766 DisplayAnalysis();
\r
6772 buf1[0] = NULLCHAR;
\r
6774 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6775 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6777 ChessProgramStats cpstats;
\r
6779 if (plyext != ' ' && plyext != '\t') {
\r
6783 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6784 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6785 curscore = -curscore;
\r
6788 cpstats.depth = plylev;
\r
6789 cpstats.nodes = nodes;
\r
6790 cpstats.time = time;
\r
6791 cpstats.score = curscore;
\r
6792 cpstats.got_only_move = 0;
\r
6793 cpstats.movelist[0] = '\0';
\r
6795 if (buf1[0] != NULLCHAR) {
\r
6796 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6799 cpstats.ok_to_send = 0;
\r
6800 cpstats.line_is_book = 0;
\r
6801 cpstats.nr_moves = 0;
\r
6802 cpstats.moves_left = 0;
\r
6804 SendProgramStatsToFrontend( cps, &cpstats );
\r
6811 /* Parse a game score from the character string "game", and
\r
6812 record it as the history of the current game. The game
\r
6813 score is NOT assumed to start from the standard position.
\r
6814 The display is not updated in any way.
\r
6817 ParseGameHistory(game)
\r
6820 ChessMove moveType;
\r
6821 int fromX, fromY, toX, toY, boardIndex;
\r
6824 char buf[MSG_SIZ];
\r
6826 if (appData.debugMode)
\r
6827 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6829 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6830 gameInfo.site = StrSave(appData.icsHost);
\r
6831 gameInfo.date = PGNDate();
\r
6832 gameInfo.round = StrSave("-");
\r
6834 /* Parse out names of players */
\r
6835 while (*game == ' ') game++;
\r
6837 while (*game != ' ') *p++ = *game++;
\r
6839 gameInfo.white = StrSave(buf);
\r
6840 while (*game == ' ') game++;
\r
6842 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6844 gameInfo.black = StrSave(buf);
\r
6847 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6850 yyboardindex = boardIndex;
\r
6851 moveType = (ChessMove) yylex();
\r
6852 switch (moveType) {
\r
6853 case IllegalMove: /* maybe suicide chess, etc. */
\r
6854 if (appData.debugMode) {
\r
6855 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6856 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6857 setbuf(debugFP, NULL);
\r
6859 case WhitePromotionChancellor:
\r
6860 case BlackPromotionChancellor:
\r
6861 case WhitePromotionArchbishop:
\r
6862 case BlackPromotionArchbishop:
\r
6863 case WhitePromotionQueen:
\r
6864 case BlackPromotionQueen:
\r
6865 case WhitePromotionRook:
\r
6866 case BlackPromotionRook:
\r
6867 case WhitePromotionBishop:
\r
6868 case BlackPromotionBishop:
\r
6869 case WhitePromotionKnight:
\r
6870 case BlackPromotionKnight:
\r
6871 case WhitePromotionKing:
\r
6872 case BlackPromotionKing:
\r
6874 case WhiteCapturesEnPassant:
\r
6875 case BlackCapturesEnPassant:
\r
6876 case WhiteKingSideCastle:
\r
6877 case WhiteQueenSideCastle:
\r
6878 case BlackKingSideCastle:
\r
6879 case BlackQueenSideCastle:
\r
6880 case WhiteKingSideCastleWild:
\r
6881 case WhiteQueenSideCastleWild:
\r
6882 case BlackKingSideCastleWild:
\r
6883 case BlackQueenSideCastleWild:
\r
6885 case WhiteHSideCastleFR:
\r
6886 case WhiteASideCastleFR:
\r
6887 case BlackHSideCastleFR:
\r
6888 case BlackASideCastleFR:
\r
6890 fromX = currentMoveString[0] - AAA;
\r
6891 fromY = currentMoveString[1] - ONE;
\r
6892 toX = currentMoveString[2] - AAA;
\r
6893 toY = currentMoveString[3] - ONE;
\r
6894 promoChar = currentMoveString[4];
\r
6898 fromX = moveType == WhiteDrop ?
\r
6899 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6900 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6901 fromY = DROP_RANK;
\r
6902 toX = currentMoveString[2] - AAA;
\r
6903 toY = currentMoveString[3] - ONE;
\r
6904 promoChar = NULLCHAR;
\r
6906 case AmbiguousMove:
\r
6908 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6909 if (appData.debugMode) {
\r
6910 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6911 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6912 setbuf(debugFP, NULL);
\r
6914 DisplayError(buf, 0);
\r
6916 case ImpossibleMove:
\r
6918 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6919 if (appData.debugMode) {
\r
6920 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6921 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6922 setbuf(debugFP, NULL);
\r
6924 DisplayError(buf, 0);
\r
6926 case (ChessMove) 0: /* end of file */
\r
6927 if (boardIndex < backwardMostMove) {
\r
6928 /* Oops, gap. How did that happen? */
\r
6929 DisplayError(_("Gap in move list"), 0);
\r
6932 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6933 if (boardIndex > forwardMostMove) {
\r
6934 forwardMostMove = boardIndex;
\r
6938 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6939 strcat(parseList[boardIndex-1], " ");
\r
6940 strcat(parseList[boardIndex-1], yy_text);
\r
6952 case GameUnfinished:
\r
6953 if (gameMode == IcsExamining) {
\r
6954 if (boardIndex < backwardMostMove) {
\r
6955 /* Oops, gap. How did that happen? */
\r
6958 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6961 gameInfo.result = moveType;
\r
6962 p = strchr(yy_text, '{');
\r
6963 if (p == NULL) p = strchr(yy_text, '(');
\r
6966 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6968 q = strchr(p, *p == '{' ? '}' : ')');
\r
6969 if (q != NULL) *q = NULLCHAR;
\r
6972 gameInfo.resultDetails = StrSave(p);
\r
6975 if (boardIndex >= forwardMostMove &&
\r
6976 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6977 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6980 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6981 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6982 parseList[boardIndex]);
\r
6983 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6984 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
\r
6985 /* currentMoveString is set as a side-effect of yylex */
\r
6986 strcpy(moveList[boardIndex], currentMoveString);
\r
6987 strcat(moveList[boardIndex], "\n");
\r
6989 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
\r
6990 castlingRights[boardIndex], &epStatus[boardIndex]);
\r
6991 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6992 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6994 case MT_STALEMATE:
\r
6998 if(gameInfo.variant != VariantShogi)
\r
6999 strcat(parseList[boardIndex - 1], "+");
\r
7001 case MT_CHECKMATE:
\r
7002 case MT_STAINMATE:
\r
7003 strcat(parseList[boardIndex - 1], "#");
\r
7010 /* Apply a move to the given board */
\r
7012 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
\r
7013 int fromX, fromY, toX, toY;
\r
7019 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7021 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7022 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
\r
7025 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7029 if( board[toY][toX] != EmptySquare )
\r
7030 *ep = EP_CAPTURE;
\r
7032 if( board[fromY][fromX] == WhitePawn ) {
\r
7033 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7034 *ep = EP_PAWN_MOVE;
\r
7035 if( toY-fromY==2) {
\r
7036 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7037 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7038 *ep = toX | berolina;
\r
7039 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7040 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7044 if( board[fromY][fromX] == BlackPawn ) {
\r
7045 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7046 *ep = EP_PAWN_MOVE;
\r
7047 if( toY-fromY== -2) {
\r
7048 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7049 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7050 *ep = toX | berolina;
\r
7051 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7052 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7057 for(i=0; i<nrCastlingRights; i++) {
\r
7058 if(castling[i] == fromX && castlingRank[i] == fromY ||
\r
7059 castling[i] == toX && castlingRank[i] == toY
\r
7060 ) castling[i] = -1; // revoke for moved or captured piece
\r
7065 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7066 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7067 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7069 if (fromX == toX && fromY == toY) return;
\r
7071 if (fromY == DROP_RANK) {
\r
7072 /* must be first */
\r
7073 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7075 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7076 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7077 if(gameInfo.variant == VariantKnightmate)
\r
7078 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7080 /* Code added by Tord: */
\r
7081 /* FRC castling assumed when king captures friendly rook. */
\r
7082 if (board[fromY][fromX] == WhiteKing &&
\r
7083 board[toY][toX] == WhiteRook) {
\r
7084 board[fromY][fromX] = EmptySquare;
\r
7085 board[toY][toX] = EmptySquare;
\r
7087 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7089 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7091 } else if (board[fromY][fromX] == BlackKing &&
\r
7092 board[toY][toX] == BlackRook) {
\r
7093 board[fromY][fromX] = EmptySquare;
\r
7094 board[toY][toX] = EmptySquare;
\r
7096 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7098 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7100 /* End of code added by Tord */
\r
7102 } else if (board[fromY][fromX] == king
\r
7103 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7104 && toY == fromY && toX > fromX+1) {
\r
7105 board[fromY][fromX] = EmptySquare;
\r
7106 board[toY][toX] = king;
\r
7107 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7108 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7109 } else if (board[fromY][fromX] == king
\r
7110 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7111 && toY == fromY && toX < fromX-1) {
\r
7112 board[fromY][fromX] = EmptySquare;
\r
7113 board[toY][toX] = king;
\r
7114 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7115 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7116 } else if (board[fromY][fromX] == WhitePawn
\r
7117 && toY == BOARD_HEIGHT-1
\r
7118 && gameInfo.variant != VariantXiangqi
\r
7120 /* white pawn promotion */
\r
7121 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7122 if (board[toY][toX] == EmptySquare) {
\r
7123 board[toY][toX] = WhiteQueen;
\r
7125 if(gameInfo.variant==VariantBughouse ||
\r
7126 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7127 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7128 board[fromY][fromX] = EmptySquare;
\r
7129 } else if ((fromY == BOARD_HEIGHT-4)
\r
7131 && gameInfo.variant != VariantXiangqi
\r
7132 && gameInfo.variant != VariantBerolina
\r
7133 && (board[fromY][fromX] == WhitePawn)
\r
7134 && (board[toY][toX] == EmptySquare)) {
\r
7135 board[fromY][fromX] = EmptySquare;
\r
7136 board[toY][toX] = WhitePawn;
\r
7137 captured = board[toY - 1][toX];
\r
7138 board[toY - 1][toX] = EmptySquare;
\r
7139 } else if ((fromY == BOARD_HEIGHT-4)
\r
7141 && gameInfo.variant == VariantBerolina
\r
7142 && (board[fromY][fromX] == WhitePawn)
\r
7143 && (board[toY][toX] == EmptySquare)) {
\r
7144 board[fromY][fromX] = EmptySquare;
\r
7145 board[toY][toX] = WhitePawn;
\r
7146 if(oldEP & EP_BEROLIN_A) {
\r
7147 captured = board[fromY][fromX-1];
\r
7148 board[fromY][fromX-1] = EmptySquare;
\r
7149 }else{ captured = board[fromY][fromX+1];
\r
7150 board[fromY][fromX+1] = EmptySquare;
\r
7152 } else if (board[fromY][fromX] == king
\r
7153 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7154 && toY == fromY && toX > fromX+1) {
\r
7155 board[fromY][fromX] = EmptySquare;
\r
7156 board[toY][toX] = king;
\r
7157 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7158 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7159 } else if (board[fromY][fromX] == king
\r
7160 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7161 && toY == fromY && toX < fromX-1) {
\r
7162 board[fromY][fromX] = EmptySquare;
\r
7163 board[toY][toX] = king;
\r
7164 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7165 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7166 } else if (fromY == 7 && fromX == 3
\r
7167 && board[fromY][fromX] == BlackKing
\r
7168 && toY == 7 && toX == 5) {
\r
7169 board[fromY][fromX] = EmptySquare;
\r
7170 board[toY][toX] = BlackKing;
\r
7171 board[fromY][7] = EmptySquare;
\r
7172 board[toY][4] = BlackRook;
\r
7173 } else if (fromY == 7 && fromX == 3
\r
7174 && board[fromY][fromX] == BlackKing
\r
7175 && toY == 7 && toX == 1) {
\r
7176 board[fromY][fromX] = EmptySquare;
\r
7177 board[toY][toX] = BlackKing;
\r
7178 board[fromY][0] = EmptySquare;
\r
7179 board[toY][2] = BlackRook;
\r
7180 } else if (board[fromY][fromX] == BlackPawn
\r
7182 && gameInfo.variant != VariantXiangqi
\r
7184 /* black pawn promotion */
\r
7185 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7186 if (board[0][toX] == EmptySquare) {
\r
7187 board[0][toX] = BlackQueen;
\r
7189 if(gameInfo.variant==VariantBughouse ||
\r
7190 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7191 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7192 board[fromY][fromX] = EmptySquare;
\r
7193 } else if ((fromY == 3)
\r
7195 && gameInfo.variant != VariantXiangqi
\r
7196 && gameInfo.variant != VariantBerolina
\r
7197 && (board[fromY][fromX] == BlackPawn)
\r
7198 && (board[toY][toX] == EmptySquare)) {
\r
7199 board[fromY][fromX] = EmptySquare;
\r
7200 board[toY][toX] = BlackPawn;
\r
7201 captured = board[toY + 1][toX];
\r
7202 board[toY + 1][toX] = EmptySquare;
\r
7203 } else if ((fromY == 3)
\r
7205 && gameInfo.variant == VariantBerolina
\r
7206 && (board[fromY][fromX] == BlackPawn)
\r
7207 && (board[toY][toX] == EmptySquare)) {
\r
7208 board[fromY][fromX] = EmptySquare;
\r
7209 board[toY][toX] = BlackPawn;
\r
7210 if(oldEP & EP_BEROLIN_A) {
\r
7211 captured = board[fromY][fromX-1];
\r
7212 board[fromY][fromX-1] = EmptySquare;
\r
7213 }else{ captured = board[fromY][fromX+1];
\r
7214 board[fromY][fromX+1] = EmptySquare;
\r
7217 board[toY][toX] = board[fromY][fromX];
\r
7218 board[fromY][fromX] = EmptySquare;
\r
7221 /* [HGM] now we promote for Shogi, if needed */
\r
7222 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7223 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7226 if (gameInfo.holdingsWidth != 0) {
\r
7228 /* !!A lot more code needs to be written to support holdings */
\r
7229 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7230 /* penultimate board files, so they are automaticlly stored */
\r
7231 /* in the game history. */
\r
7232 if (fromY == DROP_RANK) {
\r
7233 /* Delete from holdings, by decreasing count */
\r
7234 /* and erasing image if necessary */
\r
7236 if(p < (int) BlackPawn) { /* white drop */
\r
7237 p -= (int)WhitePawn;
\r
7238 if(p >= gameInfo.holdingsSize) p = 0;
\r
7239 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7240 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7241 } else { /* black drop */
\r
7242 p -= (int)BlackPawn;
\r
7243 if(p >= gameInfo.holdingsSize) p = 0;
\r
7244 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7245 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7248 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7249 && gameInfo.variant != VariantBughouse ) {
\r
7250 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7251 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7252 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7253 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7255 p = (int) captured;
\r
7256 if (p >= (int) BlackPawn) {
\r
7257 p -= (int)BlackPawn;
\r
7258 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7259 /* in Shogi restore piece to its original first */
\r
7260 captured = (ChessSquare) (DEMOTED captured);
\r
7263 p = PieceToNumber((ChessSquare)p);
\r
7264 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7265 board[p][BOARD_WIDTH-2]++;
\r
7266 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7268 p -= (int)WhitePawn;
\r
7269 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7270 captured = (ChessSquare) (DEMOTED captured);
\r
7273 p = PieceToNumber((ChessSquare)p);
\r
7274 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7275 board[BOARD_HEIGHT-1-p][1]++;
\r
7276 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7280 } else if (gameInfo.variant == VariantAtomic) {
\r
7281 if (captured != EmptySquare) {
\r
7283 for (y = toY-1; y <= toY+1; y++) {
\r
7284 for (x = toX-1; x <= toX+1; x++) {
\r
7285 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7286 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7287 board[y][x] = EmptySquare;
\r
7291 board[toY][toX] = EmptySquare;
\r
7294 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7295 /* [HGM] Shogi promotions */
\r
7296 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7299 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7300 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
7301 // [HGM] superchess: take promotion piece out of holdings
\r
7302 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
7303 if((int)piece < (int)BlackPawn) { // determine stm from piece color
\r
7304 if(!--board[k][BOARD_WIDTH-2])
\r
7305 board[k][BOARD_WIDTH-1] = EmptySquare;
\r
7307 if(!--board[BOARD_HEIGHT-1-k][1])
\r
7308 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
7314 /* Updates forwardMostMove */
\r
7316 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7317 int fromX, fromY, toX, toY;
\r
7320 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7322 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7323 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7324 piece = boards[forwardMostMove][fromY][fromX];
\r
7325 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7326 if(gameInfo.variant == VariantKnightmate)
\r
7327 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7328 if(forwardMostMove == 0) {
\r
7329 if(blackPlaysFirst)
\r
7330 fprintf(serverMoves, "%s;", second.tidy);
\r
7331 fprintf(serverMoves, "%s;", first.tidy);
\r
7332 if(!blackPlaysFirst)
\r
7333 fprintf(serverMoves, "%s;", second.tidy);
\r
7334 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7335 lastLoadFlag = loadFlag;
\r
7336 // print base move
\r
7337 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7338 // print castling suffix
\r
7339 if( toY == fromY && piece == king ) {
\r
7341 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7343 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7346 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7347 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7348 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7350 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7351 // promotion suffix
\r
7352 if(promoChar != NULLCHAR)
\r
7353 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7355 fprintf(serverMoves, "/%d/%d",
\r
7356 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7357 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7358 else timeLeft = blackTimeRemaining/1000;
\r
7359 fprintf(serverMoves, "/%d", timeLeft);
\r
7361 fflush(serverMoves);
\r
7364 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7370 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7371 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7372 if (commentList[forwardMostMove+1] != NULL) {
\r
7373 free(commentList[forwardMostMove+1]);
\r
7374 commentList[forwardMostMove+1] = NULL;
\r
7376 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7377 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
\r
7378 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
\r
7379 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
\r
7380 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7381 gameInfo.result = GameUnfinished;
\r
7382 if (gameInfo.resultDetails != NULL) {
\r
7383 free(gameInfo.resultDetails);
\r
7384 gameInfo.resultDetails = NULL;
\r
7386 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7387 moveList[forwardMostMove - 1]);
\r
7388 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7389 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7390 fromY, fromX, toY, toX, promoChar,
\r
7391 parseList[forwardMostMove - 1]);
\r
7392 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7393 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7394 castlingRights[forwardMostMove]) ) {
\r
7396 case MT_STALEMATE:
\r
7400 if(gameInfo.variant != VariantShogi)
\r
7401 strcat(parseList[forwardMostMove - 1], "+");
\r
7403 case MT_CHECKMATE:
\r
7404 case MT_STAINMATE:
\r
7405 strcat(parseList[forwardMostMove - 1], "#");
\r
7408 if (appData.debugMode) {
\r
7409 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7414 /* Updates currentMove if not pausing */
\r
7416 ShowMove(fromX, fromY, toX, toY)
\r
7418 int instant = (gameMode == PlayFromGameFile) ?
\r
7419 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7420 if(appData.noGUI) return;
\r
7421 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7423 if (forwardMostMove == currentMove + 1) {
\r
7424 AnimateMove(boards[forwardMostMove - 1],
\r
7425 fromX, fromY, toX, toY);
\r
7427 if (appData.highlightLastMove) {
\r
7428 SetHighlights(fromX, fromY, toX, toY);
\r
7431 currentMove = forwardMostMove;
\r
7434 if (instant) return;
\r
7436 DisplayMove(currentMove - 1);
\r
7437 DrawPosition(FALSE, boards[currentMove]);
\r
7438 DisplayBothClocks();
\r
7439 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7442 void SendEgtPath(ChessProgramState *cps)
\r
7443 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7444 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7446 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7449 char c, *q = name+1, *r, *s;
\r
7451 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7452 while(*p && *p != ',') *q++ = *p++;
\r
7453 *q++ = ':'; *q = 0;
\r
7454 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7455 strcmp(name, ",nalimov:") == 0 ) {
\r
7456 // take nalimov path from the menu-changeable option first, if it is defined
\r
7457 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7458 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7460 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7461 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7462 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7463 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7464 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7465 c = *r; *r = 0; // temporarily null-terminate path info
\r
7466 *--q = 0; // strip of trailig ':' from name
\r
7467 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7469 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7471 if(*p == ',') p++; // read away comma to position for next format name
\r
7476 InitChessProgram(cps, setup)
\r
7477 ChessProgramState *cps;
\r
7478 int setup; /* [HGM] needed to setup FRC opening position */
\r
7480 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7481 if (appData.noChessProgram) return;
\r
7482 hintRequested = FALSE;
\r
7483 bookRequested = FALSE;
\r
7485 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7486 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7487 if(cps->memSize) { /* [HGM] memory */
\r
7488 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7489 SendToProgram(buf, cps);
\r
7491 SendEgtPath(cps); /* [HGM] EGT */
\r
7492 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7493 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7494 SendToProgram(buf, cps);
\r
7497 SendToProgram(cps->initString, cps);
\r
7498 if (gameInfo.variant != VariantNormal &&
\r
7499 gameInfo.variant != VariantLoadable
\r
7500 /* [HGM] also send variant if board size non-standard */
\r
7501 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7503 char *v = VariantName(gameInfo.variant);
\r
7504 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7505 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7506 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7507 DisplayFatalError(buf, 0, 1);
\r
7511 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7512 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7513 if( gameInfo.variant == VariantXiangqi )
\r
7514 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7515 if( gameInfo.variant == VariantShogi )
\r
7516 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7517 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7518 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7519 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7520 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7521 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7522 if( gameInfo.variant == VariantCourier )
\r
7523 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7524 if( gameInfo.variant == VariantSuper )
\r
7525 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7526 if( gameInfo.variant == VariantGreat )
\r
7527 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7530 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7531 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7532 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7533 if(StrStr(cps->variants, b) == NULL) {
\r
7534 // specific sized variant not known, check if general sizing allowed
\r
7535 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7536 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7537 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7538 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7539 DisplayFatalError(buf, 0, 1);
\r
7542 /* [HGM] here we really should compare with the maximum supported board size */
\r
7545 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7546 sprintf(buf, "variant %s\n", b);
\r
7547 SendToProgram(buf, cps);
\r
7549 currentlyInitializedVariant = gameInfo.variant;
\r
7551 /* [HGM] send opening position in FRC to first engine */
\r
7553 SendToProgram("force\n", cps);
\r
7554 SendBoard(cps, 0);
\r
7555 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7556 setboardSpoiledMachineBlack = 1;
\r
7559 if (cps->sendICS) {
\r
7560 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7561 SendToProgram(buf, cps);
\r
7563 cps->maybeThinking = FALSE;
\r
7564 cps->offeredDraw = 0;
\r
7565 if (!appData.icsActive) {
\r
7566 SendTimeControl(cps, movesPerSession, timeControl,
\r
7567 timeIncrement, appData.searchDepth,
\r
7570 if (appData.showThinking
\r
7571 // [HGM] thinking: four options require thinking output to be sent
\r
7572 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7574 SendToProgram("post\n", cps);
\r
7576 SendToProgram("hard\n", cps);
\r
7577 if (!appData.ponderNextMove) {
\r
7578 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7579 it without being sure what state we are in first. "hard"
\r
7580 is not a toggle, so that one is OK.
\r
7582 SendToProgram("easy\n", cps);
\r
7584 if (cps->usePing) {
\r
7585 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7586 SendToProgram(buf, cps);
\r
7588 cps->initDone = TRUE;
\r
7593 StartChessProgram(cps)
\r
7594 ChessProgramState *cps;
\r
7596 char buf[MSG_SIZ];
\r
7599 if (appData.noChessProgram) return;
\r
7600 cps->initDone = FALSE;
\r
7602 if (strcmp(cps->host, "localhost") == 0) {
\r
7603 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7604 } else if (*appData.remoteShell == NULLCHAR) {
\r
7605 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7607 if (*appData.remoteUser == NULLCHAR) {
\r
7608 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7611 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7612 cps->host, appData.remoteUser, cps->program);
\r
7614 err = StartChildProcess(buf, "", &cps->pr);
\r
7618 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7619 DisplayFatalError(buf, err, 1);
\r
7625 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7626 if (cps->protocolVersion > 1) {
\r
7627 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7628 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7629 cps->comboCnt = 0; // and values of combo boxes
\r
7630 SendToProgram(buf, cps);
\r
7632 SendToProgram("xboard\n", cps);
\r
7638 TwoMachinesEventIfReady P((void))
\r
7640 if (first.lastPing != first.lastPong) {
\r
7641 DisplayMessage("", _("Waiting for first chess program"));
\r
7642 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7645 if (second.lastPing != second.lastPong) {
\r
7646 DisplayMessage("", _("Waiting for second chess program"));
\r
7647 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7651 TwoMachinesEvent();
\r
7655 NextMatchGame P((void))
\r
7657 int index; /* [HGM] autoinc: step lod index during match */
\r
7658 Reset(FALSE, TRUE);
\r
7659 if (*appData.loadGameFile != NULLCHAR) {
\r
7660 index = appData.loadGameIndex;
\r
7661 if(index < 0) { // [HGM] autoinc
\r
7662 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7663 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7665 LoadGameFromFile(appData.loadGameFile,
\r
7667 appData.loadGameFile, FALSE);
\r
7668 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7669 index = appData.loadPositionIndex;
\r
7670 if(index < 0) { // [HGM] autoinc
\r
7671 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7672 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7674 LoadPositionFromFile(appData.loadPositionFile,
\r
7676 appData.loadPositionFile);
\r
7678 TwoMachinesEventIfReady();
\r
7681 void UserAdjudicationEvent( int result )
\r
7683 ChessMove gameResult = GameIsDrawn;
\r
7685 if( result > 0 ) {
\r
7686 gameResult = WhiteWins;
\r
7688 else if( result < 0 ) {
\r
7689 gameResult = BlackWins;
\r
7692 if( gameMode == TwoMachinesPlay ) {
\r
7693 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7699 GameEnds(result, resultDetails, whosays)
\r
7701 char *resultDetails;
\r
7704 GameMode nextGameMode;
\r
7706 char buf[MSG_SIZ];
\r
7708 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7711 if (appData.debugMode) {
\r
7712 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7713 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7716 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7717 /* If we are playing on ICS, the server decides when the
\r
7718 game is over, but the engine can offer to draw, claim
\r
7719 a draw, or resign.
\r
7722 if (appData.zippyPlay && first.initDone) {
\r
7723 if (result == GameIsDrawn) {
\r
7724 /* In case draw still needs to be claimed */
\r
7725 SendToICS(ics_prefix);
\r
7726 SendToICS("draw\n");
\r
7727 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7728 SendToICS(ics_prefix);
\r
7729 SendToICS("resign\n");
\r
7733 endingGame = 0; /* [HGM] crash */
\r
7737 /* If we're loading the game from a file, stop */
\r
7738 if (whosays == GE_FILE) {
\r
7739 (void) StopLoadGameTimer();
\r
7740 gameFileFP = NULL;
\r
7743 /* Cancel draw offers */
\r
7744 first.offeredDraw = second.offeredDraw = 0;
\r
7746 /* If this is an ICS game, only ICS can really say it's done;
\r
7747 if not, anyone can. */
\r
7748 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7749 gameMode == IcsPlayingBlack ||
\r
7750 gameMode == IcsObserving ||
\r
7751 gameMode == IcsExamining);
\r
7753 if (!isIcsGame || whosays == GE_ICS) {
\r
7754 /* OK -- not an ICS game, or ICS said it was done */
\r
7756 if (!isIcsGame && !appData.noChessProgram)
\r
7757 SetUserThinkingEnables();
\r
7759 /* [HGM] if a machine claims the game end we verify this claim */
\r
7760 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7761 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7763 ChessMove trueResult = (ChessMove) -1;
\r
7765 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7766 first.twoMachinesColor[0] :
\r
7767 second.twoMachinesColor[0] ;
\r
7769 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7770 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7771 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7772 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
\r
7774 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
\r
7775 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7776 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7778 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
\r
7779 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7782 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7783 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7784 || gameInfo.variant == VariantGreat) &&
\r
7785 (result == WhiteWins && claimer == 'w' ||
\r
7786 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7787 if (appData.debugMode) {
\r
7788 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7789 result, epStatus[forwardMostMove], forwardMostMove);
\r
7791 if(result != trueResult) {
\r
7792 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7793 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7794 resultDetails = buf;
\r
7797 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7798 && (forwardMostMove <= backwardMostMove ||
\r
7799 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7800 (claimer=='b')==(forwardMostMove&1))
\r
7802 /* [HGM] verify: draws that were not flagged are false claims */
\r
7803 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7804 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7805 resultDetails = buf;
\r
7807 /* (Claiming a loss is accepted no questions asked!) */
\r
7809 /* [HGM] bare: don't allow bare King to win */
\r
7810 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7811 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7812 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7813 && result != GameIsDrawn)
\r
7814 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7815 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7816 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7817 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7819 if (appData.debugMode) {
\r
7820 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7821 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7824 result = GameIsDrawn;
\r
7825 sprintf(buf, "%s but bare king", resultDetails);
\r
7826 resultDetails = buf;
\r
7832 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7833 if(result==WhiteWins) c = '+';
\r
7834 if(result==BlackWins) c = '-';
\r
7835 if(resultDetails != NULL)
\r
7836 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7838 if (resultDetails != NULL) {
\r
7839 gameInfo.result = result;
\r
7840 gameInfo.resultDetails = StrSave(resultDetails);
\r
7842 /* display last move only if game was not loaded from file */
\r
7843 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7844 DisplayMove(currentMove - 1);
\r
7846 if (forwardMostMove != 0) {
\r
7847 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7848 if (*appData.saveGameFile != NULLCHAR) {
\r
7849 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7850 } else if (appData.autoSaveGames) {
\r
7853 if (*appData.savePositionFile != NULLCHAR) {
\r
7854 SavePositionToFile(appData.savePositionFile);
\r
7859 /* Tell program how game ended in case it is learning */
\r
7860 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7861 /* engine died and we got here through time loss. In that */
\r
7862 /* case we will get a fatal error writing the pipe, which */
\r
7863 /* would otherwise lose us the PGN. */
\r
7864 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7865 /* output during GameEnds should never be fatal anymore */
\r
7866 if (gameMode == MachinePlaysWhite ||
\r
7867 gameMode == MachinePlaysBlack ||
\r
7868 gameMode == TwoMachinesPlay ||
\r
7869 gameMode == IcsPlayingWhite ||
\r
7870 gameMode == IcsPlayingBlack ||
\r
7871 gameMode == BeginningOfGame) {
\r
7872 char buf[MSG_SIZ];
\r
7873 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7875 if (first.pr != NoProc) {
\r
7876 SendToProgram(buf, &first);
\r
7878 if (second.pr != NoProc &&
\r
7879 gameMode == TwoMachinesPlay) {
\r
7880 SendToProgram(buf, &second);
\r
7885 if (appData.icsActive) {
\r
7886 if (appData.quietPlay &&
\r
7887 (gameMode == IcsPlayingWhite ||
\r
7888 gameMode == IcsPlayingBlack)) {
\r
7889 SendToICS(ics_prefix);
\r
7890 SendToICS("set shout 1\n");
\r
7892 nextGameMode = IcsIdle;
\r
7893 ics_user_moved = FALSE;
\r
7894 /* clean up premove. It's ugly when the game has ended and the
\r
7895 * premove highlights are still on the board.
\r
7898 gotPremove = FALSE;
\r
7899 ClearPremoveHighlights();
\r
7900 DrawPosition(FALSE, boards[currentMove]);
\r
7902 if (whosays == GE_ICS) {
\r
7905 if (gameMode == IcsPlayingWhite)
\r
7906 PlayIcsWinSound();
\r
7907 else if(gameMode == IcsPlayingBlack)
\r
7908 PlayIcsLossSound();
\r
7911 if (gameMode == IcsPlayingBlack)
\r
7912 PlayIcsWinSound();
\r
7913 else if(gameMode == IcsPlayingWhite)
\r
7914 PlayIcsLossSound();
\r
7917 PlayIcsDrawSound();
\r
7920 PlayIcsUnfinishedSound();
\r
7923 } else if (gameMode == EditGame ||
\r
7924 gameMode == PlayFromGameFile ||
\r
7925 gameMode == AnalyzeMode ||
\r
7926 gameMode == AnalyzeFile) {
\r
7927 nextGameMode = gameMode;
\r
7929 nextGameMode = EndOfGame;
\r
7934 nextGameMode = gameMode;
\r
7937 if (appData.noChessProgram) {
\r
7938 gameMode = nextGameMode;
\r
7940 endingGame = 0; /* [HGM] crash */
\r
7944 if (first.reuse) {
\r
7945 /* Put first chess program into idle state */
\r
7946 if (first.pr != NoProc &&
\r
7947 (gameMode == MachinePlaysWhite ||
\r
7948 gameMode == MachinePlaysBlack ||
\r
7949 gameMode == TwoMachinesPlay ||
\r
7950 gameMode == IcsPlayingWhite ||
\r
7951 gameMode == IcsPlayingBlack ||
\r
7952 gameMode == BeginningOfGame)) {
\r
7953 SendToProgram("force\n", &first);
\r
7954 if (first.usePing) {
\r
7955 char buf[MSG_SIZ];
\r
7956 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7957 SendToProgram(buf, &first);
\r
7960 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7961 /* Kill off first chess program */
\r
7962 if (first.isr != NULL)
\r
7963 RemoveInputSource(first.isr);
\r
7966 if (first.pr != NoProc) {
\r
7967 ExitAnalyzeMode();
\r
7968 DoSleep( appData.delayBeforeQuit );
\r
7969 SendToProgram("quit\n", &first);
\r
7970 DoSleep( appData.delayAfterQuit );
\r
7971 DestroyChildProcess(first.pr, first.useSigterm);
\r
7973 first.pr = NoProc;
\r
7975 if (second.reuse) {
\r
7976 /* Put second chess program into idle state */
\r
7977 if (second.pr != NoProc &&
\r
7978 gameMode == TwoMachinesPlay) {
\r
7979 SendToProgram("force\n", &second);
\r
7980 if (second.usePing) {
\r
7981 char buf[MSG_SIZ];
\r
7982 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7983 SendToProgram(buf, &second);
\r
7986 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7987 /* Kill off second chess program */
\r
7988 if (second.isr != NULL)
\r
7989 RemoveInputSource(second.isr);
\r
7990 second.isr = NULL;
\r
7992 if (second.pr != NoProc) {
\r
7993 DoSleep( appData.delayBeforeQuit );
\r
7994 SendToProgram("quit\n", &second);
\r
7995 DoSleep( appData.delayAfterQuit );
\r
7996 DestroyChildProcess(second.pr, second.useSigterm);
\r
7998 second.pr = NoProc;
\r
8001 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8004 if (first.twoMachinesColor[0] == 'w') {
\r
8005 first.matchWins++;
\r
8007 second.matchWins++;
\r
8011 if (first.twoMachinesColor[0] == 'b') {
\r
8012 first.matchWins++;
\r
8014 second.matchWins++;
\r
8020 if (matchGame < appData.matchGames) {
\r
8022 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8023 tmp = first.twoMachinesColor;
\r
8024 first.twoMachinesColor = second.twoMachinesColor;
\r
8025 second.twoMachinesColor = tmp;
\r
8027 gameMode = nextGameMode;
\r
8029 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8030 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8031 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8032 endingGame = 0; /* [HGM] crash */
\r
8035 char buf[MSG_SIZ];
\r
8036 gameMode = nextGameMode;
\r
8037 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8038 first.tidy, second.tidy,
\r
8039 first.matchWins, second.matchWins,
\r
8040 appData.matchGames - (first.matchWins + second.matchWins));
\r
8041 DisplayFatalError(buf, 0, 0);
\r
8044 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8045 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8046 ExitAnalyzeMode();
\r
8047 gameMode = nextGameMode;
\r
8049 endingGame = 0; /* [HGM] crash */
\r
8052 /* Assumes program was just initialized (initString sent).
\r
8053 Leaves program in force mode. */
\r
8055 FeedMovesToProgram(cps, upto)
\r
8056 ChessProgramState *cps;
\r
8061 if (appData.debugMode)
\r
8062 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8063 startedFromSetupPosition ? "position and " : "",
\r
8064 backwardMostMove, upto, cps->which);
\r
8065 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8066 // [HGM] variantswitch: make engine aware of new variant
\r
8067 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8068 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8069 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8070 SendToProgram(buf, cps);
\r
8071 currentlyInitializedVariant = gameInfo.variant;
\r
8073 SendToProgram("force\n", cps);
\r
8074 if (startedFromSetupPosition) {
\r
8075 SendBoard(cps, backwardMostMove);
\r
8076 if (appData.debugMode) {
\r
8077 fprintf(debugFP, "feedMoves\n");
\r
8080 for (i = backwardMostMove; i < upto; i++) {
\r
8081 SendMoveToProgram(i, cps);
\r
8087 ResurrectChessProgram()
\r
8089 /* The chess program may have exited.
\r
8090 If so, restart it and feed it all the moves made so far. */
\r
8092 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8094 StartChessProgram(&first);
\r
8095 InitChessProgram(&first, FALSE);
\r
8096 FeedMovesToProgram(&first, currentMove);
\r
8098 if (!first.sendTime) {
\r
8099 /* can't tell gnuchess what its clock should read,
\r
8100 so we bow to its notion. */
\r
8102 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8103 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8106 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8107 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8108 SendToProgram("analyze\n", &first);
\r
8109 first.analyzing = TRUE;
\r
8114 * Button procedures
\r
8117 Reset(redraw, init)
\r
8122 if (appData.debugMode) {
\r
8123 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8124 redraw, init, gameMode);
\r
8126 pausing = pauseExamInvalid = FALSE;
\r
8127 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8129 whiteFlag = blackFlag = FALSE;
\r
8130 userOfferedDraw = FALSE;
\r
8131 hintRequested = bookRequested = FALSE;
\r
8132 first.maybeThinking = FALSE;
\r
8133 second.maybeThinking = FALSE;
\r
8134 first.bookSuspend = FALSE; // [HGM] book
\r
8135 second.bookSuspend = FALSE;
\r
8136 thinkOutput[0] = NULLCHAR;
\r
8137 lastHint[0] = NULLCHAR;
\r
8138 ClearGameInfo(&gameInfo);
\r
8139 gameInfo.variant = StringToVariant(appData.variant);
\r
8140 ics_user_moved = ics_clock_paused = FALSE;
\r
8141 ics_getting_history = H_FALSE;
\r
8143 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8144 ClearProgramStats();
\r
8145 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8148 ClearHighlights();
\r
8149 flipView = appData.flipView;
\r
8150 ClearPremoveHighlights();
\r
8151 gotPremove = FALSE;
\r
8152 alarmSounded = FALSE;
\r
8154 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8155 if(appData.serverMovesName != NULL) {
\r
8156 /* [HGM] prepare to make moves file for broadcasting */
\r
8157 clock_t t = clock();
\r
8158 if(serverMoves != NULL) fclose(serverMoves);
\r
8159 serverMoves = fopen(appData.serverMovesName, "r");
\r
8160 if(serverMoves != NULL) {
\r
8161 fclose(serverMoves);
\r
8162 /* delay 15 sec before overwriting, so all clients can see end */
\r
8163 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8165 serverMoves = fopen(appData.serverMovesName, "w");
\r
8168 ExitAnalyzeMode();
\r
8169 gameMode = BeginningOfGame;
\r
8171 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8172 InitPosition(redraw);
\r
8173 for (i = 0; i < MAX_MOVES; i++) {
\r
8174 if (commentList[i] != NULL) {
\r
8175 free(commentList[i]);
\r
8176 commentList[i] = NULL;
\r
8180 timeRemaining[0][0] = whiteTimeRemaining;
\r
8181 timeRemaining[1][0] = blackTimeRemaining;
\r
8182 if (first.pr == NULL) {
\r
8183 StartChessProgram(&first);
\r
8186 InitChessProgram(&first, startedFromSetupPosition);
\r
8189 DisplayMessage("", "");
\r
8190 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8194 AutoPlayGameLoop()
\r
8197 if (!AutoPlayOneMove())
\r
8199 if (matchMode || appData.timeDelay == 0)
\r
8201 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8203 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8212 int fromX, fromY, toX, toY;
\r
8214 if (appData.debugMode) {
\r
8215 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8218 if (gameMode != PlayFromGameFile)
\r
8221 if (currentMove >= forwardMostMove) {
\r
8222 gameMode = EditGame;
\r
8225 /* [AS] Clear current move marker at the end of a game */
\r
8226 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8231 toX = moveList[currentMove][2] - AAA;
\r
8232 toY = moveList[currentMove][3] - ONE;
\r
8234 if (moveList[currentMove][1] == '@') {
\r
8235 if (appData.highlightLastMove) {
\r
8236 SetHighlights(-1, -1, toX, toY);
\r
8239 fromX = moveList[currentMove][0] - AAA;
\r
8240 fromY = moveList[currentMove][1] - ONE;
\r
8242 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8244 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8246 if (appData.highlightLastMove) {
\r
8247 SetHighlights(fromX, fromY, toX, toY);
\r
8250 DisplayMove(currentMove);
\r
8251 SendMoveToProgram(currentMove++, &first);
\r
8252 DisplayBothClocks();
\r
8253 DrawPosition(FALSE, boards[currentMove]);
\r
8254 // [HGM] PV info: always display, routine tests if empty
\r
8255 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8261 LoadGameOneMove(readAhead)
\r
8262 ChessMove readAhead;
\r
8264 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8265 char promoChar = NULLCHAR;
\r
8266 ChessMove moveType;
\r
8267 char move[MSG_SIZ];
\r
8270 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8271 gameMode != AnalyzeMode && gameMode != Training) {
\r
8272 gameFileFP = NULL;
\r
8276 yyboardindex = forwardMostMove;
\r
8277 if (readAhead != (ChessMove)0) {
\r
8278 moveType = readAhead;
\r
8280 if (gameFileFP == NULL)
\r
8282 moveType = (ChessMove) yylex();
\r
8286 switch (moveType) {
\r
8288 if (appData.debugMode)
\r
8289 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8291 if (*p == '{' || *p == '[' || *p == '(') {
\r
8292 p[strlen(p) - 1] = NULLCHAR;
\r
8296 /* append the comment but don't display it */
\r
8297 while (*p == '\n') p++;
\r
8298 AppendComment(currentMove, p);
\r
8301 case WhiteCapturesEnPassant:
\r
8302 case BlackCapturesEnPassant:
\r
8303 case WhitePromotionChancellor:
\r
8304 case BlackPromotionChancellor:
\r
8305 case WhitePromotionArchbishop:
\r
8306 case BlackPromotionArchbishop:
\r
8307 case WhitePromotionCentaur:
\r
8308 case BlackPromotionCentaur:
\r
8309 case WhitePromotionQueen:
\r
8310 case BlackPromotionQueen:
\r
8311 case WhitePromotionRook:
\r
8312 case BlackPromotionRook:
\r
8313 case WhitePromotionBishop:
\r
8314 case BlackPromotionBishop:
\r
8315 case WhitePromotionKnight:
\r
8316 case BlackPromotionKnight:
\r
8317 case WhitePromotionKing:
\r
8318 case BlackPromotionKing:
\r
8320 case WhiteKingSideCastle:
\r
8321 case WhiteQueenSideCastle:
\r
8322 case BlackKingSideCastle:
\r
8323 case BlackQueenSideCastle:
\r
8324 case WhiteKingSideCastleWild:
\r
8325 case WhiteQueenSideCastleWild:
\r
8326 case BlackKingSideCastleWild:
\r
8327 case BlackQueenSideCastleWild:
\r
8329 case WhiteHSideCastleFR:
\r
8330 case WhiteASideCastleFR:
\r
8331 case BlackHSideCastleFR:
\r
8332 case BlackASideCastleFR:
\r
8334 if (appData.debugMode)
\r
8335 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8336 fromX = currentMoveString[0] - AAA;
\r
8337 fromY = currentMoveString[1] - ONE;
\r
8338 toX = currentMoveString[2] - AAA;
\r
8339 toY = currentMoveString[3] - ONE;
\r
8340 promoChar = currentMoveString[4];
\r
8345 if (appData.debugMode)
\r
8346 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8347 fromX = moveType == WhiteDrop ?
\r
8348 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8349 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8350 fromY = DROP_RANK;
\r
8351 toX = currentMoveString[2] - AAA;
\r
8352 toY = currentMoveString[3] - ONE;
\r
8358 case GameUnfinished:
\r
8359 if (appData.debugMode)
\r
8360 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8361 p = strchr(yy_text, '{');
\r
8362 if (p == NULL) p = strchr(yy_text, '(');
\r
8365 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8367 q = strchr(p, *p == '{' ? '}' : ')');
\r
8368 if (q != NULL) *q = NULLCHAR;
\r
8371 GameEnds(moveType, p, GE_FILE);
\r
8373 if (cmailMsgLoaded) {
\r
8374 ClearHighlights();
\r
8375 flipView = WhiteOnMove(currentMove);
\r
8376 if (moveType == GameUnfinished) flipView = !flipView;
\r
8377 if (appData.debugMode)
\r
8378 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8382 case (ChessMove) 0: /* end of file */
\r
8383 if (appData.debugMode)
\r
8384 fprintf(debugFP, "Parser hit end of file\n");
\r
8385 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8386 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8390 case MT_CHECKMATE:
\r
8391 case MT_STAINMATE:
\r
8392 if (WhiteOnMove(currentMove)) {
\r
8393 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8395 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8398 case MT_STALEMATE:
\r
8399 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8405 case MoveNumberOne:
\r
8406 if (lastLoadGameStart == GNUChessGame) {
\r
8407 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8408 if (appData.debugMode)
\r
8409 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8410 yy_text, (int) moveType);
\r
8411 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8413 /* else fall thru */
\r
8416 case GNUChessGame:
\r
8418 /* Reached start of next game in file */
\r
8419 if (appData.debugMode)
\r
8420 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8421 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8422 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8426 case MT_CHECKMATE:
\r
8427 case MT_STAINMATE:
\r
8428 if (WhiteOnMove(currentMove)) {
\r
8429 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8431 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8434 case MT_STALEMATE:
\r
8435 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8441 case PositionDiagram: /* should not happen; ignore */
\r
8442 case ElapsedTime: /* ignore */
\r
8443 case NAG: /* ignore */
\r
8444 if (appData.debugMode)
\r
8445 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8446 yy_text, (int) moveType);
\r
8447 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8450 if (appData.testLegality) {
\r
8451 if (appData.debugMode)
\r
8452 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8453 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8454 (forwardMostMove / 2) + 1,
\r
8455 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8456 DisplayError(move, 0);
\r
8459 if (appData.debugMode)
\r
8460 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8461 yy_text, currentMoveString);
\r
8462 fromX = currentMoveString[0] - AAA;
\r
8463 fromY = currentMoveString[1] - ONE;
\r
8464 toX = currentMoveString[2] - AAA;
\r
8465 toY = currentMoveString[3] - ONE;
\r
8466 promoChar = currentMoveString[4];
\r
8470 case AmbiguousMove:
\r
8471 if (appData.debugMode)
\r
8472 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8473 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8474 (forwardMostMove / 2) + 1,
\r
8475 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8476 DisplayError(move, 0);
\r
8481 case ImpossibleMove:
\r
8482 if (appData.debugMode)
\r
8483 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8484 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8485 (forwardMostMove / 2) + 1,
\r
8486 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8487 DisplayError(move, 0);
\r
8493 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8494 DrawPosition(FALSE, boards[currentMove]);
\r
8495 DisplayBothClocks();
\r
8496 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8497 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8499 (void) StopLoadGameTimer();
\r
8500 gameFileFP = NULL;
\r
8501 cmailOldMove = forwardMostMove;
\r
8504 /* currentMoveString is set as a side-effect of yylex */
\r
8505 strcat(currentMoveString, "\n");
\r
8506 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8508 thinkOutput[0] = NULLCHAR;
\r
8509 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8510 currentMove = forwardMostMove;
\r
8515 /* Load the nth game from the given file */
\r
8517 LoadGameFromFile(filename, n, title, useList)
\r
8521 /*Boolean*/ int useList;
\r
8524 char buf[MSG_SIZ];
\r
8526 if (strcmp(filename, "-") == 0) {
\r
8530 f = fopen(filename, "rb");
\r
8532 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8533 DisplayError(buf, errno);
\r
8537 if (fseek(f, 0, 0) == -1) {
\r
8538 /* f is not seekable; probably a pipe */
\r
8541 if (useList && n == 0) {
\r
8542 int error = GameListBuild(f);
\r
8544 DisplayError(_("Cannot build game list"), error);
\r
8545 } else if (!ListEmpty(&gameList) &&
\r
8546 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8547 GameListPopUp(f, title);
\r
8550 GameListDestroy();
\r
8553 if (n == 0) n = 1;
\r
8554 return LoadGame(f, n, title, FALSE);
\r
8559 MakeRegisteredMove()
\r
8561 int fromX, fromY, toX, toY;
\r
8563 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8564 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8567 if (appData.debugMode)
\r
8568 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8569 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8571 thinkOutput[0] = NULLCHAR;
\r
8572 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8573 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8574 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8575 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8576 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8577 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8578 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8579 ShowMove(fromX, fromY, toX, toY);
\r
8581 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8582 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8587 case MT_CHECKMATE:
\r
8588 case MT_STAINMATE:
\r
8589 if (WhiteOnMove(currentMove)) {
\r
8590 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8592 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8596 case MT_STALEMATE:
\r
8597 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8603 case CMAIL_RESIGN:
\r
8604 if (WhiteOnMove(currentMove)) {
\r
8605 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8607 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8611 case CMAIL_ACCEPT:
\r
8612 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8623 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8625 CmailLoadGame(f, gameNumber, title, useList)
\r
8633 if (gameNumber > nCmailGames) {
\r
8634 DisplayError(_("No more games in this message"), 0);
\r
8637 if (f == lastLoadGameFP) {
\r
8638 int offset = gameNumber - lastLoadGameNumber;
\r
8639 if (offset == 0) {
\r
8640 cmailMsg[0] = NULLCHAR;
\r
8641 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8642 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8643 nCmailMovesRegistered--;
\r
8645 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8646 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8647 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8650 if (! RegisterMove()) return FALSE;
\r
8654 retVal = LoadGame(f, gameNumber, title, useList);
\r
8656 /* Make move registered during previous look at this game, if any */
\r
8657 MakeRegisteredMove();
\r
8659 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8660 commentList[currentMove]
\r
8661 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8662 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8668 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8670 ReloadGame(offset)
\r
8673 int gameNumber = lastLoadGameNumber + offset;
\r
8674 if (lastLoadGameFP == NULL) {
\r
8675 DisplayError(_("No game has been loaded yet"), 0);
\r
8678 if (gameNumber <= 0) {
\r
8679 DisplayError(_("Can't back up any further"), 0);
\r
8682 if (cmailMsgLoaded) {
\r
8683 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8684 lastLoadGameTitle, lastLoadGameUseList);
\r
8686 return LoadGame(lastLoadGameFP, gameNumber,
\r
8687 lastLoadGameTitle, lastLoadGameUseList);
\r
8693 /* Load the nth game from open file f */
\r
8695 LoadGame(f, gameNumber, title, useList)
\r
8702 char buf[MSG_SIZ];
\r
8703 int gn = gameNumber;
\r
8704 ListGame *lg = NULL;
\r
8705 int numPGNTags = 0;
\r
8707 GameMode oldGameMode;
\r
8708 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8710 if (appData.debugMode)
\r
8711 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8713 if (gameMode == Training )
\r
8714 SetTrainingModeOff();
\r
8716 oldGameMode = gameMode;
\r
8717 if (gameMode != BeginningOfGame) {
\r
8718 Reset(FALSE, TRUE);
\r
8722 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8723 fclose(lastLoadGameFP);
\r
8727 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8730 fseek(f, lg->offset, 0);
\r
8731 GameListHighlight(gameNumber);
\r
8735 DisplayError(_("Game number out of range"), 0);
\r
8739 GameListDestroy();
\r
8740 if (fseek(f, 0, 0) == -1) {
\r
8741 if (f == lastLoadGameFP ?
\r
8742 gameNumber == lastLoadGameNumber + 1 :
\r
8743 gameNumber == 1) {
\r
8746 DisplayError(_("Can't seek on game file"), 0);
\r
8751 lastLoadGameFP = f;
\r
8752 lastLoadGameNumber = gameNumber;
\r
8753 strcpy(lastLoadGameTitle, title);
\r
8754 lastLoadGameUseList = useList;
\r
8758 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8759 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8760 lg->gameInfo.black);
\r
8761 DisplayTitle(buf);
\r
8762 } else if (*title != NULLCHAR) {
\r
8763 if (gameNumber > 1) {
\r
8764 sprintf(buf, "%s %d", title, gameNumber);
\r
8765 DisplayTitle(buf);
\r
8767 DisplayTitle(title);
\r
8771 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8772 gameMode = PlayFromGameFile;
\r
8776 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8777 CopyBoard(boards[0], initialPosition);
\r
8781 * Skip the first gn-1 games in the file.
\r
8782 * Also skip over anything that precedes an identifiable
\r
8783 * start of game marker, to avoid being confused by
\r
8784 * garbage at the start of the file. Currently
\r
8785 * recognized start of game markers are the move number "1",
\r
8786 * the pattern "gnuchess .* game", the pattern
\r
8787 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8788 * A game that starts with one of the latter two patterns
\r
8789 * will also have a move number 1, possibly
\r
8790 * following a position diagram.
\r
8791 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8792 * start with an unnumbered move. Does that break anything?
\r
8794 cm = lastLoadGameStart = (ChessMove) 0;
\r
8796 yyboardindex = forwardMostMove;
\r
8797 cm = (ChessMove) yylex();
\r
8799 case (ChessMove) 0:
\r
8800 if (cmailMsgLoaded) {
\r
8801 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8803 Reset(TRUE, TRUE);
\r
8804 DisplayError(_("Game not found in file"), 0);
\r
8808 case GNUChessGame:
\r
8811 lastLoadGameStart = cm;
\r
8814 case MoveNumberOne:
\r
8815 switch (lastLoadGameStart) {
\r
8816 case GNUChessGame:
\r
8820 case MoveNumberOne:
\r
8821 case (ChessMove) 0:
\r
8822 gn--; /* count this game */
\r
8823 lastLoadGameStart = cm;
\r
8832 switch (lastLoadGameStart) {
\r
8833 case GNUChessGame:
\r
8835 case MoveNumberOne:
\r
8836 case (ChessMove) 0:
\r
8837 gn--; /* count this game */
\r
8838 lastLoadGameStart = cm;
\r
8841 lastLoadGameStart = cm; /* game counted already */
\r
8849 yyboardindex = forwardMostMove;
\r
8850 cm = (ChessMove) yylex();
\r
8851 } while (cm == PGNTag || cm == Comment);
\r
8858 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8859 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8860 != CMAIL_OLD_RESULT) {
\r
8861 nCmailResults ++ ;
\r
8862 cmailResult[ CMAIL_MAX_GAMES
\r
8863 - gn - 1] = CMAIL_OLD_RESULT;
\r
8869 /* Only a NormalMove can be at the start of a game
\r
8870 * without a position diagram. */
\r
8871 if (lastLoadGameStart == (ChessMove) 0) {
\r
8873 lastLoadGameStart = MoveNumberOne;
\r
8882 if (appData.debugMode)
\r
8883 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8885 if (cm == XBoardGame) {
\r
8886 /* Skip any header junk before position diagram and/or move 1 */
\r
8888 yyboardindex = forwardMostMove;
\r
8889 cm = (ChessMove) yylex();
\r
8891 if (cm == (ChessMove) 0 ||
\r
8892 cm == GNUChessGame || cm == XBoardGame) {
\r
8893 /* Empty game; pretend end-of-file and handle later */
\r
8894 cm = (ChessMove) 0;
\r
8898 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8899 cm == PGNTag || cm == Comment)
\r
8902 } else if (cm == GNUChessGame) {
\r
8903 if (gameInfo.event != NULL) {
\r
8904 free(gameInfo.event);
\r
8906 gameInfo.event = StrSave(yy_text);
\r
8909 startedFromSetupPosition = FALSE;
\r
8910 while (cm == PGNTag) {
\r
8911 if (appData.debugMode)
\r
8912 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8913 err = ParsePGNTag(yy_text, &gameInfo);
\r
8914 if (!err) numPGNTags++;
\r
8916 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8917 if(gameInfo.variant != oldVariant) {
\r
8918 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8919 InitPosition(TRUE);
\r
8920 oldVariant = gameInfo.variant;
\r
8921 if (appData.debugMode)
\r
8922 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8926 if (gameInfo.fen != NULL) {
\r
8927 Board initial_position;
\r
8928 startedFromSetupPosition = TRUE;
\r
8929 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8930 Reset(TRUE, TRUE);
\r
8931 DisplayError(_("Bad FEN position in file"), 0);
\r
8934 CopyBoard(boards[0], initial_position);
\r
8935 if (blackPlaysFirst) {
\r
8936 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8937 CopyBoard(boards[1], initial_position);
\r
8938 strcpy(moveList[0], "");
\r
8939 strcpy(parseList[0], "");
\r
8940 timeRemaining[0][1] = whiteTimeRemaining;
\r
8941 timeRemaining[1][1] = blackTimeRemaining;
\r
8942 if (commentList[0] != NULL) {
\r
8943 commentList[1] = commentList[0];
\r
8944 commentList[0] = NULL;
\r
8947 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8949 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8951 initialRulePlies = FENrulePlies;
\r
8952 epStatus[forwardMostMove] = FENepStatus;
\r
8953 for( i=0; i< nrCastlingRights; i++ )
\r
8954 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8956 yyboardindex = forwardMostMove;
\r
8957 free(gameInfo.fen);
\r
8958 gameInfo.fen = NULL;
\r
8961 yyboardindex = forwardMostMove;
\r
8962 cm = (ChessMove) yylex();
\r
8964 /* Handle comments interspersed among the tags */
\r
8965 while (cm == Comment) {
\r
8967 if (appData.debugMode)
\r
8968 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8970 if (*p == '{' || *p == '[' || *p == '(') {
\r
8971 p[strlen(p) - 1] = NULLCHAR;
\r
8974 while (*p == '\n') p++;
\r
8975 AppendComment(currentMove, p);
\r
8976 yyboardindex = forwardMostMove;
\r
8977 cm = (ChessMove) yylex();
\r
8981 /* don't rely on existence of Event tag since if game was
\r
8982 * pasted from clipboard the Event tag may not exist
\r
8984 if (numPGNTags > 0){
\r
8986 if (gameInfo.variant == VariantNormal) {
\r
8987 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8990 if( appData.autoDisplayTags ) {
\r
8991 tags = PGNTags(&gameInfo);
\r
8992 TagsPopUp(tags, CmailMsg());
\r
8997 /* Make something up, but don't display it now */
\r
9002 if (cm == PositionDiagram) {
\r
9005 Board initial_position;
\r
9007 if (appData.debugMode)
\r
9008 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9010 if (!startedFromSetupPosition) {
\r
9012 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9013 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9023 initial_position[i][j++] = CharToPiece(*p);
\r
9026 while (*p == ' ' || *p == '\t' ||
\r
9027 *p == '\n' || *p == '\r') p++;
\r
9029 if (strncmp(p, "black", strlen("black"))==0)
\r
9030 blackPlaysFirst = TRUE;
\r
9032 blackPlaysFirst = FALSE;
\r
9033 startedFromSetupPosition = TRUE;
\r
9035 CopyBoard(boards[0], initial_position);
\r
9036 if (blackPlaysFirst) {
\r
9037 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9038 CopyBoard(boards[1], initial_position);
\r
9039 strcpy(moveList[0], "");
\r
9040 strcpy(parseList[0], "");
\r
9041 timeRemaining[0][1] = whiteTimeRemaining;
\r
9042 timeRemaining[1][1] = blackTimeRemaining;
\r
9043 if (commentList[0] != NULL) {
\r
9044 commentList[1] = commentList[0];
\r
9045 commentList[0] = NULL;
\r
9048 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9051 yyboardindex = forwardMostMove;
\r
9052 cm = (ChessMove) yylex();
\r
9055 if (first.pr == NoProc) {
\r
9056 StartChessProgram(&first);
\r
9058 InitChessProgram(&first, FALSE);
\r
9059 SendToProgram("force\n", &first);
\r
9060 if (startedFromSetupPosition) {
\r
9061 SendBoard(&first, forwardMostMove);
\r
9062 if (appData.debugMode) {
\r
9063 fprintf(debugFP, "Load Game\n");
\r
9065 DisplayBothClocks();
\r
9068 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9069 loadFlag = appData.suppressLoadMoves;
\r
9071 while (cm == Comment) {
\r
9073 if (appData.debugMode)
\r
9074 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9076 if (*p == '{' || *p == '[' || *p == '(') {
\r
9077 p[strlen(p) - 1] = NULLCHAR;
\r
9080 while (*p == '\n') p++;
\r
9081 AppendComment(currentMove, p);
\r
9082 yyboardindex = forwardMostMove;
\r
9083 cm = (ChessMove) yylex();
\r
9086 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9087 cm == WhiteWins || cm == BlackWins ||
\r
9088 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9089 DisplayMessage("", _("No moves in game"));
\r
9090 if (cmailMsgLoaded) {
\r
9091 if (appData.debugMode)
\r
9092 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9093 ClearHighlights();
\r
9096 DrawPosition(FALSE, boards[currentMove]);
\r
9097 DisplayBothClocks();
\r
9098 gameMode = EditGame;
\r
9100 gameFileFP = NULL;
\r
9105 // [HGM] PV info: routine tests if comment empty
\r
9106 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9107 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9109 if (!matchMode && appData.timeDelay != 0)
\r
9110 DrawPosition(FALSE, boards[currentMove]);
\r
9112 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9113 programStats.ok_to_send = 1;
\r
9116 /* if the first token after the PGN tags is a move
\r
9117 * and not move number 1, retrieve it from the parser
\r
9119 if (cm != MoveNumberOne)
\r
9120 LoadGameOneMove(cm);
\r
9122 /* load the remaining moves from the file */
\r
9123 while (LoadGameOneMove((ChessMove)0)) {
\r
9124 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9125 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9128 /* rewind to the start of the game */
\r
9129 currentMove = backwardMostMove;
\r
9131 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9133 if (oldGameMode == AnalyzeFile ||
\r
9134 oldGameMode == AnalyzeMode) {
\r
9135 AnalyzeFileEvent();
\r
9138 if (matchMode || appData.timeDelay == 0) {
\r
9140 gameMode = EditGame;
\r
9142 } else if (appData.timeDelay > 0) {
\r
9143 AutoPlayGameLoop();
\r
9146 if (appData.debugMode)
\r
9147 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9149 loadFlag = 0; /* [HGM] true game starts */
\r
9153 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9155 ReloadPosition(offset)
\r
9158 int positionNumber = lastLoadPositionNumber + offset;
\r
9159 if (lastLoadPositionFP == NULL) {
\r
9160 DisplayError(_("No position has been loaded yet"), 0);
\r
9163 if (positionNumber <= 0) {
\r
9164 DisplayError(_("Can't back up any further"), 0);
\r
9167 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9168 lastLoadPositionTitle);
\r
9171 /* Load the nth position from the given file */
\r
9173 LoadPositionFromFile(filename, n, title)
\r
9179 char buf[MSG_SIZ];
\r
9181 if (strcmp(filename, "-") == 0) {
\r
9182 return LoadPosition(stdin, n, "stdin");
\r
9184 f = fopen(filename, "rb");
\r
9186 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9187 DisplayError(buf, errno);
\r
9190 return LoadPosition(f, n, title);
\r
9195 /* Load the nth position from the given open file, and close it */
\r
9197 LoadPosition(f, positionNumber, title)
\r
9199 int positionNumber;
\r
9202 char *p, line[MSG_SIZ];
\r
9203 Board initial_position;
\r
9204 int i, j, fenMode, pn;
\r
9206 if (gameMode == Training )
\r
9207 SetTrainingModeOff();
\r
9209 if (gameMode != BeginningOfGame) {
\r
9210 Reset(FALSE, TRUE);
\r
9212 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9213 fclose(lastLoadPositionFP);
\r
9215 if (positionNumber == 0) positionNumber = 1;
\r
9216 lastLoadPositionFP = f;
\r
9217 lastLoadPositionNumber = positionNumber;
\r
9218 strcpy(lastLoadPositionTitle, title);
\r
9219 if (first.pr == NoProc) {
\r
9220 StartChessProgram(&first);
\r
9221 InitChessProgram(&first, FALSE);
\r
9223 pn = positionNumber;
\r
9224 if (positionNumber < 0) {
\r
9225 /* Negative position number means to seek to that byte offset */
\r
9226 if (fseek(f, -positionNumber, 0) == -1) {
\r
9227 DisplayError(_("Can't seek on position file"), 0);
\r
9232 if (fseek(f, 0, 0) == -1) {
\r
9233 if (f == lastLoadPositionFP ?
\r
9234 positionNumber == lastLoadPositionNumber + 1 :
\r
9235 positionNumber == 1) {
\r
9238 DisplayError(_("Can't seek on position file"), 0);
\r
9243 /* See if this file is FEN or old-style xboard */
\r
9244 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9245 DisplayError(_("Position not found in file"), 0);
\r
9249 switch (line[0]) {
\r
9250 case '#': case 'x':
\r
9254 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9255 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9256 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9257 case '7': case '8': case '9':
\r
9258 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9259 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9260 case 'C': case 'W': case 'c': case 'w':
\r
9265 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9266 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9270 if (fenMode || line[0] == '#') pn--;
\r
9272 /* skip positions before number pn */
\r
9273 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9274 Reset(TRUE, TRUE);
\r
9275 DisplayError(_("Position not found in file"), 0);
\r
9278 if (fenMode || line[0] == '#') pn--;
\r
9283 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9284 DisplayError(_("Bad FEN position in file"), 0);
\r
9288 (void) fgets(line, MSG_SIZ, f);
\r
9289 (void) fgets(line, MSG_SIZ, f);
\r
9291 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9292 (void) fgets(line, MSG_SIZ, f);
\r
9293 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9296 initial_position[i][j++] = CharToPiece(*p);
\r
9300 blackPlaysFirst = FALSE;
\r
9302 (void) fgets(line, MSG_SIZ, f);
\r
9303 if (strncmp(line, "black", strlen("black"))==0)
\r
9304 blackPlaysFirst = TRUE;
\r
9307 startedFromSetupPosition = TRUE;
\r
9309 SendToProgram("force\n", &first);
\r
9310 CopyBoard(boards[0], initial_position);
\r
9311 if (blackPlaysFirst) {
\r
9312 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9313 strcpy(moveList[0], "");
\r
9314 strcpy(parseList[0], "");
\r
9315 CopyBoard(boards[1], initial_position);
\r
9316 DisplayMessage("", _("Black to play"));
\r
9318 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9319 DisplayMessage("", _("White to play"));
\r
9321 /* [HGM] copy FEN attributes as well */
\r
9323 initialRulePlies = FENrulePlies;
\r
9324 epStatus[forwardMostMove] = FENepStatus;
\r
9325 for( i=0; i< nrCastlingRights; i++ )
\r
9326 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9328 SendBoard(&first, forwardMostMove);
\r
9329 if (appData.debugMode) {
\r
9331 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9332 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9333 fprintf(debugFP, "Load Position\n");
\r
9336 if (positionNumber > 1) {
\r
9337 sprintf(line, "%s %d", title, positionNumber);
\r
9338 DisplayTitle(line);
\r
9340 DisplayTitle(title);
\r
9342 gameMode = EditGame;
\r
9345 timeRemaining[0][1] = whiteTimeRemaining;
\r
9346 timeRemaining[1][1] = blackTimeRemaining;
\r
9347 DrawPosition(FALSE, boards[currentMove]);
\r
9354 CopyPlayerNameIntoFileName(dest, src)
\r
9355 char **dest, *src;
\r
9357 while (*src != NULLCHAR && *src != ',') {
\r
9358 if (*src == ' ') {
\r
9362 *(*dest)++ = *src++;
\r
9367 char *DefaultFileName(ext)
\r
9370 static char def[MSG_SIZ];
\r
9373 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9375 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9377 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9381 def[0] = NULLCHAR;
\r
9386 /* Save the current game to the given file */
\r
9388 SaveGameToFile(filename, append)
\r
9393 char buf[MSG_SIZ];
\r
9395 if (strcmp(filename, "-") == 0) {
\r
9396 return SaveGame(stdout, 0, NULL);
\r
9398 f = fopen(filename, append ? "a" : "w");
\r
9400 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9401 DisplayError(buf, errno);
\r
9404 return SaveGame(f, 0, NULL);
\r
9413 static char buf[MSG_SIZ];
\r
9416 p = strchr(str, ' ');
\r
9417 if (p == NULL) return str;
\r
9418 strncpy(buf, str, p - str);
\r
9419 buf[p - str] = NULLCHAR;
\r
9423 #define PGN_MAX_LINE 75
\r
9425 #define PGN_SIDE_WHITE 0
\r
9426 #define PGN_SIDE_BLACK 1
\r
9429 static int FindFirstMoveOutOfBook( int side )
\r
9433 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9434 int index = backwardMostMove;
\r
9435 int has_book_hit = 0;
\r
9437 if( (index % 2) != side ) {
\r
9441 while( index < forwardMostMove ) {
\r
9442 /* Check to see if engine is in book */
\r
9443 int depth = pvInfoList[index].depth;
\r
9444 int score = pvInfoList[index].score;
\r
9447 if( depth <= 2 ) {
\r
9450 else if( score == 0 && depth == 63 ) {
\r
9451 in_book = 1; /* Zappa */
\r
9453 else if( score == 2 && depth == 99 ) {
\r
9454 in_book = 1; /* Abrok */
\r
9457 has_book_hit += in_book;
\r
9473 void GetOutOfBookInfo( char * buf )
\r
9477 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9479 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9480 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9484 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9485 for( i=0; i<2; i++ ) {
\r
9489 if( i > 0 && oob[0] >= 0 ) {
\r
9490 strcat( buf, " " );
\r
9493 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9494 sprintf( buf+strlen(buf), "%s%.2f",
\r
9495 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9496 pvInfoList[idx].score / 100.0 );
\r
9502 /* Save game in PGN style and close the file */
\r
9507 int i, offset, linelen, newblock;
\r
9509 // char *movetext;
\r
9511 int movelen, numlen, blank;
\r
9512 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9514 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9516 tm = time((time_t *) NULL);
\r
9518 PrintPGNTags(f, &gameInfo);
\r
9520 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9521 char *fen = PositionToFEN(backwardMostMove, NULL);
\r
9522 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9523 fprintf(f, "\n{--------------\n");
\r
9524 PrintPosition(f, backwardMostMove);
\r
9525 fprintf(f, "--------------}\n");
\r
9529 /* [AS] Out of book annotation */
\r
9530 if( appData.saveOutOfBookInfo ) {
\r
9533 GetOutOfBookInfo( buf );
\r
9535 if( buf[0] != '\0' ) {
\r
9536 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9543 i = backwardMostMove;
\r
9547 while (i < forwardMostMove) {
\r
9548 /* Print comments preceding this move */
\r
9549 if (commentList[i] != NULL) {
\r
9550 if (linelen > 0) fprintf(f, "\n");
\r
9551 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9556 /* Format move number */
\r
9557 if ((i % 2) == 0) {
\r
9558 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9561 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9563 numtext[0] = NULLCHAR;
\r
9566 numlen = strlen(numtext);
\r
9569 /* Print move number */
\r
9570 blank = linelen > 0 && numlen > 0;
\r
9571 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9580 fprintf(f, numtext);
\r
9581 linelen += numlen;
\r
9584 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
\r
9585 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
\r
9586 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9587 int p = movelen - 1;
\r
9588 if(move_buffer[p] == ' ') p--;
\r
9589 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
\r
9590 while(p && move_buffer[--p] != '(');
\r
9591 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
\r
9596 blank = linelen > 0 && movelen > 0;
\r
9597 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9606 fprintf(f, move_buffer);
\r
9607 linelen += movelen;
\r
9609 /* [AS] Add PV info if present */
\r
9610 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9611 /* [HGM] add time */
\r
9612 char buf[MSG_SIZ]; int seconds = 0;
\r
9615 if(i >= backwardMostMove) {
\r
9616 if(WhiteOnMove(i))
\r
9617 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9618 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
\r
9620 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9621 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
\r
9623 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9625 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9628 if( seconds <= 0) buf[0] = 0; else
\r
9629 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9630 seconds = (seconds + 4)/10; // round to full seconds
\r
9631 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9632 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9635 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9636 pvInfoList[i].score >= 0 ? "+" : "",
\r
9637 pvInfoList[i].score / 100.0,
\r
9638 pvInfoList[i].depth,
\r
9641 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9643 /* Print score/depth */
\r
9644 blank = linelen > 0 && movelen > 0;
\r
9645 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9654 fprintf(f, move_buffer);
\r
9655 linelen += movelen;
\r
9661 /* Start a new line */
\r
9662 if (linelen > 0) fprintf(f, "\n");
\r
9664 /* Print comments after last move */
\r
9665 if (commentList[i] != NULL) {
\r
9666 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9669 /* Print result */
\r
9670 if (gameInfo.resultDetails != NULL &&
\r
9671 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9672 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9673 PGNResult(gameInfo.result));
\r
9675 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9682 /* Save game in old style and close the file */
\r
9684 SaveGameOldStyle(f)
\r
9690 tm = time((time_t *) NULL);
\r
9692 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9693 PrintOpponents(f);
\r
9695 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9696 fprintf(f, "\n[--------------\n");
\r
9697 PrintPosition(f, backwardMostMove);
\r
9698 fprintf(f, "--------------]\n");
\r
9703 i = backwardMostMove;
\r
9704 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9706 while (i < forwardMostMove) {
\r
9707 if (commentList[i] != NULL) {
\r
9708 fprintf(f, "[%s]\n", commentList[i]);
\r
9711 if ((i % 2) == 1) {
\r
9712 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9715 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9717 if (commentList[i] != NULL) {
\r
9721 if (i >= forwardMostMove) {
\r
9725 fprintf(f, "%s\n", parseList[i]);
\r
9730 if (commentList[i] != NULL) {
\r
9731 fprintf(f, "[%s]\n", commentList[i]);
\r
9734 /* This isn't really the old style, but it's close enough */
\r
9735 if (gameInfo.resultDetails != NULL &&
\r
9736 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9737 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9738 gameInfo.resultDetails);
\r
9740 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9747 /* Save the current game to open file f and close the file */
\r
9749 SaveGame(f, dummy, dummy2)
\r
9754 if (gameMode == EditPosition) EditPositionDone();
\r
9755 if (appData.oldSaveStyle)
\r
9756 return SaveGameOldStyle(f);
\r
9758 return SaveGamePGN(f);
\r
9761 /* Save the current position to the given file */
\r
9763 SavePositionToFile(filename)
\r
9767 char buf[MSG_SIZ];
\r
9769 if (strcmp(filename, "-") == 0) {
\r
9770 return SavePosition(stdout, 0, NULL);
\r
9772 f = fopen(filename, "a");
\r
9774 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9775 DisplayError(buf, errno);
\r
9778 SavePosition(f, 0, NULL);
\r
9784 /* Save the current position to the given open file and close the file */
\r
9786 SavePosition(f, dummy, dummy2)
\r
9794 if (appData.oldSaveStyle) {
\r
9795 tm = time((time_t *) NULL);
\r
9797 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9798 PrintOpponents(f);
\r
9799 fprintf(f, "[--------------\n");
\r
9800 PrintPosition(f, currentMove);
\r
9801 fprintf(f, "--------------]\n");
\r
9803 fen = PositionToFEN(currentMove, NULL);
\r
9804 fprintf(f, "%s\n", fen);
\r
9812 ReloadCmailMsgEvent(unregister)
\r
9816 static char *inFilename = NULL;
\r
9817 static char *outFilename;
\r
9819 struct stat inbuf, outbuf;
\r
9822 /* Any registered moves are unregistered if unregister is set, */
\r
9823 /* i.e. invoked by the signal handler */
\r
9825 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9826 cmailMoveRegistered[i] = FALSE;
\r
9827 if (cmailCommentList[i] != NULL) {
\r
9828 free(cmailCommentList[i]);
\r
9829 cmailCommentList[i] = NULL;
\r
9832 nCmailMovesRegistered = 0;
\r
9835 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9836 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9838 nCmailResults = 0;
\r
9840 if (inFilename == NULL) {
\r
9841 /* Because the filenames are static they only get malloced once */
\r
9842 /* and they never get freed */
\r
9843 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9844 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9846 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9847 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9850 status = stat(outFilename, &outbuf);
\r
9852 cmailMailedMove = FALSE;
\r
9854 status = stat(inFilename, &inbuf);
\r
9855 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9858 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9859 counts the games, notes how each one terminated, etc.
\r
9861 It would be nice to remove this kludge and instead gather all
\r
9862 the information while building the game list. (And to keep it
\r
9863 in the game list nodes instead of having a bunch of fixed-size
\r
9864 parallel arrays.) Note this will require getting each game's
\r
9865 termination from the PGN tags, as the game list builder does
\r
9866 not process the game moves. --mann
\r
9868 cmailMsgLoaded = TRUE;
\r
9869 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9871 /* Load first game in the file or popup game menu */
\r
9872 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9874 #endif /* !WIN32 */
\r
9882 char string[MSG_SIZ];
\r
9884 if ( cmailMailedMove
\r
9885 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9886 return TRUE; /* Allow free viewing */
\r
9889 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9890 /* with the move registered when the conditions for registering no */
\r
9892 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9893 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9894 nCmailMovesRegistered --;
\r
9896 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9898 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9899 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9903 if (cmailOldMove == -1) {
\r
9904 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9908 if (currentMove > cmailOldMove + 1) {
\r
9909 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9913 if (currentMove < cmailOldMove) {
\r
9914 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9918 if (forwardMostMove > currentMove) {
\r
9919 /* Silently truncate extra moves */
\r
9923 if ( (currentMove == cmailOldMove + 1)
\r
9924 || ( (currentMove == cmailOldMove)
\r
9925 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9926 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9927 if (gameInfo.result != GameUnfinished) {
\r
9928 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9931 if (commentList[currentMove] != NULL) {
\r
9932 cmailCommentList[lastLoadGameNumber - 1]
\r
9933 = StrSave(commentList[currentMove]);
\r
9935 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9937 if (appData.debugMode)
\r
9938 fprintf(debugFP, "Saving %s for game %d\n",
\r
9939 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9942 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9944 f = fopen(string, "w");
\r
9945 if (appData.oldSaveStyle) {
\r
9946 SaveGameOldStyle(f); /* also closes the file */
\r
9948 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9949 f = fopen(string, "w");
\r
9950 SavePosition(f, 0, NULL); /* also closes the file */
\r
9952 fprintf(f, "{--------------\n");
\r
9953 PrintPosition(f, currentMove);
\r
9954 fprintf(f, "--------------}\n\n");
\r
9956 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9959 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9960 nCmailMovesRegistered ++;
\r
9961 } else if (nCmailGames == 1) {
\r
9962 DisplayError(_("You have not made a move yet"), 0);
\r
9973 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9974 FILE *commandOutput;
\r
9975 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9976 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9982 if (! cmailMsgLoaded) {
\r
9983 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9987 if (nCmailGames == nCmailResults) {
\r
9988 DisplayError(_("No unfinished games"), 0);
\r
9992 #if CMAIL_PROHIBIT_REMAIL
\r
9993 if (cmailMailedMove) {
\r
9994 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
9995 DisplayError(msg, 0);
\r
10000 if (! (cmailMailedMove || RegisterMove())) return;
\r
10002 if ( cmailMailedMove
\r
10003 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10004 sprintf(string, partCommandString,
\r
10005 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10006 commandOutput = popen(string, "r");
\r
10008 if (commandOutput == NULL) {
\r
10009 DisplayError(_("Failed to invoke cmail"), 0);
\r
10011 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10012 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10014 if (nBuffers > 1) {
\r
10015 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10016 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10017 nBytes = MSG_SIZ - 1;
\r
10019 (void) memcpy(msg, buffer, nBytes);
\r
10021 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10023 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10024 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10027 for (i = 0; i < nCmailGames; i ++) {
\r
10028 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10029 archived = FALSE;
\r
10033 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10035 sprintf(buffer, "%s/%s.%s.archive",
\r
10037 appData.cmailGameName,
\r
10039 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10040 cmailMsgLoaded = FALSE;
\r
10044 DisplayInformation(msg);
\r
10045 pclose(commandOutput);
\r
10048 if ((*cmailMsg) != '\0') {
\r
10049 DisplayInformation(cmailMsg);
\r
10054 #endif /* !WIN32 */
\r
10063 int prependComma = 0;
\r
10065 char string[MSG_SIZ]; /* Space for game-list */
\r
10068 if (!cmailMsgLoaded) return "";
\r
10070 if (cmailMailedMove) {
\r
10071 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10073 /* Create a list of games left */
\r
10074 sprintf(string, "[");
\r
10075 for (i = 0; i < nCmailGames; i ++) {
\r
10076 if (! ( cmailMoveRegistered[i]
\r
10077 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10078 if (prependComma) {
\r
10079 sprintf(number, ",%d", i + 1);
\r
10081 sprintf(number, "%d", i + 1);
\r
10082 prependComma = 1;
\r
10085 strcat(string, number);
\r
10088 strcat(string, "]");
\r
10090 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10091 switch (nCmailGames) {
\r
10093 sprintf(cmailMsg,
\r
10094 _("Still need to make move for game\n"));
\r
10098 sprintf(cmailMsg,
\r
10099 _("Still need to make moves for both games\n"));
\r
10103 sprintf(cmailMsg,
\r
10104 _("Still need to make moves for all %d games\n"),
\r
10109 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10111 sprintf(cmailMsg,
\r
10112 _("Still need to make a move for game %s\n"),
\r
10117 if (nCmailResults == nCmailGames) {
\r
10118 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10120 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10125 sprintf(cmailMsg,
\r
10126 _("Still need to make moves for games %s\n"),
\r
10132 #endif /* WIN32 */
\r
10138 if (gameMode == Training)
\r
10139 SetTrainingModeOff();
\r
10141 Reset(TRUE, TRUE);
\r
10142 cmailMsgLoaded = FALSE;
\r
10143 if (appData.icsActive) {
\r
10144 SendToICS(ics_prefix);
\r
10145 SendToICS("refresh\n");
\r
10150 ExitEvent(status)
\r
10154 if (exiting > 2) {
\r
10155 /* Give up on clean exit */
\r
10158 if (exiting > 1) {
\r
10159 /* Keep trying for clean exit */
\r
10163 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10165 if (telnetISR != NULL) {
\r
10166 RemoveInputSource(telnetISR);
\r
10168 if (icsPR != NoProc) {
\r
10169 DestroyChildProcess(icsPR, TRUE);
\r
10172 /* Save game if resource set and not already saved by GameEnds() */
\r
10173 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10174 && forwardMostMove > 0) {
\r
10175 if (*appData.saveGameFile != NULLCHAR) {
\r
10176 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10177 } else if (appData.autoSaveGames) {
\r
10180 if (*appData.savePositionFile != NULLCHAR) {
\r
10181 SavePositionToFile(appData.savePositionFile);
\r
10184 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10186 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10187 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10189 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10190 /* make sure this other one finishes before killing it! */
\r
10191 if(endingGame) { int count = 0;
\r
10192 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10193 while(endingGame && count++ < 10) DoSleep(1);
\r
10194 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10197 /* Kill off chess programs */
\r
10198 if (first.pr != NoProc) {
\r
10199 ExitAnalyzeMode();
\r
10201 DoSleep( appData.delayBeforeQuit );
\r
10202 SendToProgram("quit\n", &first);
\r
10203 DoSleep( appData.delayAfterQuit );
\r
10204 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10206 if (second.pr != NoProc) {
\r
10207 DoSleep( appData.delayBeforeQuit );
\r
10208 SendToProgram("quit\n", &second);
\r
10209 DoSleep( appData.delayAfterQuit );
\r
10210 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10212 if (first.isr != NULL) {
\r
10213 RemoveInputSource(first.isr);
\r
10215 if (second.isr != NULL) {
\r
10216 RemoveInputSource(second.isr);
\r
10219 ShutDownFrontEnd();
\r
10226 if (appData.debugMode)
\r
10227 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10231 if (gameMode == MachinePlaysWhite ||
\r
10232 gameMode == MachinePlaysBlack) {
\r
10235 DisplayBothClocks();
\r
10237 if (gameMode == PlayFromGameFile) {
\r
10238 if (appData.timeDelay >= 0)
\r
10239 AutoPlayGameLoop();
\r
10240 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10241 Reset(FALSE, TRUE);
\r
10242 SendToICS(ics_prefix);
\r
10243 SendToICS("refresh\n");
\r
10244 } else if (currentMove < forwardMostMove) {
\r
10245 ForwardInner(forwardMostMove);
\r
10247 pauseExamInvalid = FALSE;
\r
10249 switch (gameMode) {
\r
10252 case IcsExamining:
\r
10253 pauseExamForwardMostMove = forwardMostMove;
\r
10254 pauseExamInvalid = FALSE;
\r
10255 /* fall through */
\r
10256 case IcsObserving:
\r
10257 case IcsPlayingWhite:
\r
10258 case IcsPlayingBlack:
\r
10262 case PlayFromGameFile:
\r
10263 (void) StopLoadGameTimer();
\r
10267 case BeginningOfGame:
\r
10268 if (appData.icsActive) return;
\r
10269 /* else fall through */
\r
10270 case MachinePlaysWhite:
\r
10271 case MachinePlaysBlack:
\r
10272 case TwoMachinesPlay:
\r
10273 if (forwardMostMove == 0)
\r
10274 return; /* don't pause if no one has moved */
\r
10275 if ((gameMode == MachinePlaysWhite &&
\r
10276 !WhiteOnMove(forwardMostMove)) ||
\r
10277 (gameMode == MachinePlaysBlack &&
\r
10278 WhiteOnMove(forwardMostMove))) {
\r
10289 EditCommentEvent()
\r
10291 char title[MSG_SIZ];
\r
10293 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10294 strcpy(title, _("Edit comment"));
\r
10296 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10297 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10298 parseList[currentMove - 1]);
\r
10301 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10308 char *tags = PGNTags(&gameInfo);
\r
10309 EditTagsPopUp(tags);
\r
10314 AnalyzeModeEvent()
\r
10316 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10319 if (gameMode != AnalyzeFile) {
\r
10320 if (!appData.icsEngineAnalyze) {
\r
10322 if (gameMode != EditGame) return;
\r
10324 ResurrectChessProgram();
\r
10325 SendToProgram("analyze\n", &first);
\r
10326 first.analyzing = TRUE;
\r
10327 /*first.maybeThinking = TRUE;*/
\r
10328 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10329 AnalysisPopUp(_("Analysis"),
\r
10330 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10332 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10337 StartAnalysisClock();
\r
10338 GetTimeMark(&lastNodeCountTime);
\r
10339 lastNodeCount = 0;
\r
10343 AnalyzeFileEvent()
\r
10345 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10348 if (gameMode != AnalyzeMode) {
\r
10350 if (gameMode != EditGame) return;
\r
10351 ResurrectChessProgram();
\r
10352 SendToProgram("analyze\n", &first);
\r
10353 first.analyzing = TRUE;
\r
10354 /*first.maybeThinking = TRUE;*/
\r
10355 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10356 AnalysisPopUp(_("Analysis"),
\r
10357 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10359 gameMode = AnalyzeFile;
\r
10364 StartAnalysisClock();
\r
10365 GetTimeMark(&lastNodeCountTime);
\r
10366 lastNodeCount = 0;
\r
10370 MachineWhiteEvent()
\r
10372 char buf[MSG_SIZ];
\r
10373 char *bookHit = NULL;
\r
10375 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10379 if (gameMode == PlayFromGameFile ||
\r
10380 gameMode == TwoMachinesPlay ||
\r
10381 gameMode == Training ||
\r
10382 gameMode == AnalyzeMode ||
\r
10383 gameMode == EndOfGame)
\r
10386 if (gameMode == EditPosition)
\r
10387 EditPositionDone();
\r
10389 if (!WhiteOnMove(currentMove)) {
\r
10390 DisplayError(_("It is not White's turn"), 0);
\r
10394 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10395 ExitAnalyzeMode();
\r
10397 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10398 gameMode == AnalyzeFile)
\r
10401 ResurrectChessProgram(); /* in case it isn't running */
\r
10402 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10403 gameMode = MachinePlaysWhite;
\r
10406 gameMode = MachinePlaysWhite;
\r
10410 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10411 DisplayTitle(buf);
\r
10412 if (first.sendName) {
\r
10413 sprintf(buf, "name %s\n", gameInfo.black);
\r
10414 SendToProgram(buf, &first);
\r
10416 if (first.sendTime) {
\r
10417 if (first.useColors) {
\r
10418 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10420 SendTimeRemaining(&first, TRUE);
\r
10422 if (first.useColors) {
\r
10423 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10425 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10426 SetMachineThinkingEnables();
\r
10427 first.maybeThinking = TRUE;
\r
10430 if (appData.autoFlipView && !flipView) {
\r
10431 flipView = !flipView;
\r
10432 DrawPosition(FALSE, NULL);
\r
10433 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10436 if(bookHit) { // [HGM] book: simulate book reply
\r
10437 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10439 programStats.nodes = programStats.depth = programStats.time =
\r
10440 programStats.score = programStats.got_only_move = 0;
\r
10441 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10443 strcpy(bookMove, "move ");
\r
10444 strcat(bookMove, bookHit);
\r
10445 HandleMachineMove(bookMove, &first);
\r
10450 MachineBlackEvent()
\r
10452 char buf[MSG_SIZ];
\r
10453 char *bookHit = NULL;
\r
10455 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10459 if (gameMode == PlayFromGameFile ||
\r
10460 gameMode == TwoMachinesPlay ||
\r
10461 gameMode == Training ||
\r
10462 gameMode == AnalyzeMode ||
\r
10463 gameMode == EndOfGame)
\r
10466 if (gameMode == EditPosition)
\r
10467 EditPositionDone();
\r
10469 if (WhiteOnMove(currentMove)) {
\r
10470 DisplayError(_("It is not Black's turn"), 0);
\r
10474 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10475 ExitAnalyzeMode();
\r
10477 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10478 gameMode == AnalyzeFile)
\r
10481 ResurrectChessProgram(); /* in case it isn't running */
\r
10482 gameMode = MachinePlaysBlack;
\r
10486 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10487 DisplayTitle(buf);
\r
10488 if (first.sendName) {
\r
10489 sprintf(buf, "name %s\n", gameInfo.white);
\r
10490 SendToProgram(buf, &first);
\r
10492 if (first.sendTime) {
\r
10493 if (first.useColors) {
\r
10494 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10496 SendTimeRemaining(&first, FALSE);
\r
10498 if (first.useColors) {
\r
10499 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10501 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10502 SetMachineThinkingEnables();
\r
10503 first.maybeThinking = TRUE;
\r
10506 if (appData.autoFlipView && flipView) {
\r
10507 flipView = !flipView;
\r
10508 DrawPosition(FALSE, NULL);
\r
10509 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10511 if(bookHit) { // [HGM] book: simulate book reply
\r
10512 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10514 programStats.nodes = programStats.depth = programStats.time =
\r
10515 programStats.score = programStats.got_only_move = 0;
\r
10516 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10518 strcpy(bookMove, "move ");
\r
10519 strcat(bookMove, bookHit);
\r
10520 HandleMachineMove(bookMove, &first);
\r
10526 DisplayTwoMachinesTitle()
\r
10528 char buf[MSG_SIZ];
\r
10529 if (appData.matchGames > 0) {
\r
10530 if (first.twoMachinesColor[0] == 'w') {
\r
10531 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10532 gameInfo.white, gameInfo.black,
\r
10533 first.matchWins, second.matchWins,
\r
10534 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10536 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10537 gameInfo.white, gameInfo.black,
\r
10538 second.matchWins, first.matchWins,
\r
10539 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10542 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10544 DisplayTitle(buf);
\r
10548 TwoMachinesEvent P((void))
\r
10551 char buf[MSG_SIZ];
\r
10552 ChessProgramState *onmove;
\r
10553 char *bookHit = NULL;
\r
10555 if (appData.noChessProgram) return;
\r
10557 switch (gameMode) {
\r
10558 case TwoMachinesPlay:
\r
10560 case MachinePlaysWhite:
\r
10561 case MachinePlaysBlack:
\r
10562 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10563 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10566 /* fall through */
\r
10567 case BeginningOfGame:
\r
10568 case PlayFromGameFile:
\r
10571 if (gameMode != EditGame) return;
\r
10573 case EditPosition:
\r
10574 EditPositionDone();
\r
10576 case AnalyzeMode:
\r
10577 case AnalyzeFile:
\r
10578 ExitAnalyzeMode();
\r
10585 forwardMostMove = currentMove;
\r
10586 ResurrectChessProgram(); /* in case first program isn't running */
\r
10588 if (second.pr == NULL) {
\r
10589 StartChessProgram(&second);
\r
10590 if (second.protocolVersion == 1) {
\r
10591 TwoMachinesEventIfReady();
\r
10593 /* kludge: allow timeout for initial "feature" command */
\r
10595 DisplayMessage("", _("Starting second chess program"));
\r
10596 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10600 DisplayMessage("", "");
\r
10601 InitChessProgram(&second, FALSE);
\r
10602 SendToProgram("force\n", &second);
\r
10603 if (startedFromSetupPosition) {
\r
10604 SendBoard(&second, backwardMostMove);
\r
10605 if (appData.debugMode) {
\r
10606 fprintf(debugFP, "Two Machines\n");
\r
10609 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10610 SendMoveToProgram(i, &second);
\r
10613 gameMode = TwoMachinesPlay;
\r
10617 DisplayTwoMachinesTitle();
\r
10618 firstMove = TRUE;
\r
10619 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10622 onmove = &second;
\r
10625 SendToProgram(first.computerString, &first);
\r
10626 if (first.sendName) {
\r
10627 sprintf(buf, "name %s\n", second.tidy);
\r
10628 SendToProgram(buf, &first);
\r
10630 SendToProgram(second.computerString, &second);
\r
10631 if (second.sendName) {
\r
10632 sprintf(buf, "name %s\n", first.tidy);
\r
10633 SendToProgram(buf, &second);
\r
10637 if (!first.sendTime || !second.sendTime) {
\r
10638 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10639 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10641 if (onmove->sendTime) {
\r
10642 if (onmove->useColors) {
\r
10643 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10645 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10647 if (onmove->useColors) {
\r
10648 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10650 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10651 // SendToProgram("go\n", onmove);
\r
10652 onmove->maybeThinking = TRUE;
\r
10653 SetMachineThinkingEnables();
\r
10657 if(bookHit) { // [HGM] book: simulate book reply
\r
10658 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10660 programStats.nodes = programStats.depth = programStats.time =
\r
10661 programStats.score = programStats.got_only_move = 0;
\r
10662 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10664 strcpy(bookMove, "move ");
\r
10665 strcat(bookMove, bookHit);
\r
10666 HandleMachineMove(bookMove, &first);
\r
10673 if (gameMode == Training) {
\r
10674 SetTrainingModeOff();
\r
10675 gameMode = PlayFromGameFile;
\r
10676 DisplayMessage("", _("Training mode off"));
\r
10678 gameMode = Training;
\r
10679 animateTraining = appData.animate;
\r
10681 /* make sure we are not already at the end of the game */
\r
10682 if (currentMove < forwardMostMove) {
\r
10683 SetTrainingModeOn();
\r
10684 DisplayMessage("", _("Training mode on"));
\r
10686 gameMode = PlayFromGameFile;
\r
10687 DisplayError(_("Already at end of game"), 0);
\r
10696 if (!appData.icsActive) return;
\r
10697 switch (gameMode) {
\r
10698 case IcsPlayingWhite:
\r
10699 case IcsPlayingBlack:
\r
10700 case IcsObserving:
\r
10702 case BeginningOfGame:
\r
10703 case IcsExamining:
\r
10709 case EditPosition:
\r
10710 EditPositionDone();
\r
10713 case AnalyzeMode:
\r
10714 case AnalyzeFile:
\r
10715 ExitAnalyzeMode();
\r
10723 gameMode = IcsIdle;
\r
10734 switch (gameMode) {
\r
10736 SetTrainingModeOff();
\r
10738 case MachinePlaysWhite:
\r
10739 case MachinePlaysBlack:
\r
10740 case BeginningOfGame:
\r
10741 SendToProgram("force\n", &first);
\r
10742 SetUserThinkingEnables();
\r
10744 case PlayFromGameFile:
\r
10745 (void) StopLoadGameTimer();
\r
10746 if (gameFileFP != NULL) {
\r
10747 gameFileFP = NULL;
\r
10750 case EditPosition:
\r
10751 EditPositionDone();
\r
10753 case AnalyzeMode:
\r
10754 case AnalyzeFile:
\r
10755 ExitAnalyzeMode();
\r
10756 SendToProgram("force\n", &first);
\r
10758 case TwoMachinesPlay:
\r
10759 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10760 ResurrectChessProgram();
\r
10761 SetUserThinkingEnables();
\r
10764 ResurrectChessProgram();
\r
10766 case IcsPlayingBlack:
\r
10767 case IcsPlayingWhite:
\r
10768 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10770 case IcsObserving:
\r
10771 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10773 case IcsExamining:
\r
10774 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10785 first.offeredDraw = second.offeredDraw = 0;
\r
10787 if (gameMode == PlayFromGameFile) {
\r
10788 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10789 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10790 DisplayTitle("");
\r
10793 if (gameMode == MachinePlaysWhite ||
\r
10794 gameMode == MachinePlaysBlack ||
\r
10795 gameMode == TwoMachinesPlay ||
\r
10796 gameMode == EndOfGame) {
\r
10797 i = forwardMostMove;
\r
10798 while (i > currentMove) {
\r
10799 SendToProgram("undo\n", &first);
\r
10802 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10803 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10804 DisplayBothClocks();
\r
10805 if (whiteFlag || blackFlag) {
\r
10806 whiteFlag = blackFlag = 0;
\r
10808 DisplayTitle("");
\r
10811 gameMode = EditGame;
\r
10818 EditPositionEvent()
\r
10820 if (gameMode == EditPosition) {
\r
10826 if (gameMode != EditGame) return;
\r
10828 gameMode = EditPosition;
\r
10831 if (currentMove > 0)
\r
10832 CopyBoard(boards[0], boards[currentMove]);
\r
10834 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10836 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10837 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10842 ExitAnalyzeMode()
\r
10844 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10845 if (appData.icsEngineAnalyze) {
\r
10846 appData.icsEngineAnalyze = FALSE;
\r
10848 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10850 if (first.analysisSupport && first.analyzing) {
\r
10851 SendToProgram("exit\n", &first);
\r
10852 first.analyzing = FALSE;
\r
10854 AnalysisPopDown();
\r
10855 thinkOutput[0] = NULLCHAR;
\r
10859 EditPositionDone()
\r
10861 startedFromSetupPosition = TRUE;
\r
10862 InitChessProgram(&first, FALSE);
\r
10863 SendToProgram("force\n", &first);
\r
10864 if (blackPlaysFirst) {
\r
10865 strcpy(moveList[0], "");
\r
10866 strcpy(parseList[0], "");
\r
10867 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10868 CopyBoard(boards[1], boards[0]);
\r
10869 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10871 epStatus[1] = epStatus[0];
\r
10872 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10875 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10877 SendBoard(&first, forwardMostMove);
\r
10878 if (appData.debugMode) {
\r
10879 fprintf(debugFP, "EditPosDone\n");
\r
10881 DisplayTitle("");
\r
10882 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10883 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10884 gameMode = EditGame;
\r
10886 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10887 ClearHighlights(); /* [AS] */
\r
10890 /* Pause for `ms' milliseconds */
\r
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10898 GetTimeMark(&m1);
\r
10900 GetTimeMark(&m2);
\r
10901 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10904 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10906 SendMultiLineToICS(buf)
\r
10909 char temp[MSG_SIZ+1], *p;
\r
10912 len = strlen(buf);
\r
10913 if (len > MSG_SIZ)
\r
10916 strncpy(temp, buf, len);
\r
10921 if (*p == '\n' || *p == '\r')
\r
10926 strcat(temp, "\n");
\r
10928 SendToPlayer(temp, strlen(temp));
\r
10932 SetWhiteToPlayEvent()
\r
10934 if (gameMode == EditPosition) {
\r
10935 blackPlaysFirst = FALSE;
\r
10936 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10937 } else if (gameMode == IcsExamining) {
\r
10938 SendToICS(ics_prefix);
\r
10939 SendToICS("tomove white\n");
\r
10944 SetBlackToPlayEvent()
\r
10946 if (gameMode == EditPosition) {
\r
10947 blackPlaysFirst = TRUE;
\r
10948 currentMove = 1; /* kludge */
\r
10949 DisplayBothClocks();
\r
10951 } else if (gameMode == IcsExamining) {
\r
10952 SendToICS(ics_prefix);
\r
10953 SendToICS("tomove black\n");
\r
10958 EditPositionMenuEvent(selection, x, y)
\r
10959 ChessSquare selection;
\r
10962 char buf[MSG_SIZ];
\r
10963 ChessSquare piece = boards[0][y][x];
\r
10965 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10967 switch (selection) {
\r
10969 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10970 SendToICS(ics_prefix);
\r
10971 SendToICS("bsetup clear\n");
\r
10972 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10973 SendToICS(ics_prefix);
\r
10974 SendToICS("clearboard\n");
\r
10976 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10977 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10978 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10979 if (gameMode == IcsExamining) {
\r
10980 if (boards[currentMove][y][x] != EmptySquare) {
\r
10981 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10982 AAA + x, ONE + y);
\r
10986 boards[0][y][x] = p;
\r
10991 if (gameMode == EditPosition) {
\r
10992 DrawPosition(FALSE, boards[0]);
\r
10997 SetWhiteToPlayEvent();
\r
11001 SetBlackToPlayEvent();
\r
11004 case EmptySquare:
\r
11005 if (gameMode == IcsExamining) {
\r
11006 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11009 boards[0][y][x] = EmptySquare;
\r
11010 DrawPosition(FALSE, boards[0]);
\r
11014 case PromotePiece:
\r
11015 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11016 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11017 selection = (ChessSquare) (PROMOTED piece);
\r
11018 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11019 else selection = (ChessSquare)((int)piece - 1);
\r
11020 goto defaultlabel;
\r
11022 case DemotePiece:
\r
11023 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11024 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11025 selection = (ChessSquare) (DEMOTED piece);
\r
11026 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11027 else selection = (ChessSquare)((int)piece + 1);
\r
11028 goto defaultlabel;
\r
11032 if(gameInfo.variant == VariantShatranj ||
\r
11033 gameInfo.variant == VariantXiangqi ||
\r
11034 gameInfo.variant == VariantCourier )
\r
11035 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11036 goto defaultlabel;
\r
11040 if(gameInfo.variant == VariantXiangqi)
\r
11041 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11042 if(gameInfo.variant == VariantKnightmate)
\r
11043 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11046 if (gameMode == IcsExamining) {
\r
11047 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11048 PieceToChar(selection), AAA + x, ONE + y);
\r
11051 boards[0][y][x] = selection;
\r
11052 DrawPosition(FALSE, boards[0]);
\r
11060 DropMenuEvent(selection, x, y)
\r
11061 ChessSquare selection;
\r
11064 ChessMove moveType;
\r
11066 switch (gameMode) {
\r
11067 case IcsPlayingWhite:
\r
11068 case MachinePlaysBlack:
\r
11069 if (!WhiteOnMove(currentMove)) {
\r
11070 DisplayMoveError(_("It is Black's turn"));
\r
11073 moveType = WhiteDrop;
\r
11075 case IcsPlayingBlack:
\r
11076 case MachinePlaysWhite:
\r
11077 if (WhiteOnMove(currentMove)) {
\r
11078 DisplayMoveError(_("It is White's turn"));
\r
11081 moveType = BlackDrop;
\r
11084 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11090 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11091 selection = (ChessSquare) ((int) selection
\r
11092 + (int) BlackPawn - (int) WhitePawn);
\r
11094 if (boards[currentMove][y][x] != EmptySquare) {
\r
11095 DisplayMoveError(_("That square is occupied"));
\r
11099 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11105 /* Accept a pending offer of any kind from opponent */
\r
11107 if (appData.icsActive) {
\r
11108 SendToICS(ics_prefix);
\r
11109 SendToICS("accept\n");
\r
11110 } else if (cmailMsgLoaded) {
\r
11111 if (currentMove == cmailOldMove &&
\r
11112 commentList[cmailOldMove] != NULL &&
\r
11113 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11114 "Black offers a draw" : "White offers a draw")) {
\r
11116 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11117 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11119 DisplayError(_("There is no pending offer on this move"), 0);
\r
11120 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11123 /* Not used for offers from chess program */
\r
11130 /* Decline a pending offer of any kind from opponent */
\r
11132 if (appData.icsActive) {
\r
11133 SendToICS(ics_prefix);
\r
11134 SendToICS("decline\n");
\r
11135 } else if (cmailMsgLoaded) {
\r
11136 if (currentMove == cmailOldMove &&
\r
11137 commentList[cmailOldMove] != NULL &&
\r
11138 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11139 "Black offers a draw" : "White offers a draw")) {
\r
11141 AppendComment(cmailOldMove, "Draw declined");
\r
11142 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11143 #endif /*NOTDEF*/
\r
11145 DisplayError(_("There is no pending offer on this move"), 0);
\r
11148 /* Not used for offers from chess program */
\r
11155 /* Issue ICS rematch command */
\r
11156 if (appData.icsActive) {
\r
11157 SendToICS(ics_prefix);
\r
11158 SendToICS("rematch\n");
\r
11165 /* Call your opponent's flag (claim a win on time) */
\r
11166 if (appData.icsActive) {
\r
11167 SendToICS(ics_prefix);
\r
11168 SendToICS("flag\n");
\r
11170 switch (gameMode) {
\r
11173 case MachinePlaysWhite:
\r
11176 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11179 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11181 DisplayError(_("Your opponent is not out of time"), 0);
\r
11184 case MachinePlaysBlack:
\r
11187 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11190 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11192 DisplayError(_("Your opponent is not out of time"), 0);
\r
11202 /* Offer draw or accept pending draw offer from opponent */
\r
11204 if (appData.icsActive) {
\r
11205 /* Note: tournament rules require draw offers to be
\r
11206 made after you make your move but before you punch
\r
11207 your clock. Currently ICS doesn't let you do that;
\r
11208 instead, you immediately punch your clock after making
\r
11209 a move, but you can offer a draw at any time. */
\r
11211 SendToICS(ics_prefix);
\r
11212 SendToICS("draw\n");
\r
11213 } else if (cmailMsgLoaded) {
\r
11214 if (currentMove == cmailOldMove &&
\r
11215 commentList[cmailOldMove] != NULL &&
\r
11216 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11217 "Black offers a draw" : "White offers a draw")) {
\r
11218 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11219 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11220 } else if (currentMove == cmailOldMove + 1) {
\r
11221 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11222 "White offers a draw" : "Black offers a draw";
\r
11223 AppendComment(currentMove, offer);
\r
11224 DisplayComment(currentMove - 1, offer);
\r
11225 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11227 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11228 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11230 } else if (first.offeredDraw) {
\r
11231 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11233 if (first.sendDrawOffers) {
\r
11234 SendToProgram("draw\n", &first);
\r
11235 userOfferedDraw = TRUE;
\r
11243 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11245 if (appData.icsActive) {
\r
11246 SendToICS(ics_prefix);
\r
11247 SendToICS("adjourn\n");
\r
11249 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11257 /* Offer Abort or accept pending Abort offer from opponent */
\r
11259 if (appData.icsActive) {
\r
11260 SendToICS(ics_prefix);
\r
11261 SendToICS("abort\n");
\r
11263 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11270 /* Resign. You can do this even if it's not your turn. */
\r
11272 if (appData.icsActive) {
\r
11273 SendToICS(ics_prefix);
\r
11274 SendToICS("resign\n");
\r
11276 switch (gameMode) {
\r
11277 case MachinePlaysWhite:
\r
11278 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11280 case MachinePlaysBlack:
\r
11281 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11284 if (cmailMsgLoaded) {
\r
11286 if (WhiteOnMove(cmailOldMove)) {
\r
11287 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11289 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11291 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11302 StopObservingEvent()
\r
11304 /* Stop observing current games */
\r
11305 SendToICS(ics_prefix);
\r
11306 SendToICS("unobserve\n");
\r
11310 StopExaminingEvent()
\r
11312 /* Stop observing current game */
\r
11313 SendToICS(ics_prefix);
\r
11314 SendToICS("unexamine\n");
\r
11318 ForwardInner(target)
\r
11323 if (appData.debugMode)
\r
11324 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11325 target, currentMove, forwardMostMove);
\r
11327 if (gameMode == EditPosition)
\r
11330 if (gameMode == PlayFromGameFile && !pausing)
\r
11333 if (gameMode == IcsExamining && pausing)
\r
11334 limit = pauseExamForwardMostMove;
\r
11336 limit = forwardMostMove;
\r
11338 if (target > limit) target = limit;
\r
11340 if (target > 0 && moveList[target - 1][0]) {
\r
11341 int fromX, fromY, toX, toY;
\r
11342 toX = moveList[target - 1][2] - AAA;
\r
11343 toY = moveList[target - 1][3] - ONE;
\r
11344 if (moveList[target - 1][1] == '@') {
\r
11345 if (appData.highlightLastMove) {
\r
11346 SetHighlights(-1, -1, toX, toY);
\r
11349 fromX = moveList[target - 1][0] - AAA;
\r
11350 fromY = moveList[target - 1][1] - ONE;
\r
11351 if (target == currentMove + 1) {
\r
11352 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11354 if (appData.highlightLastMove) {
\r
11355 SetHighlights(fromX, fromY, toX, toY);
\r
11359 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11360 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11361 gameMode == AnalyzeFile) {
\r
11362 while (currentMove < target) {
\r
11363 SendMoveToProgram(currentMove++, &first);
\r
11366 currentMove = target;
\r
11369 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11370 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11371 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11373 DisplayBothClocks();
\r
11374 DisplayMove(currentMove - 1);
\r
11375 DrawPosition(FALSE, boards[currentMove]);
\r
11376 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11377 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11378 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11386 if (gameMode == IcsExamining && !pausing) {
\r
11387 SendToICS(ics_prefix);
\r
11388 SendToICS("forward\n");
\r
11390 ForwardInner(currentMove + 1);
\r
11397 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11398 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11399 * the remaining moves to the engine. Otherwise we get analysis output
\r
11400 * after each move.
\r
11402 if (first.analysisSupport) {
\r
11403 SendToProgram("exit\nforce\n", &first);
\r
11404 first.analyzing = FALSE;
\r
11408 if (gameMode == IcsExamining && !pausing) {
\r
11409 SendToICS(ics_prefix);
\r
11410 SendToICS("forward 999999\n");
\r
11412 ForwardInner(forwardMostMove);
\r
11415 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11416 /* we have fed all the moves, so reactivate analysis mode */
\r
11417 SendToProgram("analyze\n", &first);
\r
11418 first.analyzing = TRUE;
\r
11419 /*first.maybeThinking = TRUE;*/
\r
11420 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11425 BackwardInner(target)
\r
11428 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11430 if (appData.debugMode)
\r
11431 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11432 target, currentMove, forwardMostMove);
\r
11434 if (gameMode == EditPosition) return;
\r
11435 if (currentMove <= backwardMostMove) {
\r
11436 ClearHighlights();
\r
11437 DrawPosition(full_redraw, boards[currentMove]);
\r
11440 if (gameMode == PlayFromGameFile && !pausing)
\r
11443 if (moveList[target][0]) {
\r
11444 int fromX, fromY, toX, toY;
\r
11445 toX = moveList[target][2] - AAA;
\r
11446 toY = moveList[target][3] - ONE;
\r
11447 if (moveList[target][1] == '@') {
\r
11448 if (appData.highlightLastMove) {
\r
11449 SetHighlights(-1, -1, toX, toY);
\r
11452 fromX = moveList[target][0] - AAA;
\r
11453 fromY = moveList[target][1] - ONE;
\r
11454 if (target == currentMove - 1) {
\r
11455 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11457 if (appData.highlightLastMove) {
\r
11458 SetHighlights(fromX, fromY, toX, toY);
\r
11462 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11463 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11464 while (currentMove > target) {
\r
11465 SendToProgram("undo\n", &first);
\r
11469 currentMove = target;
\r
11472 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11473 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11474 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11476 DisplayBothClocks();
\r
11477 DisplayMove(currentMove - 1);
\r
11478 DrawPosition(full_redraw, boards[currentMove]);
\r
11479 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11480 // [HGM] PV info: routine tests if comment empty
\r
11481 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11487 if (gameMode == IcsExamining && !pausing) {
\r
11488 SendToICS(ics_prefix);
\r
11489 SendToICS("backward\n");
\r
11491 BackwardInner(currentMove - 1);
\r
11498 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11499 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11500 * all the moves. Otherwise we get analysis output after each undo.
\r
11502 if (first.analysisSupport) {
\r
11503 SendToProgram("exit\nforce\n", &first);
\r
11504 first.analyzing = FALSE;
\r
11508 if (gameMode == IcsExamining && !pausing) {
\r
11509 SendToICS(ics_prefix);
\r
11510 SendToICS("backward 999999\n");
\r
11512 BackwardInner(backwardMostMove);
\r
11515 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11516 /* we have fed all the moves, so reactivate analysis mode */
\r
11517 SendToProgram("analyze\n", &first);
\r
11518 first.analyzing = TRUE;
\r
11519 /*first.maybeThinking = TRUE;*/
\r
11520 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11525 ToNrEvent(int to)
\r
11527 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11528 if (to >= forwardMostMove) to = forwardMostMove;
\r
11529 if (to <= backwardMostMove) to = backwardMostMove;
\r
11530 if (to < currentMove) {
\r
11531 BackwardInner(to);
\r
11533 ForwardInner(to);
\r
11540 if (gameMode != IcsExamining) {
\r
11541 DisplayError(_("You are not examining a game"), 0);
\r
11545 DisplayError(_("You can't revert while pausing"), 0);
\r
11548 SendToICS(ics_prefix);
\r
11549 SendToICS("revert\n");
\r
11553 RetractMoveEvent()
\r
11555 switch (gameMode) {
\r
11556 case MachinePlaysWhite:
\r
11557 case MachinePlaysBlack:
\r
11558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11562 if (forwardMostMove < 2) return;
\r
11563 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11564 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11565 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11566 DisplayBothClocks();
\r
11567 DisplayMove(currentMove - 1);
\r
11568 ClearHighlights();/*!! could figure this out*/
\r
11569 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11570 SendToProgram("remove\n", &first);
\r
11571 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11574 case BeginningOfGame:
\r
11578 case IcsPlayingWhite:
\r
11579 case IcsPlayingBlack:
\r
11580 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11581 SendToICS(ics_prefix);
\r
11582 SendToICS("takeback 2\n");
\r
11584 SendToICS(ics_prefix);
\r
11585 SendToICS("takeback 1\n");
\r
11594 ChessProgramState *cps;
\r
11596 switch (gameMode) {
\r
11597 case MachinePlaysWhite:
\r
11598 if (!WhiteOnMove(forwardMostMove)) {
\r
11599 DisplayError(_("It is your turn"), 0);
\r
11604 case MachinePlaysBlack:
\r
11605 if (WhiteOnMove(forwardMostMove)) {
\r
11606 DisplayError(_("It is your turn"), 0);
\r
11611 case TwoMachinesPlay:
\r
11612 if (WhiteOnMove(forwardMostMove) ==
\r
11613 (first.twoMachinesColor[0] == 'w')) {
\r
11619 case BeginningOfGame:
\r
11623 SendToProgram("?\n", cps);
\r
11627 TruncateGameEvent()
\r
11630 if (gameMode != EditGame) return;
\r
11637 if (forwardMostMove > currentMove) {
\r
11638 if (gameInfo.resultDetails != NULL) {
\r
11639 free(gameInfo.resultDetails);
\r
11640 gameInfo.resultDetails = NULL;
\r
11641 gameInfo.result = GameUnfinished;
\r
11643 forwardMostMove = currentMove;
\r
11644 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11652 if (appData.noChessProgram) return;
\r
11653 switch (gameMode) {
\r
11654 case MachinePlaysWhite:
\r
11655 if (WhiteOnMove(forwardMostMove)) {
\r
11656 DisplayError(_("Wait until your turn"), 0);
\r
11660 case BeginningOfGame:
\r
11661 case MachinePlaysBlack:
\r
11662 if (!WhiteOnMove(forwardMostMove)) {
\r
11663 DisplayError(_("Wait until your turn"), 0);
\r
11668 DisplayError(_("No hint available"), 0);
\r
11671 SendToProgram("hint\n", &first);
\r
11672 hintRequested = TRUE;
\r
11678 if (appData.noChessProgram) return;
\r
11679 switch (gameMode) {
\r
11680 case MachinePlaysWhite:
\r
11681 if (WhiteOnMove(forwardMostMove)) {
\r
11682 DisplayError(_("Wait until your turn"), 0);
\r
11686 case BeginningOfGame:
\r
11687 case MachinePlaysBlack:
\r
11688 if (!WhiteOnMove(forwardMostMove)) {
\r
11689 DisplayError(_("Wait until your turn"), 0);
\r
11693 case EditPosition:
\r
11694 EditPositionDone();
\r
11696 case TwoMachinesPlay:
\r
11701 SendToProgram("bk\n", &first);
\r
11702 bookOutput[0] = NULLCHAR;
\r
11703 bookRequested = TRUE;
\r
11709 char *tags = PGNTags(&gameInfo);
\r
11710 TagsPopUp(tags, CmailMsg());
\r
11714 /* end button procedures */
\r
11717 PrintPosition(fp, move)
\r
11723 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11724 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11725 char c = PieceToChar(boards[move][i][j]);
\r
11726 fputc(c == 'x' ? '.' : c, fp);
\r
11727 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11730 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11731 fprintf(fp, "white to play\n");
\r
11733 fprintf(fp, "black to play\n");
\r
11737 PrintOpponents(fp)
\r
11740 if (gameInfo.white != NULL) {
\r
11741 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11743 fprintf(fp, "\n");
\r
11747 /* Find last component of program's own name, using some heuristics */
\r
11749 TidyProgramName(prog, host, buf)
\r
11750 char *prog, *host, buf[MSG_SIZ];
\r
11753 int local = (strcmp(host, "localhost") == 0);
\r
11754 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11756 while (*p == ' ') p++;
\r
11759 if (*prog == '"' || *prog == '\'') {
\r
11760 q = strchr(prog + 1, *prog);
\r
11762 q = strchr(prog, ' ');
\r
11764 if (q == NULL) q = prog + strlen(prog);
\r
11766 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11768 if(p == prog && *p == '"') p++;
\r
11769 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11770 memcpy(buf, p, q - p);
\r
11771 buf[q - p] = NULLCHAR;
\r
11773 strcat(buf, "@");
\r
11774 strcat(buf, host);
\r
11779 TimeControlTagValue()
\r
11781 char buf[MSG_SIZ];
\r
11782 if (!appData.clockMode) {
\r
11783 strcpy(buf, "-");
\r
11784 } else if (movesPerSession > 0) {
\r
11785 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11786 } else if (timeIncrement == 0) {
\r
11787 sprintf(buf, "%ld", timeControl/1000);
\r
11789 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11791 return StrSave(buf);
\r
11797 /* This routine is used only for certain modes */
\r
11798 VariantClass v = gameInfo.variant;
\r
11799 ClearGameInfo(&gameInfo);
\r
11800 gameInfo.variant = v;
\r
11802 switch (gameMode) {
\r
11803 case MachinePlaysWhite:
\r
11804 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11805 gameInfo.site = StrSave(HostName());
\r
11806 gameInfo.date = PGNDate();
\r
11807 gameInfo.round = StrSave("-");
\r
11808 gameInfo.white = StrSave(first.tidy);
\r
11809 gameInfo.black = StrSave(UserName());
\r
11810 gameInfo.timeControl = TimeControlTagValue();
\r
11813 case MachinePlaysBlack:
\r
11814 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11815 gameInfo.site = StrSave(HostName());
\r
11816 gameInfo.date = PGNDate();
\r
11817 gameInfo.round = StrSave("-");
\r
11818 gameInfo.white = StrSave(UserName());
\r
11819 gameInfo.black = StrSave(first.tidy);
\r
11820 gameInfo.timeControl = TimeControlTagValue();
\r
11823 case TwoMachinesPlay:
\r
11824 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11825 gameInfo.site = StrSave(HostName());
\r
11826 gameInfo.date = PGNDate();
\r
11827 if (matchGame > 0) {
\r
11828 char buf[MSG_SIZ];
\r
11829 sprintf(buf, "%d", matchGame);
\r
11830 gameInfo.round = StrSave(buf);
\r
11832 gameInfo.round = StrSave("-");
\r
11834 if (first.twoMachinesColor[0] == 'w') {
\r
11835 gameInfo.white = StrSave(first.tidy);
\r
11836 gameInfo.black = StrSave(second.tidy);
\r
11838 gameInfo.white = StrSave(second.tidy);
\r
11839 gameInfo.black = StrSave(first.tidy);
\r
11841 gameInfo.timeControl = TimeControlTagValue();
\r
11845 gameInfo.event = StrSave("Edited game");
\r
11846 gameInfo.site = StrSave(HostName());
\r
11847 gameInfo.date = PGNDate();
\r
11848 gameInfo.round = StrSave("-");
\r
11849 gameInfo.white = StrSave("-");
\r
11850 gameInfo.black = StrSave("-");
\r
11853 case EditPosition:
\r
11854 gameInfo.event = StrSave("Edited position");
\r
11855 gameInfo.site = StrSave(HostName());
\r
11856 gameInfo.date = PGNDate();
\r
11857 gameInfo.round = StrSave("-");
\r
11858 gameInfo.white = StrSave("-");
\r
11859 gameInfo.black = StrSave("-");
\r
11862 case IcsPlayingWhite:
\r
11863 case IcsPlayingBlack:
\r
11864 case IcsObserving:
\r
11865 case IcsExamining:
\r
11868 case PlayFromGameFile:
\r
11869 gameInfo.event = StrSave("Game from non-PGN file");
\r
11870 gameInfo.site = StrSave(HostName());
\r
11871 gameInfo.date = PGNDate();
\r
11872 gameInfo.round = StrSave("-");
\r
11873 gameInfo.white = StrSave("?");
\r
11874 gameInfo.black = StrSave("?");
\r
11883 ReplaceComment(index, text)
\r
11889 while (*text == '\n') text++;
\r
11890 len = strlen(text);
\r
11891 while (len > 0 && text[len - 1] == '\n') len--;
\r
11893 if (commentList[index] != NULL)
\r
11894 free(commentList[index]);
\r
11897 commentList[index] = NULL;
\r
11900 commentList[index] = (char *) malloc(len + 2);
\r
11901 strncpy(commentList[index], text, len);
\r
11902 commentList[index][len] = '\n';
\r
11903 commentList[index][len + 1] = NULLCHAR;
\r
11916 if (ch == '\r') continue;
\r
11918 } while (ch != '\0');
\r
11922 AppendComment(index, text)
\r
11929 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11932 while (*text == '\n') text++;
\r
11933 len = strlen(text);
\r
11934 while (len > 0 && text[len - 1] == '\n') len--;
\r
11936 if (len == 0) return;
\r
11938 if (commentList[index] != NULL) {
\r
11939 old = commentList[index];
\r
11940 oldlen = strlen(old);
\r
11941 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11942 strcpy(commentList[index], old);
\r
11944 strncpy(&commentList[index][oldlen], text, len);
\r
11945 commentList[index][oldlen + len] = '\n';
\r
11946 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11948 commentList[index] = (char *) malloc(len + 2);
\r
11949 strncpy(commentList[index], text, len);
\r
11950 commentList[index][len] = '\n';
\r
11951 commentList[index][len + 1] = NULLCHAR;
\r
11955 static char * FindStr( char * text, char * sub_text )
\r
11957 char * result = strstr( text, sub_text );
\r
11959 if( result != NULL ) {
\r
11960 result += strlen( sub_text );
\r
11966 /* [AS] Try to extract PV info from PGN comment */
\r
11967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11968 char *GetInfoFromComment( int index, char * text )
\r
11970 char * sep = text;
\r
11972 if( text != NULL && index > 0 ) {
\r
11975 int time = -1, sec = 0, deci;
\r
11976 char * s_eval = FindStr( text, "[%eval " );
\r
11977 char * s_emt = FindStr( text, "[%emt " );
\r
11979 if( s_eval != NULL || s_emt != NULL ) {
\r
11983 if( s_eval != NULL ) {
\r
11984 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11988 if( delim != ']' ) {
\r
11993 if( s_emt != NULL ) {
\r
11997 /* We expect something like: [+|-]nnn.nn/dd */
\r
11998 int score_lo = 0;
\r
12000 sep = strchr( text, '/' );
\r
12001 if( sep == NULL || sep < (text+4) ) {
\r
12005 time = -1; sec = -1; deci = -1;
\r
12006 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12007 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12008 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12009 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12013 if( score_lo < 0 || score_lo >= 100 ) {
\r
12017 if(sec >= 0) time = 600*time + 10*sec; else
\r
12018 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12020 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12022 /* [HGM] PV time: now locate end of PV info */
\r
12023 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12025 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12027 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12029 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12030 while(*sep == ' ') sep++;
\r
12033 if( depth <= 0 ) {
\r
12041 pvInfoList[index-1].depth = depth;
\r
12042 pvInfoList[index-1].score = score;
\r
12043 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12049 SendToProgram(message, cps)
\r
12051 ChessProgramState *cps;
\r
12053 int count, outCount, error;
\r
12054 char buf[MSG_SIZ];
\r
12056 if (cps->pr == NULL) return;
\r
12059 if (appData.debugMode) {
\r
12061 GetTimeMark(&now);
\r
12062 fprintf(debugFP, "%ld >%-6s: %s",
\r
12063 SubtractTimeMarks(&now, &programStartTime),
\r
12064 cps->which, message);
\r
12067 count = strlen(message);
\r
12068 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12069 if (outCount < count && !exiting
\r
12070 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12071 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12072 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12073 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12074 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12075 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12077 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12079 gameInfo.resultDetails = buf;
\r
12081 DisplayFatalError(buf, error, 1);
\r
12086 ReceiveFromProgram(isr, closure, message, count, error)
\r
12087 InputSourceRef isr;
\r
12088 VOIDSTAR closure;
\r
12094 char buf[MSG_SIZ];
\r
12095 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12097 if (isr != cps->isr) return; /* Killed intentionally */
\r
12098 if (count <= 0) {
\r
12099 if (count == 0) {
\r
12101 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12102 cps->which, cps->program);
\r
12103 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12104 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12105 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12106 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12108 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12110 gameInfo.resultDetails = buf;
\r
12112 RemoveInputSource(cps->isr);
\r
12113 DisplayFatalError(buf, 0, 1);
\r
12116 _("Error reading from %s chess program (%s)"),
\r
12117 cps->which, cps->program);
\r
12118 RemoveInputSource(cps->isr);
\r
12120 /* [AS] Program is misbehaving badly... kill it */
\r
12121 if( count == -2 ) {
\r
12122 DestroyChildProcess( cps->pr, 9 );
\r
12123 cps->pr = NoProc;
\r
12126 DisplayFatalError(buf, error, 1);
\r
12131 if ((end_str = strchr(message, '\r')) != NULL)
\r
12132 *end_str = NULLCHAR;
\r
12133 if ((end_str = strchr(message, '\n')) != NULL)
\r
12134 *end_str = NULLCHAR;
\r
12136 if (appData.debugMode) {
\r
12137 TimeMark now; int print = 1;
\r
12138 char *quote = ""; char c; int i;
\r
12140 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12141 char start = message[0];
\r
12142 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12143 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12144 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12145 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12146 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12147 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12148 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12149 { quote = "# "; print = (appData.engineComments == 2); }
\r
12150 message[0] = start; // restore original message
\r
12153 GetTimeMark(&now);
\r
12154 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12155 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12161 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12162 if (appData.icsEngineAnalyze) {
\r
12163 if (strstr(message, "whisper") != NULL ||
\r
12164 strstr(message, "kibitz") != NULL ||
\r
12165 strstr(message, "tellics") != NULL) return;
\r
12168 HandleMachineMove(message, cps);
\r
12173 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12174 ChessProgramState *cps;
\r
12175 int mps, inc, sd, st;
\r
12178 char buf[MSG_SIZ];
\r
12181 if( timeControl_2 > 0 ) {
\r
12182 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12183 tc = timeControl_2;
\r
12186 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12187 inc /= cps->timeOdds;
\r
12188 st /= cps->timeOdds;
\r
12190 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12193 /* Set exact time per move, normally using st command */
\r
12194 if (cps->stKludge) {
\r
12195 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12196 seconds = st % 60;
\r
12197 if (seconds == 0) {
\r
12198 sprintf(buf, "level 1 %d\n", st/60);
\r
12200 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12203 sprintf(buf, "st %d\n", st);
\r
12206 /* Set conventional or incremental time control, using level command */
\r
12207 if (seconds == 0) {
\r
12208 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12209 Fixed in later versions, but still avoid :seconds
\r
12210 when seconds is 0. */
\r
12211 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12213 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12214 seconds, inc/1000);
\r
12217 SendToProgram(buf, cps);
\r
12219 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12220 /* Orthogonally, limit search to given depth */
\r
12222 if (cps->sdKludge) {
\r
12223 sprintf(buf, "depth\n%d\n", sd);
\r
12225 sprintf(buf, "sd %d\n", sd);
\r
12227 SendToProgram(buf, cps);
\r
12230 if(cps->nps > 0) { /* [HGM] nps */
\r
12231 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12233 sprintf(buf, "nps %d\n", cps->nps);
\r
12234 SendToProgram(buf, cps);
\r
12239 ChessProgramState *WhitePlayer()
\r
12240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12242 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12243 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12249 SendTimeRemaining(cps, machineWhite)
\r
12250 ChessProgramState *cps;
\r
12251 int /*boolean*/ machineWhite;
\r
12253 char message[MSG_SIZ];
\r
12254 long time, otime;
\r
12256 /* Note: this routine must be called when the clocks are stopped
\r
12257 or when they have *just* been set or switched; otherwise
\r
12258 it will be off by the time since the current tick started.
\r
12260 if (machineWhite) {
\r
12261 time = whiteTimeRemaining / 10;
\r
12262 otime = blackTimeRemaining / 10;
\r
12264 time = blackTimeRemaining / 10;
\r
12265 otime = whiteTimeRemaining / 10;
\r
12267 /* [HGM] translate opponent's time by time-odds factor */
\r
12268 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12269 if (appData.debugMode) {
\r
12270 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12273 if (time <= 0) time = 1;
\r
12274 if (otime <= 0) otime = 1;
\r
12276 sprintf(message, "time %ld\n", time);
\r
12277 SendToProgram(message, cps);
\r
12279 sprintf(message, "otim %ld\n", otime);
\r
12280 SendToProgram(message, cps);
\r
12284 BoolFeature(p, name, loc, cps)
\r
12288 ChessProgramState *cps;
\r
12290 char buf[MSG_SIZ];
\r
12291 int len = strlen(name);
\r
12293 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12295 sscanf(*p, "%d", &val);
\r
12296 *loc = (val != 0);
\r
12297 while (**p && **p != ' ') (*p)++;
\r
12298 sprintf(buf, "accepted %s\n", name);
\r
12299 SendToProgram(buf, cps);
\r
12306 IntFeature(p, name, loc, cps)
\r
12310 ChessProgramState *cps;
\r
12312 char buf[MSG_SIZ];
\r
12313 int len = strlen(name);
\r
12314 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12316 sscanf(*p, "%d", loc);
\r
12317 while (**p && **p != ' ') (*p)++;
\r
12318 sprintf(buf, "accepted %s\n", name);
\r
12319 SendToProgram(buf, cps);
\r
12326 StringFeature(p, name, loc, cps)
\r
12330 ChessProgramState *cps;
\r
12332 char buf[MSG_SIZ];
\r
12333 int len = strlen(name);
\r
12334 if (strncmp((*p), name, len) == 0
\r
12335 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12337 sscanf(*p, "%[^\"]", loc);
\r
12338 while (**p && **p != '\"') (*p)++;
\r
12339 if (**p == '\"') (*p)++;
\r
12340 sprintf(buf, "accepted %s\n", name);
\r
12341 SendToProgram(buf, cps);
\r
12348 ParseOption(Option *opt, ChessProgramState *cps)
\r
12349 // [HGM] options: process the string that defines an engine option, and determine
\r
12350 // name, type, default value, and allowed value range
\r
12352 char *p, *q, buf[MSG_SIZ];
\r
12353 int n, min = (-1)<<31, max = 1<<31, def;
\r
12355 if(p = strstr(opt->name, " -spin ")) {
\r
12356 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12357 if(max < min) max = min; // enforce consistency
\r
12358 if(def < min) def = min;
\r
12359 if(def > max) def = max;
\r
12360 opt->value = def;
\r
12363 opt->type = Spin;
\r
12364 } else if(p = strstr(opt->name, " -string ")) {
\r
12365 opt->textValue = p+9;
\r
12366 opt->type = TextBox;
\r
12367 } else if(p = strstr(opt->name, " -check ")) {
\r
12368 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12369 opt->value = (def != 0);
\r
12370 opt->type = CheckBox;
\r
12371 } else if(p = strstr(opt->name, " -combo ")) {
\r
12372 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12373 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12374 opt->value = n = 0;
\r
12375 while(q = StrStr(q, " /// ")) {
\r
12376 n++; *q = 0; // count choices, and null-terminate each of them
\r
12378 if(*q == '*') { // remember default, which is marked with * prefix
\r
12382 cps->comboList[cps->comboCnt++] = q;
\r
12384 cps->comboList[cps->comboCnt++] = NULL;
\r
12385 opt->max = n + 1;
\r
12386 opt->type = ComboBox;
\r
12387 } else if(p = strstr(opt->name, " -button")) {
\r
12388 opt->type = Button;
\r
12389 } else if(p = strstr(opt->name, " -save")) {
\r
12390 opt->type = SaveButton;
\r
12391 } else return FALSE;
\r
12392 *p = 0; // terminate option name
\r
12393 // now look if the command-line options define a setting for this engine option.
\r
12394 if(cps->optionSettings && cps->optionSettings[0])
\r
12395 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12396 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12397 sprintf(buf, "option %s", p);
\r
12398 if(p = strstr(buf, ",")) *p = 0;
\r
12399 strcat(buf, "\n");
\r
12400 SendToProgram(buf, cps);
\r
12406 FeatureDone(cps, val)
\r
12407 ChessProgramState* cps;
\r
12410 DelayedEventCallback cb = GetDelayedEvent();
\r
12411 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12412 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12413 CancelDelayedEvent();
\r
12414 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12416 cps->initDone = val;
\r
12419 /* Parse feature command from engine */
\r
12421 ParseFeatures(args, cps)
\r
12423 ChessProgramState *cps;
\r
12428 char buf[MSG_SIZ];
\r
12431 while (*p == ' ') p++;
\r
12432 if (*p == NULLCHAR) return;
\r
12434 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12435 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12436 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12437 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12438 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12439 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12440 /* Engine can disable reuse, but can't enable it if user said no */
\r
12441 if (!val) cps->reuse = FALSE;
\r
12444 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12445 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12446 if (gameMode == TwoMachinesPlay) {
\r
12447 DisplayTwoMachinesTitle();
\r
12449 DisplayTitle("");
\r
12453 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12454 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12455 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12456 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12457 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12458 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12459 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12460 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12461 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12462 if (IntFeature(&p, "done", &val, cps)) {
\r
12463 FeatureDone(cps, val);
\r
12466 /* Added by Tord: */
\r
12467 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12468 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12469 /* End of additions by Tord */
\r
12471 /* [HGM] added features: */
\r
12472 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12473 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12474 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12475 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12476 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12477 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12478 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12479 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12480 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12481 cps->nrOptions--;
\r
12482 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12483 DisplayError(buf, 0);
\r
12487 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12488 /* End of additions by HGM */
\r
12490 /* unknown feature: complain and skip */
\r
12492 while (*q && *q != '=') q++;
\r
12493 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12494 SendToProgram(buf, cps);
\r
12498 if (*p == '\"') {
\r
12500 while (*p && *p != '\"') p++;
\r
12501 if (*p == '\"') p++;
\r
12503 while (*p && *p != ' ') p++;
\r
12511 PeriodicUpdatesEvent(newState)
\r
12514 if (newState == appData.periodicUpdates)
\r
12517 appData.periodicUpdates=newState;
\r
12519 /* Display type changes, so update it now */
\r
12520 DisplayAnalysis();
\r
12522 /* Get the ball rolling again... */
\r
12524 AnalysisPeriodicEvent(1);
\r
12525 StartAnalysisClock();
\r
12530 PonderNextMoveEvent(newState)
\r
12533 if (newState == appData.ponderNextMove) return;
\r
12534 if (gameMode == EditPosition) EditPositionDone();
\r
12536 SendToProgram("hard\n", &first);
\r
12537 if (gameMode == TwoMachinesPlay) {
\r
12538 SendToProgram("hard\n", &second);
\r
12541 SendToProgram("easy\n", &first);
\r
12542 thinkOutput[0] = NULLCHAR;
\r
12543 if (gameMode == TwoMachinesPlay) {
\r
12544 SendToProgram("easy\n", &second);
\r
12547 appData.ponderNextMove = newState;
\r
12551 NewSettingEvent(option, command, value)
\r
12553 int option, value;
\r
12555 char buf[MSG_SIZ];
\r
12557 if (gameMode == EditPosition) EditPositionDone();
\r
12558 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12559 SendToProgram(buf, &first);
\r
12560 if (gameMode == TwoMachinesPlay) {
\r
12561 SendToProgram(buf, &second);
\r
12566 ShowThinkingEvent()
\r
12567 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12569 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12570 int newState = appData.showThinking
\r
12571 // [HGM] thinking: other features now need thinking output as well
\r
12572 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12574 if (oldState == newState) return;
\r
12575 oldState = newState;
\r
12576 if (gameMode == EditPosition) EditPositionDone();
\r
12578 SendToProgram("post\n", &first);
\r
12579 if (gameMode == TwoMachinesPlay) {
\r
12580 SendToProgram("post\n", &second);
\r
12583 SendToProgram("nopost\n", &first);
\r
12584 thinkOutput[0] = NULLCHAR;
\r
12585 if (gameMode == TwoMachinesPlay) {
\r
12586 SendToProgram("nopost\n", &second);
\r
12589 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12593 AskQuestionEvent(title, question, replyPrefix, which)
\r
12594 char *title; char *question; char *replyPrefix; char *which;
\r
12596 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12597 if (pr == NoProc) return;
\r
12598 AskQuestion(title, question, replyPrefix, pr);
\r
12602 DisplayMove(moveNumber)
\r
12605 char message[MSG_SIZ];
\r
12606 char res[MSG_SIZ];
\r
12607 char cpThinkOutput[MSG_SIZ];
\r
12609 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12611 if (moveNumber == forwardMostMove - 1 ||
\r
12612 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12614 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12616 if (strchr(cpThinkOutput, '\n')) {
\r
12617 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12620 *cpThinkOutput = NULLCHAR;
\r
12623 /* [AS] Hide thinking from human user */
\r
12624 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12625 *cpThinkOutput = NULLCHAR;
\r
12626 if( thinkOutput[0] != NULLCHAR ) {
\r
12629 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12630 cpThinkOutput[i] = '.';
\r
12632 cpThinkOutput[i] = NULLCHAR;
\r
12633 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12637 if (moveNumber == forwardMostMove - 1 &&
\r
12638 gameInfo.resultDetails != NULL) {
\r
12639 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12640 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12642 sprintf(res, " {%s} %s",
\r
12643 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12646 res[0] = NULLCHAR;
\r
12649 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12650 DisplayMessage(res, cpThinkOutput);
\r
12652 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12653 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12654 parseList[moveNumber], res);
\r
12655 DisplayMessage(message, cpThinkOutput);
\r
12660 DisplayAnalysisText(text)
\r
12663 char buf[MSG_SIZ];
\r
12665 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12666 || appData.icsEngineAnalyze) {
\r
12667 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12668 AnalysisPopUp(buf, text);
\r
12673 only_one_move(str)
\r
12676 while (*str && isspace(*str)) ++str;
\r
12677 while (*str && !isspace(*str)) ++str;
\r
12678 if (!*str) return 1;
\r
12679 while (*str && isspace(*str)) ++str;
\r
12680 if (!*str) return 1;
\r
12685 DisplayAnalysis()
\r
12687 char buf[MSG_SIZ];
\r
12688 char lst[MSG_SIZ / 2];
\r
12690 static char *xtra[] = { "", " (--)", " (++)" };
\r
12693 if (programStats.time == 0) {
\r
12694 programStats.time = 1;
\r
12697 if (programStats.got_only_move) {
\r
12698 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12700 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12702 nps = (u64ToDouble(programStats.nodes) /
\r
12703 ((double)programStats.time /100.0));
\r
12705 cs = programStats.time % 100;
\r
12706 s = programStats.time / 100;
\r
12707 h = (s / (60*60));
\r
12712 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12713 if (programStats.move_name[0] != NULLCHAR) {
\r
12714 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12715 programStats.depth,
\r
12716 programStats.nr_moves-programStats.moves_left,
\r
12717 programStats.nr_moves, programStats.move_name,
\r
12718 ((float)programStats.score)/100.0, lst,
\r
12719 only_one_move(lst)?
\r
12720 xtra[programStats.got_fail] : "",
\r
12721 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12723 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12724 programStats.depth,
\r
12725 programStats.nr_moves-programStats.moves_left,
\r
12726 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12728 only_one_move(lst)?
\r
12729 xtra[programStats.got_fail] : "",
\r
12730 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12733 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12734 programStats.depth,
\r
12735 ((float)programStats.score)/100.0,
\r
12737 only_one_move(lst)?
\r
12738 xtra[programStats.got_fail] : "",
\r
12739 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12742 DisplayAnalysisText(buf);
\r
12746 DisplayComment(moveNumber, text)
\r
12750 char title[MSG_SIZ];
\r
12751 char buf[8000]; // comment can be long!
\r
12752 int score, depth;
\r
12754 if( appData.autoDisplayComment ) {
\r
12755 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12756 strcpy(title, "Comment");
\r
12758 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12759 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12760 parseList[moveNumber]);
\r
12762 // [HGM] PV info: display PV info together with (or as) comment
\r
12763 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12764 if(text == NULL) text = "";
\r
12765 score = pvInfoList[moveNumber].score;
\r
12766 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12767 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12770 } else title[0] = 0;
\r
12772 if (text != NULL)
\r
12773 CommentPopUp(title, text);
\r
12776 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12777 * might be busy thinking or pondering. It can be omitted if your
\r
12778 * gnuchess is configured to stop thinking immediately on any user
\r
12779 * input. However, that gnuchess feature depends on the FIONREAD
\r
12780 * ioctl, which does not work properly on some flavors of Unix.
\r
12784 ChessProgramState *cps;
\r
12787 if (!cps->useSigint) return;
\r
12788 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12789 switch (gameMode) {
\r
12790 case MachinePlaysWhite:
\r
12791 case MachinePlaysBlack:
\r
12792 case TwoMachinesPlay:
\r
12793 case IcsPlayingWhite:
\r
12794 case IcsPlayingBlack:
\r
12795 case AnalyzeMode:
\r
12796 case AnalyzeFile:
\r
12797 /* Skip if we know it isn't thinking */
\r
12798 if (!cps->maybeThinking) return;
\r
12799 if (appData.debugMode)
\r
12800 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12801 InterruptChildProcess(cps->pr);
\r
12802 cps->maybeThinking = FALSE;
\r
12807 #endif /*ATTENTION*/
\r
12813 if (whiteTimeRemaining <= 0) {
\r
12814 if (!whiteFlag) {
\r
12815 whiteFlag = TRUE;
\r
12816 if (appData.icsActive) {
\r
12817 if (appData.autoCallFlag &&
\r
12818 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12819 SendToICS(ics_prefix);
\r
12820 SendToICS("flag\n");
\r
12824 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12826 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12827 if (appData.autoCallFlag) {
\r
12828 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12835 if (blackTimeRemaining <= 0) {
\r
12836 if (!blackFlag) {
\r
12837 blackFlag = TRUE;
\r
12838 if (appData.icsActive) {
\r
12839 if (appData.autoCallFlag &&
\r
12840 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12841 SendToICS(ics_prefix);
\r
12842 SendToICS("flag\n");
\r
12846 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12848 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12849 if (appData.autoCallFlag) {
\r
12850 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12861 CheckTimeControl()
\r
12863 if (!appData.clockMode || appData.icsActive ||
\r
12864 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12867 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12869 if ( !WhiteOnMove(forwardMostMove) )
\r
12870 /* White made time control */
\r
12871 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12872 /* [HGM] time odds: correct new time quota for time odds! */
\r
12873 / WhitePlayer()->timeOdds;
\r
12875 /* Black made time control */
\r
12876 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12877 / WhitePlayer()->other->timeOdds;
\r
12881 DisplayBothClocks()
\r
12883 int wom = gameMode == EditPosition ?
\r
12884 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12885 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12886 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12890 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12891 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12892 to use other calls if you don't. Clocks will be less accurate if
\r
12893 you have neither ftime nor gettimeofday.
\r
12896 /* VS 2008 requires the #include outside of the function */
\r
12897 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12898 #include <sys/timeb.h>
\r
12901 /* Get the current time as a TimeMark */
\r
12906 #if HAVE_GETTIMEOFDAY
\r
12908 struct timeval timeVal;
\r
12909 struct timezone timeZone;
\r
12911 gettimeofday(&timeVal, &timeZone);
\r
12912 tm->sec = (long) timeVal.tv_sec;
\r
12913 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12915 #else /*!HAVE_GETTIMEOFDAY*/
\r
12918 // include <sys/timeb.h> / moved to just above start of function
\r
12919 struct timeb timeB;
\r
12922 tm->sec = (long) timeB.time;
\r
12923 tm->ms = (int) timeB.millitm;
\r
12925 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12926 tm->sec = (long) time(NULL);
\r
12932 /* Return the difference in milliseconds between two
\r
12933 time marks. We assume the difference will fit in a long!
\r
12936 SubtractTimeMarks(tm2, tm1)
\r
12937 TimeMark *tm2, *tm1;
\r
12939 return 1000L*(tm2->sec - tm1->sec) +
\r
12940 (long) (tm2->ms - tm1->ms);
\r
12945 * Code to manage the game clocks.
\r
12947 * In tournament play, black starts the clock and then white makes a move.
\r
12948 * We give the human user a slight advantage if he is playing white---the
\r
12949 * clocks don't run until he makes his first move, so it takes zero time.
\r
12950 * Also, we don't account for network lag, so we could get out of sync
\r
12951 * with GNU Chess's clock -- but then, referees are always right.
\r
12954 static TimeMark tickStartTM;
\r
12955 static long intendedTickLength;
\r
12958 NextTickLength(timeRemaining)
\r
12959 long timeRemaining;
\r
12961 long nominalTickLength, nextTickLength;
\r
12963 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12964 nominalTickLength = 100L;
\r
12966 nominalTickLength = 1000L;
\r
12967 nextTickLength = timeRemaining % nominalTickLength;
\r
12968 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12970 return nextTickLength;
\r
12973 /* Adjust clock one minute up or down */
\r
12975 AdjustClock(Boolean which, int dir)
\r
12977 if(which) blackTimeRemaining += 60000*dir;
\r
12978 else whiteTimeRemaining += 60000*dir;
\r
12979 DisplayBothClocks();
\r
12982 /* Stop clocks and reset to a fresh time control */
\r
12986 (void) StopClockTimer();
\r
12987 if (appData.icsActive) {
\r
12988 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12989 } else { /* [HGM] correct new time quote for time odds */
\r
12990 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12991 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12993 if (whiteFlag || blackFlag) {
\r
12994 DisplayTitle("");
\r
12995 whiteFlag = blackFlag = FALSE;
\r
12997 DisplayBothClocks();
\r
13000 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13002 /* Decrement running clock by amount of time that has passed */
\r
13004 DecrementClocks()
\r
13006 long timeRemaining;
\r
13007 long lastTickLength, fudge;
\r
13010 if (!appData.clockMode) return;
\r
13011 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13013 GetTimeMark(&now);
\r
13015 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13017 /* Fudge if we woke up a little too soon */
\r
13018 fudge = intendedTickLength - lastTickLength;
\r
13019 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13021 if (WhiteOnMove(forwardMostMove)) {
\r
13022 if(whiteNPS >= 0) lastTickLength = 0;
\r
13023 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13024 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13025 WhiteOnMove(currentMove));
\r
13027 if(blackNPS >= 0) lastTickLength = 0;
\r
13028 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13029 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13030 !WhiteOnMove(currentMove));
\r
13033 if (CheckFlags()) return;
\r
13035 tickStartTM = now;
\r
13036 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13037 StartClockTimer(intendedTickLength);
\r
13039 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13040 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13041 * with increment) the time remaining has increased to a level above the
\r
13042 * threshold, reset the alarm so it can sound again.
\r
13045 if (appData.icsActive && appData.icsAlarm) {
\r
13047 /* make sure we are dealing with the user's clock */
\r
13048 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13049 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13052 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13053 alarmSounded = FALSE;
\r
13054 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13055 PlayAlarmSound();
\r
13056 alarmSounded = TRUE;
\r
13062 /* A player has just moved, so stop the previously running
\r
13063 clock and (if in clock mode) start the other one.
\r
13064 We redisplay both clocks in case we're in ICS mode, because
\r
13065 ICS gives us an update to both clocks after every move.
\r
13066 Note that this routine is called *after* forwardMostMove
\r
13067 is updated, so the last fractional tick must be subtracted
\r
13068 from the color that is *not* on move now.
\r
13073 long lastTickLength;
\r
13075 int flagged = FALSE;
\r
13077 GetTimeMark(&now);
\r
13079 if (StopClockTimer() && appData.clockMode) {
\r
13080 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13081 if (WhiteOnMove(forwardMostMove)) {
\r
13082 if(blackNPS >= 0) lastTickLength = 0;
\r
13083 blackTimeRemaining -= lastTickLength;
\r
13084 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13085 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13086 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13087 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13089 if(whiteNPS >= 0) lastTickLength = 0;
\r
13090 whiteTimeRemaining -= lastTickLength;
\r
13091 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13092 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13093 pvInfoList[forwardMostMove-1].time =
\r
13094 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13096 flagged = CheckFlags();
\r
13098 CheckTimeControl();
\r
13100 if (flagged || !appData.clockMode) return;
\r
13102 switch (gameMode) {
\r
13103 case MachinePlaysBlack:
\r
13104 case MachinePlaysWhite:
\r
13105 case BeginningOfGame:
\r
13106 if (pausing) return;
\r
13110 case PlayFromGameFile:
\r
13111 case IcsExamining:
\r
13118 tickStartTM = now;
\r
13119 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13120 whiteTimeRemaining : blackTimeRemaining);
\r
13121 StartClockTimer(intendedTickLength);
\r
13125 /* Stop both clocks */
\r
13129 long lastTickLength;
\r
13132 if (!StopClockTimer()) return;
\r
13133 if (!appData.clockMode) return;
\r
13135 GetTimeMark(&now);
\r
13137 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13138 if (WhiteOnMove(forwardMostMove)) {
\r
13139 if(whiteNPS >= 0) lastTickLength = 0;
\r
13140 whiteTimeRemaining -= lastTickLength;
\r
13141 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13143 if(blackNPS >= 0) lastTickLength = 0;
\r
13144 blackTimeRemaining -= lastTickLength;
\r
13145 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13150 /* Start clock of player on move. Time may have been reset, so
\r
13151 if clock is already running, stop and restart it. */
\r
13155 (void) StopClockTimer(); /* in case it was running already */
\r
13156 DisplayBothClocks();
\r
13157 if (CheckFlags()) return;
\r
13159 if (!appData.clockMode) return;
\r
13160 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13162 GetTimeMark(&tickStartTM);
\r
13163 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13164 whiteTimeRemaining : blackTimeRemaining);
\r
13166 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13167 whiteNPS = blackNPS = -1;
\r
13168 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13169 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13170 whiteNPS = first.nps;
\r
13171 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13172 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13173 blackNPS = first.nps;
\r
13174 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13175 whiteNPS = second.nps;
\r
13176 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13177 blackNPS = second.nps;
\r
13178 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13180 StartClockTimer(intendedTickLength);
\r
13187 long second, minute, hour, day;
\r
13189 static char buf[32];
\r
13191 if (ms > 0 && ms <= 9900) {
\r
13192 /* convert milliseconds to tenths, rounding up */
\r
13193 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13195 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13199 /* convert milliseconds to seconds, rounding up */
\r
13200 /* use floating point to avoid strangeness of integer division
\r
13201 with negative dividends on many machines */
\r
13202 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13204 if (second < 0) {
\r
13206 second = -second;
\r
13209 day = second / (60 * 60 * 24);
\r
13210 second = second % (60 * 60 * 24);
\r
13211 hour = second / (60 * 60);
\r
13212 second = second % (60 * 60);
\r
13213 minute = second / 60;
\r
13214 second = second % 60;
\r
13217 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13218 sign, day, hour, minute, second);
\r
13219 else if (hour > 0)
\r
13220 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13222 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13229 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13232 StrStr(string, match)
\r
13233 char *string, *match;
\r
13237 length = strlen(match);
\r
13239 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13240 if (!strncmp(match, string, length))
\r
13247 StrCaseStr(string, match)
\r
13248 char *string, *match;
\r
13250 int i, j, length;
\r
13252 length = strlen(match);
\r
13254 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13255 for (j = 0; j < length; j++) {
\r
13256 if (ToLower(match[j]) != ToLower(string[j]))
\r
13259 if (j == length) return string;
\r
13265 #ifndef _amigados
\r
13267 StrCaseCmp(s1, s2)
\r
13273 c1 = ToLower(*s1++);
\r
13274 c2 = ToLower(*s2++);
\r
13275 if (c1 > c2) return 1;
\r
13276 if (c1 < c2) return -1;
\r
13277 if (c1 == NULLCHAR) return 0;
\r
13286 return isupper(c) ? tolower(c) : c;
\r
13294 return islower(c) ? toupper(c) : c;
\r
13296 #endif /* !_amigados */
\r
13304 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13311 StrSavePtr(s, savePtr)
\r
13312 char *s, **savePtr;
\r
13317 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13318 strcpy(*savePtr, s);
\r
13320 return(*savePtr);
\r
13328 char buf[MSG_SIZ];
\r
13330 clock = time((time_t *)NULL);
\r
13331 tm = localtime(&clock);
\r
13332 sprintf(buf, "%04d.%02d.%02d",
\r
13333 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13334 return StrSave(buf);
\r
13339 PositionToFEN(move, overrideCastling)
\r
13341 char *overrideCastling;
\r
13343 int i, j, fromX, fromY, toX, toY;
\r
13348 ChessSquare piece;
\r
13350 whiteToPlay = (gameMode == EditPosition) ?
\r
13351 !blackPlaysFirst : (move % 2 == 0);
\r
13354 /* Piece placement data */
\r
13355 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13357 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13358 if (boards[move][i][j] == EmptySquare) {
\r
13360 } else { ChessSquare piece = boards[move][i][j];
\r
13361 if (emptycount > 0) {
\r
13362 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13363 *p++ = '0' + emptycount;
\r
13364 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13367 if(PieceToChar(piece) == '+') {
\r
13368 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13370 piece = (ChessSquare)(DEMOTED piece);
\r
13372 *p++ = PieceToChar(piece);
\r
13373 if(p[-1] == '~') {
\r
13374 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13375 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13380 if (emptycount > 0) {
\r
13381 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13382 *p++ = '0' + emptycount;
\r
13383 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13390 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13391 if( gameInfo.holdingsWidth ) {
\r
13392 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13394 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13395 piece = boards[move][i][BOARD_WIDTH-1];
\r
13396 if( piece != EmptySquare )
\r
13397 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13398 *p++ = PieceToChar(piece);
\r
13400 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13401 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13402 if( piece != EmptySquare )
\r
13403 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13404 *p++ = PieceToChar(piece);
\r
13407 if( q == p ) *p++ = '-';
\r
13412 /* Active color */
\r
13413 *p++ = whiteToPlay ? 'w' : 'b';
\r
13416 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
\r
13417 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
\r
13419 if(nrCastlingRights) {
\r
13421 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13422 /* [HGM] write directly from rights */
\r
13423 if(castlingRights[move][2] >= 0 &&
\r
13424 castlingRights[move][0] >= 0 )
\r
13425 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13426 if(castlingRights[move][2] >= 0 &&
\r
13427 castlingRights[move][1] >= 0 )
\r
13428 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13429 if(castlingRights[move][5] >= 0 &&
\r
13430 castlingRights[move][3] >= 0 )
\r
13431 *p++ = castlingRights[move][3] + AAA;
\r
13432 if(castlingRights[move][5] >= 0 &&
\r
13433 castlingRights[move][4] >= 0 )
\r
13434 *p++ = castlingRights[move][4] + AAA;
\r
13437 /* [HGM] write true castling rights */
\r
13438 if( nrCastlingRights == 6 ) {
\r
13439 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13440 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13441 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13442 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13443 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13444 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13445 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13446 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13449 if (q == p) *p++ = '-'; /* No castling rights */
\r
13453 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13454 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13455 /* En passant target square */
\r
13456 if (move > backwardMostMove) {
\r
13457 fromX = moveList[move - 1][0] - AAA;
\r
13458 fromY = moveList[move - 1][1] - ONE;
\r
13459 toX = moveList[move - 1][2] - AAA;
\r
13460 toY = moveList[move - 1][3] - ONE;
\r
13461 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13462 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13463 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13465 /* 2-square pawn move just happened */
\r
13466 *p++ = toX + AAA;
\r
13467 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13478 /* [HGM] find reversible plies */
\r
13479 { int i = 0, j=move;
\r
13481 if (appData.debugMode) { int k;
\r
13482 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13483 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13484 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13488 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13489 if( j == backwardMostMove ) i += initialRulePlies;
\r
13490 sprintf(p, "%d ", i);
\r
13491 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13493 /* Fullmove number */
\r
13494 sprintf(p, "%d", (move / 2) + 1);
\r
13496 return StrSave(buf);
\r
13500 ParseFEN(board, blackPlaysFirst, fen)
\r
13502 int *blackPlaysFirst;
\r
13508 ChessSquare piece;
\r
13512 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13513 if(gameInfo.holdingsWidth) {
\r
13514 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13515 board[i][0] = EmptySquare; /* black holdings */
\r
13516 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13517 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13518 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13522 /* Piece placement data */
\r
13523 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13526 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13527 if (*p == '/') p++;
\r
13528 emptycount = gameInfo.boardWidth - j;
\r
13529 while (emptycount--)
\r
13530 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13532 #if(BOARD_SIZE >= 10)
\r
13533 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13534 p++; emptycount=10;
\r
13535 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13536 while (emptycount--)
\r
13537 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13539 } else if (isdigit(*p)) {
\r
13540 emptycount = *p++ - '0';
\r
13541 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13542 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13543 while (emptycount--)
\r
13544 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13545 } else if (*p == '+' || isalpha(*p)) {
\r
13546 if (j >= gameInfo.boardWidth) return FALSE;
\r
13548 piece = CharToPiece(*++p);
\r
13549 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13550 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13551 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13552 } else piece = CharToPiece(*p++);
\r
13554 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13555 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13556 piece = (ChessSquare) (PROMOTED piece);
\r
13557 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13560 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13566 while (*p == '/' || *p == ' ') p++;
\r
13568 /* [HGM] look for Crazyhouse holdings here */
\r
13569 while(*p==' ') p++;
\r
13570 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13571 if(*p == '[') p++;
\r
13572 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13573 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13574 /* if we would allow FEN reading to set board size, we would */
\r
13575 /* have to add holdings and shift the board read so far here */
\r
13576 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13578 if((int) piece >= (int) BlackPawn ) {
\r
13579 i = (int)piece - (int)BlackPawn;
\r
13580 i = PieceToNumber((ChessSquare)i);
\r
13581 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13582 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13583 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13585 i = (int)piece - (int)WhitePawn;
\r
13586 i = PieceToNumber((ChessSquare)i);
\r
13587 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13588 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13589 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13593 if(*p == ']') *p++;
\r
13596 while(*p == ' ') p++;
\r
13598 /* Active color */
\r
13601 *blackPlaysFirst = FALSE;
\r
13604 *blackPlaysFirst = TRUE;
\r
13610 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13611 /* return the extra info in global variiables */
\r
13613 /* set defaults in case FEN is incomplete */
\r
13614 FENepStatus = EP_UNKNOWN;
\r
13615 for(i=0; i<nrCastlingRights; i++ ) {
\r
13616 FENcastlingRights[i] =
\r
13617 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13618 } /* assume possible unless obviously impossible */
\r
13619 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13620 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13621 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13622 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13623 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13624 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13625 FENrulePlies = 0;
\r
13627 while(*p==' ') p++;
\r
13628 if(nrCastlingRights) {
\r
13629 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13630 /* castling indicator present, so default becomes no castlings */
\r
13631 for(i=0; i<nrCastlingRights; i++ ) {
\r
13632 FENcastlingRights[i] = -1;
\r
13635 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13636 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13637 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13638 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13639 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13641 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13642 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13643 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13647 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13648 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13649 FENcastlingRights[2] = whiteKingFile;
\r
13652 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13653 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13654 FENcastlingRights[2] = whiteKingFile;
\r
13657 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13658 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13659 FENcastlingRights[5] = blackKingFile;
\r
13662 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13663 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13664 FENcastlingRights[5] = blackKingFile;
\r
13667 default: /* FRC castlings */
\r
13668 if(c >= 'a') { /* black rights */
\r
13669 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13670 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13671 if(i == BOARD_RGHT) break;
\r
13672 FENcastlingRights[5] = i;
\r
13674 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13675 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13677 FENcastlingRights[3] = c;
\r
13679 FENcastlingRights[4] = c;
\r
13680 } else { /* white rights */
\r
13681 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13682 if(board[0][i] == WhiteKing) break;
\r
13683 if(i == BOARD_RGHT) break;
\r
13684 FENcastlingRights[2] = i;
\r
13685 c -= AAA - 'a' + 'A';
\r
13686 if(board[0][c] >= WhiteKing) break;
\r
13688 FENcastlingRights[0] = c;
\r
13690 FENcastlingRights[1] = c;
\r
13694 if (appData.debugMode) {
\r
13695 fprintf(debugFP, "FEN castling rights:");
\r
13696 for(i=0; i<nrCastlingRights; i++)
\r
13697 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13698 fprintf(debugFP, "\n");
\r
13701 while(*p==' ') p++;
\r
13704 /* read e.p. field in games that know e.p. capture */
\r
13705 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13706 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13708 p++; FENepStatus = EP_NONE;
\r
13710 char c = *p++ - AAA;
\r
13712 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13713 if(*p >= '0' && *p <='9') *p++;
\r
13719 if(sscanf(p, "%d", &i) == 1) {
\r
13720 FENrulePlies = i; /* 50-move ply counter */
\r
13721 /* (The move number is still ignored) */
\r
13728 EditPositionPasteFEN(char *fen)
\r
13730 if (fen != NULL) {
\r
13731 Board initial_position;
\r
13733 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13734 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13737 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13738 EditPositionEvent();
\r
13739 blackPlaysFirst = savedBlackPlaysFirst;
\r
13740 CopyBoard(boards[0], initial_position);
\r
13741 /* [HGM] copy FEN attributes as well */
\r
13743 initialRulePlies = FENrulePlies;
\r
13744 epStatus[0] = FENepStatus;
\r
13745 for( i=0; i<nrCastlingRights; i++ )
\r
13746 castlingRights[0][i] = FENcastlingRights[i];
\r
13748 EditPositionDone();
\r
13749 DisplayBothClocks();
\r
13750 DrawPosition(FALSE, boards[currentMove]);
\r