2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard,
\r
6 * Massachusetts. Enhancements Copyright
\r
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
\r
10 * The following terms apply to Digital Equipment Corporation's copyright
\r
11 * interest in XBoard:
\r
12 * ------------------------------------------------------------------------
\r
13 * All Rights Reserved
\r
15 * Permission to use, copy, modify, and distribute this software and its
\r
16 * documentation for any purpose and without fee is hereby granted,
\r
17 * provided that the above copyright notice appear in all copies and that
\r
18 * both that copyright notice and this permission notice appear in
\r
19 * supporting documentation, and that the name of Digital not be
\r
20 * used in advertising or publicity pertaining to distribution of the
\r
21 * software without specific, written prior permission.
\r
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
30 * ------------------------------------------------------------------------
\r
32 * The following terms apply to the enhanced version of XBoard
\r
33 * distributed by the Free Software Foundation:
\r
34 * ------------------------------------------------------------------------
\r
36 * GNU XBoard is free software: you can redistribute it and/or modify
\r
37 * it under the terms of the GNU General Public License as published by
\r
38 * the Free Software Foundation, either version 3 of the License, or (at
\r
39 * your option) any later version.
\r
41 * GNU XBoard is distributed in the hope that it will be useful, but
\r
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
\r
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
44 * General Public License for more details.
\r
46 * You should have received a copy of the GNU General Public License
\r
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
\r
49 *------------------------------------------------------------------------
\r
50 ** See the file ChangeLog for a revision history. */
\r
52 /* [AS] Also useful here for debugging */
\r
54 #include <windows.h>
\r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
70 #include <sys/types.h>
\r
71 #include <sys/stat.h>
\r
76 # include <stdlib.h>
\r
77 # include <string.h>
\r
78 #else /* not STDC_HEADERS */
\r
80 # include <string.h>
\r
81 # else /* not HAVE_STRING_H */
\r
82 # include <strings.h>
\r
83 # endif /* not HAVE_STRING_H */
\r
84 #endif /* not STDC_HEADERS */
\r
86 #if HAVE_SYS_FCNTL_H
\r
87 # include <sys/fcntl.h>
\r
88 #else /* not HAVE_SYS_FCNTL_H */
\r
91 # endif /* HAVE_FCNTL_H */
\r
92 #endif /* not HAVE_SYS_FCNTL_H */
\r
94 #if TIME_WITH_SYS_TIME
\r
95 # include <sys/time.h>
\r
98 # if HAVE_SYS_TIME_H
\r
99 # include <sys/time.h>
\r
105 #if defined(_amigados) && !defined(__GNUC__)
\r
107 int tz_minuteswest;
\r
110 extern int gettimeofday(struct timeval *, struct timezone *);
\r
114 # include <unistd.h>
\r
117 #include "common.h"
\r
118 #include "frontend.h"
\r
119 #include "backend.h"
\r
120 #include "parser.h"
\r
123 # include "zippy.h"
\r
125 #include "backendz.h"
\r
126 #include "gettext.h"
\r
129 # define _(s) gettext (s)
\r
130 # define N_(s) gettext_noop (s)
\r
137 /* A point in time */
\r
139 long sec; /* Assuming this is >= 32 bits */
\r
140 int ms; /* Assuming this is >= 16 bits */
\r
143 int establish P((void));
\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
145 char *buf, int count, int error));
\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
147 char *buf, int count, int error));
\r
148 void SendToICS P((char *s));
\r
149 void SendToICSDelayed P((char *s, long msdelay));
\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
151 int toX, int toY));
\r
152 void InitPosition P((int redraw));
\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
154 int AutoPlayOneMove P((void));
\r
155 int LoadGameOneMove P((ChessMove readAhead));
\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
158 int SavePositionToFile P((char *filename));
\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
164 /*char*/int promoChar));
\r
165 void BackwardInner P((int target));
\r
166 void ForwardInner P((int target));
\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
168 void EditPositionDone P((void));
\r
169 void PrintOpponents P((FILE *fp));
\r
170 void PrintPosition P((FILE *fp, int move));
\r
171 void StartChessProgram P((ChessProgramState *cps));
\r
172 void SendToProgram P((char *message, ChessProgramState *cps));
\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
175 char *buf, int count, int error));
\r
176 void SendTimeControl P((ChessProgramState *cps,
\r
177 int mps, long tc, int inc, int sd, int st));
\r
178 char *TimeControlTagValue P((void));
\r
179 void Attention P((ChessProgramState *cps));
\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
181 void ResurrectChessProgram P((void));
\r
182 void DisplayComment P((int moveNumber, char *text));
\r
183 void DisplayMove P((int moveNumber));
\r
184 void DisplayAnalysis P((void));
\r
186 void ParseGameHistory P((char *game));
\r
187 void ParseBoard12 P((char *string));
\r
188 void StartClocks P((void));
\r
189 void SwitchClocks P((void));
\r
190 void StopClocks P((void));
\r
191 void ResetClocks P((void));
\r
192 char *PGNDate P((void));
\r
193 void SetGameInfo P((void));
\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
195 int RegisterMove P((void));
\r
196 void MakeRegisteredMove P((void));
\r
197 void TruncateGame P((void));
\r
198 int looking_at P((char *, int *, char *));
\r
199 void CopyPlayerNameIntoFileName P((char **, char *));
\r
200 char *SavePart P((char *));
\r
201 int SaveGameOldStyle P((FILE *));
\r
202 int SaveGamePGN P((FILE *));
\r
203 void GetTimeMark P((TimeMark *));
\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
205 int CheckFlags P((void));
\r
206 long NextTickLength P((long));
\r
207 void CheckTimeControl P((void));
\r
208 void show_bytes P((FILE *, char *, int));
\r
209 int string_to_rating P((char *str));
\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
211 void InitBackEnd3 P((void));
\r
212 void FeatureDone P((ChessProgramState* cps, int val));
\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
214 void OutputKibitz(int window, char *text);
\r
215 int PerpetualChase(int first, int last);
\r
216 int EngineOutputIsUp();
\r
217 void InitDrawingSizes(int x, int y);
\r
220 extern void ConsoleCreate();
\r
223 ChessProgramState *WhitePlayer();
\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
225 int VerifyDisplayMode P(());
\r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
231 extern char installDir[MSG_SIZ];
\r
233 extern int tinyLayout, smallLayout;
\r
234 ChessProgramStats programStats;
\r
235 static int exiting = 0; /* [HGM] moved to top */
\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
\r
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
242 int opponentKibitzes;
\r
244 /* States for ics_getting_history */
\r
246 #define H_REQUESTED 1
\r
247 #define H_GOT_REQ_HEADER 2
\r
248 #define H_GOT_UNREQ_HEADER 3
\r
249 #define H_GETTING_MOVES 4
\r
250 #define H_GOT_UNWANTED_HEADER 5
\r
252 /* whosays values for GameEnds */
\r
254 #define GE_ENGINE 1
\r
255 #define GE_PLAYER 2
\r
257 #define GE_XBOARD 4
\r
258 #define GE_ENGINE1 5
\r
259 #define GE_ENGINE2 6
\r
261 /* Maximum number of games in a cmail message */
\r
262 #define CMAIL_MAX_GAMES 20
\r
264 /* Different types of move when calling RegisterMove */
\r
265 #define CMAIL_MOVE 0
\r
266 #define CMAIL_RESIGN 1
\r
267 #define CMAIL_DRAW 2
\r
268 #define CMAIL_ACCEPT 3
\r
270 /* Different types of result to remember for each game */
\r
271 #define CMAIL_NOT_RESULT 0
\r
272 #define CMAIL_OLD_RESULT 1
\r
273 #define CMAIL_NEW_RESULT 2
\r
275 /* Telnet protocol constants */
\r
276 #define TN_WILL 0373
\r
277 #define TN_WONT 0374
\r
279 #define TN_DONT 0376
\r
280 #define TN_IAC 0377
\r
281 #define TN_ECHO 0001
\r
282 #define TN_SGA 0003
\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
288 assert( dst != NULL );
\r
289 assert( src != NULL );
\r
290 assert( count > 0 );
\r
292 strncpy( dst, src, count );
\r
293 dst[ count-1 ] = '\0';
\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.
\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
303 assert( dst != NULL );
\r
304 assert( src != NULL );
\r
305 assert( count > 0 );
\r
307 dst_len = strlen(dst);
\r
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
311 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
317 /* Some compiler can't cast u64 to double
\r
318 * This function do the job for us:
\r
320 * We use the highest bit for cast, this only
\r
321 * works if the highest bit is not
\r
322 * in use (This should not happen)
\r
324 * We used this for all compiler
\r
327 u64ToDouble(u64 value)
\r
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
331 r = (double)(s64)tmp;
\r
332 if (value & u64Const(0x8000000000000000))
\r
333 r += 9.2233720368547758080e18; /* 2^63 */
\r
337 /* Fake up flags for now, as we aren't keeping track of castling
\r
338 availability yet. [HGM] Change of logic: the flag now only
\r
339 indicates the type of castlings allowed by the rule of the game.
\r
340 The actual rights themselves are maintained in the array
\r
341 castlingRights, as part of the game history, and are not probed
\r
347 int flags = F_ALL_CASTLE_OK;
\r
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
349 switch (gameInfo.variant) {
\r
350 case VariantSuicide:
\r
351 flags &= ~F_ALL_CASTLE_OK;
\r
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
353 flags |= F_IGNORE_CHECK;
\r
354 case VariantLosers:
\r
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
\r
357 case VariantAtomic:
\r
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
360 case VariantKriegspiel:
\r
361 flags |= F_KRIEGSPIEL_CAPTURE;
\r
363 case VariantCapaRandom:
\r
364 case VariantFischeRandom:
\r
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
366 case VariantNoCastle:
\r
367 case VariantShatranj:
\r
368 case VariantCourier:
\r
369 flags &= ~F_ALL_CASTLE_OK;
\r
377 FILE *gameFileFP, *debugFP;
\r
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
381 into a fixed-size buffer. Because of this, we must be prepared to
\r
382 receive strings as long as the size of the input buffer, which is currently
\r
383 set to 4K for Windows and 8K for the rest.
\r
384 So, we must either allocate sufficiently large buffers here, or
\r
385 reduce the size of the input buffer in the input reading part.
\r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
390 char thinkOutput1[MSG_SIZ*10];
\r
392 ChessProgramState first, second;
\r
394 /* premove variables */
\r
395 int premoveToX = 0;
\r
396 int premoveToY = 0;
\r
397 int premoveFromX = 0;
\r
398 int premoveFromY = 0;
\r
399 int premovePromoChar = 0;
\r
400 int gotPremove = 0;
\r
401 Boolean alarmSounded;
\r
402 /* end premove variables */
\r
404 char *ics_prefix = "$";
\r
405 int ics_type = ICS_GENERIC;
\r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
408 int pauseExamForwardMostMove = 0;
\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
415 int whiteFlag = FALSE, blackFlag = FALSE;
\r
416 int userOfferedDraw = FALSE;
\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
419 int cmailMoveType[CMAIL_MAX_GAMES];
\r
420 long ics_clock_paused = 0;
\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
423 GameMode gameMode = BeginningOfGame;
\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
427 int hiddenThinkOutputState = 0; /* [AS] */
\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
429 int adjudicateLossPlies = 6;
\r
430 char white_holding[64], black_holding[64];
\r
431 TimeMark lastNodeCountTime;
\r
432 long lastNodeCount=0;
\r
433 int have_sent_ICS_logon = 0;
\r
434 int movesPerSession;
\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
436 long timeControl_2; /* [AS] Allow separate time controls */
\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
438 long timeRemaining[2][MAX_MOVES];
\r
440 TimeMark programStartTime;
\r
441 char ics_handle[MSG_SIZ];
\r
442 int have_set_title = 0;
\r
444 /* animateTraining preserves the state of appData.animate
\r
445 * when Training mode is activated. This allows the
\r
446 * response to be animated when appData.animate == TRUE and
\r
447 * appData.animateDragging == TRUE.
\r
449 Boolean animateTraining;
\r
455 Board boards[MAX_MOVES];
\r
456 /* [HGM] Following 7 needed for accurate legality tests: */
\r
457 char epStatus[MAX_MOVES];
\r
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
462 int initialRulePlies, FENrulePlies;
\r
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
466 int shuffleOpenings;
\r
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
479 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
504 #if (BOARD_SIZE>=10)
\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
548 #define GothicArray CapablancaArray
\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
559 #define FalconArray CapablancaArray
\r
562 #else // !(BOARD_SIZE>=10)
\r
563 #define XiangqiPosition FIDEArray
\r
564 #define CapablancaArray FIDEArray
\r
565 #define GothicArray FIDEArray
\r
566 #define GreatArray FIDEArray
\r
567 #endif // !(BOARD_SIZE>=10)
\r
569 #if (BOARD_SIZE>=12)
\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
576 #else // !(BOARD_SIZE>=12)
\r
577 #define CourierArray CapablancaArray
\r
578 #endif // !(BOARD_SIZE>=12)
\r
581 Board initialPosition;
\r
584 /* Convert str to a rating. Checks for special cases of "----",
\r
586 "++++", etc. Also strips ()'s */
\r
588 string_to_rating(str)
\r
591 while(*str && !isdigit(*str)) ++str;
\r
593 return 0; /* One of the special "no rating" cases */
\r
599 ClearProgramStats()
\r
601 /* Init programStats */
\r
602 programStats.movelist[0] = 0;
\r
603 programStats.depth = 0;
\r
604 programStats.nr_moves = 0;
\r
605 programStats.moves_left = 0;
\r
606 programStats.nodes = 0;
\r
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
608 programStats.score = 0;
\r
609 programStats.got_only_move = 0;
\r
610 programStats.got_fail = 0;
\r
611 programStats.line_is_book = 0;
\r
617 int matched, min, sec;
\r
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
621 GetTimeMark(&programStartTime);
\r
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
\r
624 ClearProgramStats();
\r
625 programStats.ok_to_send = 1;
\r
626 programStats.seen_stat = 0;
\r
629 * Initialize game list
\r
631 ListNew(&gameList);
\r
635 * Internet chess server status
\r
637 if (appData.icsActive) {
\r
638 appData.matchMode = FALSE;
\r
639 appData.matchGames = 0;
\r
641 appData.noChessProgram = !appData.zippyPlay;
\r
643 appData.zippyPlay = FALSE;
\r
644 appData.zippyTalk = FALSE;
\r
645 appData.noChessProgram = TRUE;
\r
647 if (*appData.icsHelper != NULLCHAR) {
\r
648 appData.useTelnet = TRUE;
\r
649 appData.telnetProgram = appData.icsHelper;
\r
652 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
655 /* [AS] Initialize pv info list [HGM] and game state */
\r
659 for( i=0; i<MAX_MOVES; i++ ) {
\r
660 pvInfoList[i].depth = -1;
\r
661 epStatus[i]=EP_NONE;
\r
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
667 * Parse timeControl resource
\r
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
670 appData.movesPerSession)) {
\r
672 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
673 DisplayFatalError(buf, 0, 2);
\r
677 * Parse searchTime resource
\r
679 if (*appData.searchTime != NULLCHAR) {
\r
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
681 if (matched == 1) {
\r
682 searchTime = min * 60;
\r
683 } else if (matched == 2) {
\r
684 searchTime = min * 60 + sec;
\r
687 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
688 DisplayFatalError(buf, 0, 2);
\r
692 /* [AS] Adjudication threshold */
\r
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
695 first.which = "first";
\r
696 second.which = "second";
\r
697 first.maybeThinking = second.maybeThinking = FALSE;
\r
698 first.pr = second.pr = NoProc;
\r
699 first.isr = second.isr = NULL;
\r
700 first.sendTime = second.sendTime = 2;
\r
701 first.sendDrawOffers = 1;
\r
702 if (appData.firstPlaysBlack) {
\r
703 first.twoMachinesColor = "black\n";
\r
704 second.twoMachinesColor = "white\n";
\r
706 first.twoMachinesColor = "white\n";
\r
707 second.twoMachinesColor = "black\n";
\r
709 first.program = appData.firstChessProgram;
\r
710 second.program = appData.secondChessProgram;
\r
711 first.host = appData.firstHost;
\r
712 second.host = appData.secondHost;
\r
713 first.dir = appData.firstDirectory;
\r
714 second.dir = appData.secondDirectory;
\r
715 first.other = &second;
\r
716 second.other = &first;
\r
717 first.initString = appData.initString;
\r
718 second.initString = appData.secondInitString;
\r
719 first.computerString = appData.firstComputerString;
\r
720 second.computerString = appData.secondComputerString;
\r
721 first.useSigint = second.useSigint = TRUE;
\r
722 first.useSigterm = second.useSigterm = TRUE;
\r
723 first.reuse = appData.reuseFirst;
\r
724 second.reuse = appData.reuseSecond;
\r
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
726 second.nps = appData.secondNPS;
\r
727 first.useSetboard = second.useSetboard = FALSE;
\r
728 first.useSAN = second.useSAN = FALSE;
\r
729 first.usePing = second.usePing = FALSE;
\r
730 first.lastPing = second.lastPing = 0;
\r
731 first.lastPong = second.lastPong = 0;
\r
732 first.usePlayother = second.usePlayother = FALSE;
\r
733 first.useColors = second.useColors = TRUE;
\r
734 first.useUsermove = second.useUsermove = FALSE;
\r
735 first.sendICS = second.sendICS = FALSE;
\r
736 first.sendName = second.sendName = appData.icsActive;
\r
737 first.sdKludge = second.sdKludge = FALSE;
\r
738 first.stKludge = second.stKludge = FALSE;
\r
739 TidyProgramName(first.program, first.host, first.tidy);
\r
740 TidyProgramName(second.program, second.host, second.tidy);
\r
741 first.matchWins = second.matchWins = 0;
\r
742 strcpy(first.variants, appData.variant);
\r
743 strcpy(second.variants, appData.variant);
\r
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
745 first.analyzing = second.analyzing = FALSE;
\r
746 first.initDone = second.initDone = FALSE;
\r
748 /* New features added by Tord: */
\r
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
751 /* End of new features added by Tord. */
\r
752 first.fenOverride = appData.fenOverride1;
\r
753 second.fenOverride = appData.fenOverride2;
\r
755 /* [HGM] time odds: set factor for each machine */
\r
756 first.timeOdds = appData.firstTimeOdds;
\r
757 second.timeOdds = appData.secondTimeOdds;
\r
759 if(appData.timeOddsMode) {
\r
760 norm = first.timeOdds;
\r
761 if(norm > second.timeOdds) norm = second.timeOdds;
\r
763 first.timeOdds /= norm;
\r
764 second.timeOdds /= norm;
\r
767 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
768 first.accumulateTC = appData.firstAccumulateTC;
\r
769 second.accumulateTC = appData.secondAccumulateTC;
\r
770 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
773 first.debug = second.debug = FALSE;
\r
774 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
776 /* [HGM] options */
\r
777 first.optionSettings = appData.firstOptions;
\r
778 second.optionSettings = appData.secondOptions;
\r
780 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
781 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
782 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
783 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
784 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
785 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
787 if (appData.firstProtocolVersion > PROTOVER ||
\r
788 appData.firstProtocolVersion < 1) {
\r
790 sprintf(buf, _("protocol version %d not supported"),
\r
791 appData.firstProtocolVersion);
\r
792 DisplayFatalError(buf, 0, 2);
\r
794 first.protocolVersion = appData.firstProtocolVersion;
\r
797 if (appData.secondProtocolVersion > PROTOVER ||
\r
798 appData.secondProtocolVersion < 1) {
\r
800 sprintf(buf, _("protocol version %d not supported"),
\r
801 appData.secondProtocolVersion);
\r
802 DisplayFatalError(buf, 0, 2);
\r
804 second.protocolVersion = appData.secondProtocolVersion;
\r
807 if (appData.icsActive) {
\r
808 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
809 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
810 appData.clockMode = FALSE;
\r
811 first.sendTime = second.sendTime = 0;
\r
815 /* Override some settings from environment variables, for backward
\r
816 compatibility. Unfortunately it's not feasible to have the env
\r
817 vars just set defaults, at least in xboard. Ugh.
\r
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
824 if (appData.noChessProgram) {
\r
825 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
826 + strlen(PATCHLEVEL));
\r
827 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
832 while (*q != ' ' && *q != NULLCHAR) q++;
\r
834 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
835 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
836 + strlen(PATCHLEVEL) + (q - p));
\r
837 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
838 strncat(programVersion, p, q - p);
\r
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
841 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
842 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
843 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
847 if (!appData.icsActive) {
\r
849 /* Check for variants that are supported only in ICS mode,
\r
850 or not at all. Some that are accepted here nevertheless
\r
851 have bugs; see comments below.
\r
853 VariantClass variant = StringToVariant(appData.variant);
\r
855 case VariantBughouse: /* need four players and two boards */
\r
856 case VariantKriegspiel: /* need to hide pieces and move details */
\r
857 /* case VariantFischeRandom: (Fabien: moved below) */
\r
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
859 DisplayFatalError(buf, 0, 2);
\r
862 case VariantUnknown:
\r
863 case VariantLoadable:
\r
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
874 DisplayFatalError(buf, 0, 2);
\r
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
879 case VariantGothic: /* [HGM] should work */
\r
880 case VariantCapablanca: /* [HGM] should work */
\r
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
882 case VariantShogi: /* [HGM] drops not tested for legality */
\r
883 case VariantKnightmate: /* [HGM] should work */
\r
884 case VariantCylinder: /* [HGM] untested */
\r
885 case VariantFalcon: /* [HGM] untested */
\r
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
887 offboard interposition not understood */
\r
888 case VariantNormal: /* definitely works! */
\r
889 case VariantWildCastle: /* pieces not automatically shuffled */
\r
890 case VariantNoCastle: /* pieces not automatically shuffled */
\r
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
892 case VariantLosers: /* should work except for win condition,
\r
893 and doesn't know captures are mandatory */
\r
894 case VariantSuicide: /* should work except for win condition,
\r
895 and doesn't know captures are mandatory */
\r
896 case VariantGiveaway: /* should work except for win condition,
\r
897 and doesn't know captures are mandatory */
\r
898 case VariantTwoKings: /* should work */
\r
899 case VariantAtomic: /* should work except for win condition */
\r
900 case Variant3Check: /* should work except for win condition */
\r
901 case VariantShatranj: /* should work except for all win conditions */
\r
902 case VariantBerolina: /* might work if TestLegality is off */
\r
903 case VariantCapaRandom: /* should work */
\r
904 case VariantJanus: /* should work */
\r
905 case VariantSuper: /* experimental */
\r
906 case VariantGreat: /* experimental, requires legality testing to be off */
\r
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
912 InitEngineUCI( installDir, &second );
\r
915 int NextIntegerFromString( char ** str, long * value )
\r
920 while( *s == ' ' || *s == '\t' ) {
\r
926 if( *s >= '0' && *s <= '9' ) {
\r
927 while( *s >= '0' && *s <= '9' ) {
\r
928 *value = *value * 10 + (*s - '0');
\r
940 int NextTimeControlFromString( char ** str, long * value )
\r
943 int result = NextIntegerFromString( str, &temp );
\r
945 if( result == 0 ) {
\r
946 *value = temp * 60; /* Minutes */
\r
947 if( **str == ':' ) {
\r
949 result = NextIntegerFromString( str, &temp );
\r
950 *value += temp; /* Seconds */
\r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
959 int result = -1; long temp, temp2;
\r
961 if(**str != '+') return -1; // old params remain in force!
\r
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
966 /* time only: incremental or sudden-death time control */
\r
967 if(**str == '+') { /* increment follows; read it */
\r
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
970 *inc = temp2 * 1000;
\r
972 *moves = 0; *tc = temp * 1000;
\r
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
976 (*str)++; /* classical time control */
\r
977 result = NextTimeControlFromString( str, &temp2);
\r
980 *tc = temp2 * 1000;
\r
986 int GetTimeQuota(int movenr)
\r
987 { /* [HGM] get time to add from the multi-session time-control string */
\r
988 int moves=1; /* kludge to force reading of first session */
\r
989 long time, increment;
\r
990 char *s = fullTimeControlString;
\r
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
996 if(movenr == -1) return time; /* last move before new session */
\r
997 if(!moves) return increment; /* current session is incremental */
\r
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
999 } while(movenr >= -1); /* try again for next session */
\r
1001 return 0; // no new time quota on this move
\r
1005 ParseTimeControl(tc, ti, mps)
\r
1011 int matched, min, sec;
\r
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1014 if (matched == 1) {
\r
1015 timeControl = min * 60 * 1000;
\r
1016 } else if (matched == 2) {
\r
1017 timeControl = (min * 60 + sec) * 1000;
\r
1024 char buf[MSG_SIZ];
\r
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1030 else sprintf(buf, "+%s+%d", tc, ti);
\r
1033 sprintf(buf, "+%d/%s", mps, tc);
\r
1034 else sprintf(buf, "+%s", tc);
\r
1036 fullTimeControlString = StrSave(buf);
\r
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1042 if( *tc == '/' ) {
\r
1043 /* Parse second time control */
\r
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1054 timeControl_2 = tc2 * 1000;
\r
1057 timeControl_2 = 0;
\r
1064 timeControl = tc1 * 1000;
\r
1068 timeIncrement = ti * 1000; /* convert to ms */
\r
1069 movesPerSession = 0;
\r
1071 timeIncrement = 0;
\r
1072 movesPerSession = mps;
\r
1080 if (appData.debugMode) {
\r
1081 fprintf(debugFP, "%s\n", programVersion);
\r
1084 if (appData.matchGames > 0) {
\r
1085 appData.matchMode = TRUE;
\r
1086 } else if (appData.matchMode) {
\r
1087 appData.matchGames = 1;
\r
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1090 appData.matchGames = appData.sameColorGames;
\r
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1095 Reset(TRUE, FALSE);
\r
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1099 /* kludge: allow timeout for initial "feature" commands */
\r
1101 DisplayMessage("", _("Starting chess program"));
\r
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1107 InitBackEnd3 P((void))
\r
1109 GameMode initialMode;
\r
1110 char buf[MSG_SIZ];
\r
1113 InitChessProgram(&first, startedFromSetupPosition);
\r
1116 if (appData.icsActive) {
\r
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1121 err = establish();
\r
1123 if (*appData.icsCommPort != NULLCHAR) {
\r
1124 sprintf(buf, _("Could not open comm port %s"),
\r
1125 appData.icsCommPort);
\r
1127 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1128 appData.icsHost, appData.icsPort);
\r
1130 DisplayFatalError(buf, err, 1);
\r
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1138 } else if (appData.noChessProgram) {
\r
1144 if (*appData.cmailGameName != NULLCHAR) {
\r
1146 OpenLoopback(&cmailPR);
\r
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1152 DisplayMessage("", "");
\r
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1154 initialMode = BeginningOfGame;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1156 initialMode = TwoMachinesPlay;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1158 initialMode = AnalyzeFile;
\r
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1160 initialMode = AnalyzeMode;
\r
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1162 initialMode = MachinePlaysWhite;
\r
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1164 initialMode = MachinePlaysBlack;
\r
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1166 initialMode = EditGame;
\r
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1168 initialMode = EditPosition;
\r
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1170 initialMode = Training;
\r
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1173 DisplayFatalError(buf, 0, 2);
\r
1177 if (appData.matchMode) {
\r
1178 /* Set up machine vs. machine match */
\r
1179 if (appData.noChessProgram) {
\r
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1186 if (*appData.loadGameFile != NULLCHAR) {
\r
1187 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1188 if(index<0) lastIndex = index = 1;
\r
1189 if (!LoadGameFromFile(appData.loadGameFile,
\r
1191 appData.loadGameFile, FALSE)) {
\r
1192 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1197 if(index<0) lastIndex = index = 1;
\r
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1200 appData.loadPositionFile)) {
\r
1201 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1205 TwoMachinesEvent();
\r
1206 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1207 /* Set up cmail mode */
\r
1208 ReloadCmailMsgEvent(TRUE);
\r
1210 /* Set up other modes */
\r
1211 if (initialMode == AnalyzeFile) {
\r
1212 if (*appData.loadGameFile == NULLCHAR) {
\r
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1217 if (*appData.loadGameFile != NULLCHAR) {
\r
1218 (void) LoadGameFromFile(appData.loadGameFile,
\r
1219 appData.loadGameIndex,
\r
1220 appData.loadGameFile, TRUE);
\r
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1223 appData.loadPositionIndex,
\r
1224 appData.loadPositionFile);
\r
1225 /* [HGM] try to make self-starting even after FEN load */
\r
1226 /* to allow automatic setup of fairy variants with wtm */
\r
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1228 gameMode = BeginningOfGame;
\r
1229 setboardSpoiledMachineBlack = 1;
\r
1231 /* [HGM] loadPos: make that every new game uses the setup */
\r
1232 /* from file as long as we do not switch variant */
\r
1233 if(!blackPlaysFirst) { int i;
\r
1234 startedFromPositionFile = TRUE;
\r
1235 CopyBoard(filePosition, boards[0]);
\r
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1239 if (initialMode == AnalyzeMode) {
\r
1240 if (appData.noChessProgram) {
\r
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1244 if (appData.icsActive) {
\r
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1248 AnalyzeModeEvent();
\r
1249 } else if (initialMode == AnalyzeFile) {
\r
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1251 ShowThinkingEvent();
\r
1252 AnalyzeFileEvent();
\r
1253 AnalysisPeriodicEvent(1);
\r
1254 } else if (initialMode == MachinePlaysWhite) {
\r
1255 if (appData.noChessProgram) {
\r
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1260 if (appData.icsActive) {
\r
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1265 MachineWhiteEvent();
\r
1266 } else if (initialMode == MachinePlaysBlack) {
\r
1267 if (appData.noChessProgram) {
\r
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1272 if (appData.icsActive) {
\r
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1277 MachineBlackEvent();
\r
1278 } else if (initialMode == TwoMachinesPlay) {
\r
1279 if (appData.noChessProgram) {
\r
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1284 if (appData.icsActive) {
\r
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1289 TwoMachinesEvent();
\r
1290 } else if (initialMode == EditGame) {
\r
1292 } else if (initialMode == EditPosition) {
\r
1293 EditPositionEvent();
\r
1294 } else if (initialMode == Training) {
\r
1295 if (*appData.loadGameFile == NULLCHAR) {
\r
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1305 * Establish will establish a contact to a remote host.port.
\r
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1307 * used to talk to the host.
\r
1308 * Returns 0 if okay, error code if not.
\r
1313 char buf[MSG_SIZ];
\r
1315 if (*appData.icsCommPort != NULLCHAR) {
\r
1316 /* Talk to the host through a serial comm port */
\r
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1319 } else if (*appData.gateway != NULLCHAR) {
\r
1320 if (*appData.remoteShell == NULLCHAR) {
\r
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1322 sprintf(buf, "%s %s %s",
\r
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1327 /* Use the rsh program to run telnet program on a gateway host */
\r
1328 if (*appData.remoteUser == NULLCHAR) {
\r
1329 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1330 appData.gateway, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1334 appData.remoteShell, appData.gateway,
\r
1335 appData.remoteUser, appData.telnetProgram,
\r
1336 appData.icsHost, appData.icsPort);
\r
1338 return StartChildProcess(buf, "", &icsPR);
\r
1341 } else if (appData.useTelnet) {
\r
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1345 /* TCP socket interface differs somewhat between
\r
1346 Unix and NT; handle details in the front end.
\r
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1353 show_bytes(fp, buf, count)
\r
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1360 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1369 /* Returns an errno value */
\r
1371 OutputMaybeTelnet(pr, message, count, outError)
\r
1377 char buf[8192], *p, *q, *buflim;
\r
1378 int left, newcount, outcount;
\r
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1381 *appData.gateway != NULLCHAR) {
\r
1382 if (appData.debugMode) {
\r
1383 fprintf(debugFP, ">ICS: ");
\r
1384 show_bytes(debugFP, message, count);
\r
1385 fprintf(debugFP, "\n");
\r
1387 return OutputToProcess(pr, message, count, outError);
\r
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1396 if (q >= buflim) {
\r
1397 if (appData.debugMode) {
\r
1398 fprintf(debugFP, ">ICS: ");
\r
1399 show_bytes(debugFP, buf, newcount);
\r
1400 fprintf(debugFP, "\n");
\r
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1403 if (outcount < newcount) return -1; /* to be sure */
\r
1410 } else if (((unsigned char) *p) == TN_IAC) {
\r
1411 *q++ = (char) TN_IAC;
\r
1418 if (appData.debugMode) {
\r
1419 fprintf(debugFP, ">ICS: ");
\r
1420 show_bytes(debugFP, buf, newcount);
\r
1421 fprintf(debugFP, "\n");
\r
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1424 if (outcount < newcount) return -1; /* to be sure */
\r
1429 read_from_player(isr, closure, message, count, error)
\r
1430 InputSourceRef isr;
\r
1436 int outError, outCount;
\r
1437 static int gotEof = 0;
\r
1439 /* Pass data read from player on to ICS */
\r
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1443 if (outCount < count) {
\r
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1446 } else if (count < 0) {
\r
1447 RemoveInputSource(isr);
\r
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1449 } else if (gotEof++ > 0) {
\r
1450 RemoveInputSource(isr);
\r
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1459 int count, outCount, outError;
\r
1461 if (icsPR == NULL) return;
\r
1463 count = strlen(s);
\r
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1465 if (outCount < count) {
\r
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1470 /* This is used for sending logon scripts to the ICS. Sending
\r
1471 without a delay causes problems when using timestamp on ICC
\r
1472 (at least on my machine). */
\r
1474 SendToICSDelayed(s,msdelay)
\r
1478 int count, outCount, outError;
\r
1480 if (icsPR == NULL) return;
\r
1482 count = strlen(s);
\r
1483 if (appData.debugMode) {
\r
1484 fprintf(debugFP, ">ICS: ");
\r
1485 show_bytes(debugFP, s, count);
\r
1486 fprintf(debugFP, "\n");
\r
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1490 if (outCount < count) {
\r
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1496 /* Remove all highlighting escape sequences in s
\r
1497 Also deletes any suffix starting with '('
\r
1500 StripHighlightAndTitle(s)
\r
1503 static char retbuf[MSG_SIZ];
\r
1506 while (*s != NULLCHAR) {
\r
1507 while (*s == '\033') {
\r
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1509 if (*s != NULLCHAR) s++;
\r
1511 while (*s != NULLCHAR && *s != '\033') {
\r
1512 if (*s == '(' || *s == '[') {
\r
1523 /* Remove all highlighting escape sequences in s */
\r
1528 static char retbuf[MSG_SIZ];
\r
1531 while (*s != NULLCHAR) {
\r
1532 while (*s == '\033') {
\r
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1534 if (*s != NULLCHAR) s++;
\r
1536 while (*s != NULLCHAR && *s != '\033') {
\r
1544 char *variantNames[] = VARIANT_NAMES;
\r
1549 return variantNames[v];
\r
1553 /* Identify a variant from the strings the chess servers use or the
\r
1554 PGN Variant tag names we use. */
\r
1556 StringToVariant(e)
\r
1561 VariantClass v = VariantNormal;
\r
1562 int i, found = FALSE;
\r
1563 char buf[MSG_SIZ];
\r
1567 /* [HGM] skip over optional board-size prefixes */
\r
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1570 while( *e++ != '_');
\r
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1574 if (StrCaseStr(e, variantNames[i])) {
\r
1575 v = (VariantClass) i;
\r
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1583 || StrCaseStr(e, "wild/fr")
\r
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1585 v = VariantFischeRandom;
\r
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1587 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1590 if (isdigit(*p)) {
\r
1596 case 0: /* FICS only, actually */
\r
1598 /* Castling legal even if K starts on d-file */
\r
1599 v = VariantWildCastle;
\r
1604 /* Castling illegal even if K & R happen to start in
\r
1605 normal positions. */
\r
1606 v = VariantNoCastle;
\r
1619 /* Castling legal iff K & R start in normal positions */
\r
1620 v = VariantNormal;
\r
1625 /* Special wilds for position setup; unclear what to do here */
\r
1626 v = VariantLoadable;
\r
1629 /* Bizarre ICC game */
\r
1630 v = VariantTwoKings;
\r
1633 v = VariantKriegspiel;
\r
1636 v = VariantLosers;
\r
1639 v = VariantFischeRandom;
\r
1642 v = VariantCrazyhouse;
\r
1645 v = VariantBughouse;
\r
1648 v = Variant3Check;
\r
1651 /* Not quite the same as FICS suicide! */
\r
1652 v = VariantGiveaway;
\r
1655 v = VariantAtomic;
\r
1658 v = VariantShatranj;
\r
1661 /* Temporary names for future ICC types. The name *will* change in
\r
1662 the next xboard/WinBoard release after ICC defines it. */
\r
1691 v = VariantXiangqi;
\r
1694 v = VariantCourier;
\r
1697 v = VariantGothic;
\r
1700 v = VariantCapablanca;
\r
1703 v = VariantKnightmate;
\r
1709 v = VariantCylinder;
\r
1712 v = VariantFalcon;
\r
1715 v = VariantCapaRandom;
\r
1718 v = VariantBerolina;
\r
1730 /* Found "wild" or "w" in the string but no number;
\r
1731 must assume it's normal chess. */
\r
1732 v = VariantNormal;
\r
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1736 DisplayError(buf, 0);
\r
1737 v = VariantUnknown;
\r
1742 if (appData.debugMode) {
\r
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1744 e, wnum, VariantName(v));
\r
1749 static int leftover_start = 0, leftover_len = 0;
\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1753 advance *index beyond it, and set leftover_start to the new value of
\r
1754 *index; else return FALSE. If pattern contains the character '*', it
\r
1755 matches any sequence of characters not containing '\r', '\n', or the
\r
1756 character following the '*' (if any), and the matched sequence(s) are
\r
1757 copied into star_match.
\r
1760 looking_at(buf, index, pattern)
\r
1765 char *bufp = &buf[*index], *patternp = pattern;
\r
1766 int star_count = 0;
\r
1767 char *matchp = star_match[0];
\r
1770 if (*patternp == NULLCHAR) {
\r
1771 *index = leftover_start = bufp - buf;
\r
1772 *matchp = NULLCHAR;
\r
1775 if (*bufp == NULLCHAR) return FALSE;
\r
1776 if (*patternp == '*') {
\r
1777 if (*bufp == *(patternp + 1)) {
\r
1778 *matchp = NULLCHAR;
\r
1779 matchp = star_match[++star_count];
\r
1783 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1785 if (*patternp == NULLCHAR)
\r
1790 *matchp++ = *bufp++;
\r
1794 if (*patternp != *bufp) return FALSE;
\r
1801 SendToPlayer(data, length)
\r
1805 int error, outCount;
\r
1806 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1807 if (outCount < length) {
\r
1808 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1813 PackHolding(packed, holding)
\r
1817 char *p = holding;
\r
1819 int runlength = 0;
\r
1825 switch (runlength) {
\r
1836 sprintf(q, "%d", runlength);
\r
1848 /* Telnet protocol requests from the front end */
\r
1850 TelnetRequest(ddww, option)
\r
1851 unsigned char ddww, option;
\r
1853 unsigned char msg[3];
\r
1854 int outCount, outError;
\r
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1858 if (appData.debugMode) {
\r
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1875 sprintf(buf1, "%d", ddww);
\r
1880 optionStr = "ECHO";
\r
1884 sprintf(buf2, "%d", option);
\r
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1893 if (outCount < 3) {
\r
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1901 if (!appData.icsActive) return;
\r
1902 TelnetRequest(TN_DO, TN_ECHO);
\r
1908 if (!appData.icsActive) return;
\r
1909 TelnetRequest(TN_DONT, TN_ECHO);
\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1915 /* put the holdings sent to us by the server on the board holdings area */
\r
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1918 ChessSquare piece;
\r
1920 if(gameInfo.holdingsWidth < 2) return;
\r
1922 if( (int)lowestPiece >= BlackPawn ) {
\r
1923 holdingsColumn = 0;
\r
1925 holdingsStartRow = BOARD_HEIGHT-1;
\r
1928 holdingsColumn = BOARD_WIDTH-1;
\r
1929 countsColumn = BOARD_WIDTH-2;
\r
1930 holdingsStartRow = 0;
\r
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1935 board[i][holdingsColumn] = EmptySquare;
\r
1936 board[i][countsColumn] = (ChessSquare) 0;
\r
1938 while( (p=*holdings++) != NULLCHAR ) {
\r
1939 piece = CharToPiece( ToUpper(p) );
\r
1940 if(piece == EmptySquare) continue;
\r
1941 /*j = (int) piece - (int) WhitePawn;*/
\r
1942 j = PieceToNumber(piece);
\r
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1944 if(j < 0) continue; /* should not happen */
\r
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1947 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1954 VariantSwitch(Board board, VariantClass newVariant)
\r
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1960 startedFromPositionFile = FALSE;
\r
1961 if(gameInfo.variant == newVariant) return;
\r
1963 /* [HGM] This routine is called each time an assignment is made to
\r
1964 * gameInfo.variant during a game, to make sure the board sizes
\r
1965 * are set to match the new variant. If that means adding or deleting
\r
1966 * holdings, we shift the playing board accordingly
\r
1967 * This kludge is needed because in ICS observe mode, we get boards
\r
1968 * of an ongoing game without knowing the variant, and learn about the
\r
1969 * latter only later. This can be because of the move list we requested,
\r
1970 * in which case the game history is refilled from the beginning anyway,
\r
1971 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1972 * case we want to add those holdings to the already received position.
\r
1976 if (appData.debugMode) {
\r
1977 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1978 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1979 setbuf(debugFP, NULL);
\r
1981 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1983 switch(newVariant) {
\r
1984 case VariantShogi:
\r
1985 newWidth = 9; newHeight = 9;
\r
1986 gameInfo.holdingsSize = 7;
\r
1987 case VariantBughouse:
\r
1988 case VariantCrazyhouse:
\r
1989 newHoldingsWidth = 2; break;
\r
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1994 if(newWidth != gameInfo.boardWidth ||
\r
1995 newHeight != gameInfo.boardHeight ||
\r
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1998 /* shift position to new playing area, if needed */
\r
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
2000 for(i=0; i<BOARD_HEIGHT; i++)
\r
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2004 for(i=0; i<newHeight; i++) {
\r
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2009 for(i=0; i<BOARD_HEIGHT; i++)
\r
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2015 gameInfo.boardWidth = newWidth;
\r
2016 gameInfo.boardHeight = newHeight;
\r
2017 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2018 gameInfo.variant = newVariant;
\r
2019 InitDrawingSizes(-2, 0);
\r
2021 /* [HGM] The following should definitely be solved in a better way */
\r
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2025 saveEP = epStatus[0];
\r
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2029 epStatus[0] = saveEP;
\r
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2035 forwardMostMove = oldForwardMostMove;
\r
2036 backwardMostMove = oldBackwardMostMove;
\r
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2040 static int loggedOn = FALSE;
\r
2042 /*-- Game start info cache: --*/
\r
2044 char gs_kind[MSG_SIZ];
\r
2045 static char player1Name[128] = "";
\r
2046 static char player2Name[128] = "";
\r
2047 static int player1Rating = -1;
\r
2048 static int player2Rating = -1;
\r
2049 /*----------------------------*/
\r
2051 ColorClass curColor = ColorNormal;
\r
2052 int suppressKibitz = 0;
\r
2055 read_from_ics(isr, closure, data, count, error)
\r
2056 InputSourceRef isr;
\r
2062 #define BUF_SIZE 8192
\r
2063 #define STARTED_NONE 0
\r
2064 #define STARTED_MOVES 1
\r
2065 #define STARTED_BOARD 2
\r
2066 #define STARTED_OBSERVE 3
\r
2067 #define STARTED_HOLDINGS 4
\r
2068 #define STARTED_CHATTER 5
\r
2069 #define STARTED_COMMENT 6
\r
2070 #define STARTED_MOVES_NOHIDE 7
\r
2072 static int started = STARTED_NONE;
\r
2073 static char parse[20000];
\r
2074 static int parse_pos = 0;
\r
2075 static char buf[BUF_SIZE + 1];
\r
2076 static int firstTime = TRUE, intfSet = FALSE;
\r
2077 static ColorClass prevColor = ColorNormal;
\r
2078 static int savingComment = FALSE;
\r
2084 int backup; /* [DM] For zippy color lines */
\r
2087 if (appData.debugMode) {
\r
2089 fprintf(debugFP, "<ICS: ");
\r
2090 show_bytes(debugFP, data, count);
\r
2091 fprintf(debugFP, "\n");
\r
2095 if (appData.debugMode) { int f = forwardMostMove;
\r
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2100 /* If last read ended with a partial line that we couldn't parse,
\r
2101 prepend it to the new read and try again. */
\r
2102 if (leftover_len > 0) {
\r
2103 for (i=0; i<leftover_len; i++)
\r
2104 buf[i] = buf[leftover_start + i];
\r
2107 /* Copy in new characters, removing nulls and \r's */
\r
2108 buf_len = leftover_len;
\r
2109 for (i = 0; i < count; i++) {
\r
2110 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2111 buf[buf_len++] = data[i];
\r
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2117 buf[buf_len] = NULLCHAR;
\r
2118 next_out = leftover_len;
\r
2119 leftover_start = 0;
\r
2122 while (i < buf_len) {
\r
2123 /* Deal with part of the TELNET option negotiation
\r
2124 protocol. We refuse to do anything beyond the
\r
2125 defaults, except that we allow the WILL ECHO option,
\r
2126 which ICS uses to turn off password echoing when we are
\r
2127 directly connected to it. We reject this option
\r
2128 if localLineEditing mode is on (always on in xboard)
\r
2129 and we are talking to port 23, which might be a real
\r
2130 telnet server that will try to keep WILL ECHO on permanently.
\r
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2134 unsigned char option;
\r
2136 switch ((unsigned char) buf[++i]) {
\r
2138 if (appData.debugMode)
\r
2139 fprintf(debugFP, "\n<WILL ");
\r
2140 switch (option = (unsigned char) buf[++i]) {
\r
2142 if (appData.debugMode)
\r
2143 fprintf(debugFP, "ECHO ");
\r
2144 /* Reply only if this is a change, according
\r
2145 to the protocol rules. */
\r
2146 if (remoteEchoOption) break;
\r
2147 if (appData.localLineEditing &&
\r
2148 atoi(appData.icsPort) == TN_PORT) {
\r
2149 TelnetRequest(TN_DONT, TN_ECHO);
\r
2152 TelnetRequest(TN_DO, TN_ECHO);
\r
2153 remoteEchoOption = TRUE;
\r
2157 if (appData.debugMode)
\r
2158 fprintf(debugFP, "%d ", option);
\r
2159 /* Whatever this is, we don't want it. */
\r
2160 TelnetRequest(TN_DONT, option);
\r
2165 if (appData.debugMode)
\r
2166 fprintf(debugFP, "\n<WONT ");
\r
2167 switch (option = (unsigned char) buf[++i]) {
\r
2169 if (appData.debugMode)
\r
2170 fprintf(debugFP, "ECHO ");
\r
2171 /* Reply only if this is a change, according
\r
2172 to the protocol rules. */
\r
2173 if (!remoteEchoOption) break;
\r
2175 TelnetRequest(TN_DONT, TN_ECHO);
\r
2176 remoteEchoOption = FALSE;
\r
2179 if (appData.debugMode)
\r
2180 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2181 /* Whatever this is, it must already be turned
\r
2182 off, because we never agree to turn on
\r
2183 anything non-default, so according to the
\r
2184 protocol rules, we don't reply. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "\n<DO ");
\r
2191 switch (option = (unsigned char) buf[++i]) {
\r
2193 /* Whatever this is, we refuse to do it. */
\r
2194 if (appData.debugMode)
\r
2195 fprintf(debugFP, "%d ", option);
\r
2196 TelnetRequest(TN_WONT, option);
\r
2201 if (appData.debugMode)
\r
2202 fprintf(debugFP, "\n<DONT ");
\r
2203 switch (option = (unsigned char) buf[++i]) {
\r
2205 if (appData.debugMode)
\r
2206 fprintf(debugFP, "%d ", option);
\r
2207 /* Whatever this is, we are already not doing
\r
2208 it, because we never agree to do anything
\r
2209 non-default, so according to the protocol
\r
2210 rules, we don't reply. */
\r
2215 if (appData.debugMode)
\r
2216 fprintf(debugFP, "\n<IAC ");
\r
2217 /* Doubled IAC; pass it through */
\r
2221 if (appData.debugMode)
\r
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2223 /* Drop all other telnet commands on the floor */
\r
2226 if (oldi > next_out)
\r
2227 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2228 if (++i > next_out)
\r
2233 /* OK, this at least will *usually* work */
\r
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2238 if (loggedOn && !intfSet) {
\r
2239 if (ics_type == ICS_ICC) {
\r
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2244 } else if (ics_type == ICS_CHESSNET) {
\r
2245 sprintf(str, "/style 12\n");
\r
2247 strcpy(str, "alias $ @\n$set interface ");
\r
2248 strcat(str, programVersion);
\r
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2251 strcat(str, "$iset nohighlight 1\n");
\r
2253 strcat(str, "$iset lock 1\n$style 12\n");
\r
2259 if (started == STARTED_COMMENT) {
\r
2260 /* Accumulate characters in comment */
\r
2261 parse[parse_pos++] = buf[i];
\r
2262 if (buf[i] == '\n') {
\r
2263 parse[parse_pos] = NULLCHAR;
\r
2264 if(!suppressKibitz) // [HGM] kibitz
\r
2265 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2267 int nrDigit = 0, nrAlph = 0, i;
\r
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2270 parse[parse_pos] = NULLCHAR;
\r
2271 // try to be smart: if it does not look like search info, it should go to
\r
2272 // ICS interaction window after all, not to engine-output window.
\r
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2279 OutputKibitz(suppressKibitz, parse);
\r
2281 char tmp[MSG_SIZ];
\r
2282 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2283 SendToPlayer(tmp, strlen(tmp));
\r
2286 started = STARTED_NONE;
\r
2288 /* Don't match patterns against characters in chatter */
\r
2293 if (started == STARTED_CHATTER) {
\r
2294 if (buf[i] != '\n') {
\r
2295 /* Don't match patterns against characters in chatter */
\r
2299 started = STARTED_NONE;
\r
2302 /* Kludge to deal with rcmd protocol */
\r
2303 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2304 DisplayFatalError(&buf[1], 0, 1);
\r
2307 firstTime = FALSE;
\r
2310 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2311 ics_type = ICS_ICC;
\r
2313 if (appData.debugMode)
\r
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2317 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2318 ics_type = ICS_FICS;
\r
2320 if (appData.debugMode)
\r
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2324 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2325 ics_type = ICS_CHESSNET;
\r
2327 if (appData.debugMode)
\r
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2333 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2334 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2335 looking_at(buf, &i, "will be \"*\""))) {
\r
2336 strcpy(ics_handle, star_match[0]);
\r
2340 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2341 char buf[MSG_SIZ];
\r
2342 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2343 DisplayIcsInteractionTitle(buf);
\r
2344 have_set_title = TRUE;
\r
2347 /* skip finger notes */
\r
2348 if (started == STARTED_NONE &&
\r
2349 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2350 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2351 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2352 started = STARTED_CHATTER;
\r
2357 /* skip formula vars */
\r
2358 if (started == STARTED_NONE &&
\r
2359 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2360 started = STARTED_CHATTER;
\r
2366 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2367 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2368 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2369 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2370 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2371 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2372 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2373 suppressKibitz = TRUE;
\r
2374 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2375 && (gameMode == IcsPlayingWhite)) ||
\r
2376 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2377 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2378 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2380 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2381 parse_pos = 0; parse[0] = NULLCHAR;
\r
2382 savingComment = TRUE;
\r
2383 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2384 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2388 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2389 started = STARTED_CHATTER;
\r
2390 suppressKibitz = TRUE;
\r
2392 } // [HGM] kibitz: end of patch
\r
2394 if (appData.zippyTalk || appData.zippyPlay) {
\r
2395 /* [DM] Backup address for color zippy lines */
\r
2399 if (loggedOn == TRUE)
\r
2400 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2401 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2403 if (ZippyControl(buf, &i) ||
\r
2404 ZippyConverse(buf, &i) ||
\r
2405 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2407 if (!appData.colorize) continue;
\r
2411 } // [DM] 'else { ' deleted
\r
2412 if (/* Don't color "message" or "messages" output */
\r
2413 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2414 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2415 looking_at(buf, &i, "--* (*:*): ") ||
\r
2416 /* Regular tells and says */
\r
2417 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2418 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2419 looking_at(buf, &i, "* says: ") ||
\r
2420 /* Message notifications (same color as tells) */
\r
2421 looking_at(buf, &i, "* has left a message ") ||
\r
2422 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2423 /* Whispers and kibitzes */
\r
2424 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2425 looking_at(buf, &i, "* kibitzes: ") ||
\r
2426 /* Channel tells */
\r
2427 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2429 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2430 /* Avoid "tells you:" spoofs in channels */
\r
2433 if (star_match[0][0] == NULLCHAR ||
\r
2434 strchr(star_match[0], ' ') ||
\r
2435 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2436 /* Reject bogus matches */
\r
2439 if (appData.colorize) {
\r
2440 if (oldi > next_out) {
\r
2441 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2446 Colorize(ColorTell, FALSE);
\r
2447 curColor = ColorTell;
\r
2450 Colorize(ColorKibitz, FALSE);
\r
2451 curColor = ColorKibitz;
\r
2454 p = strrchr(star_match[1], '(');
\r
2456 p = star_match[1];
\r
2460 if (atoi(p) == 1) {
\r
2461 Colorize(ColorChannel1, FALSE);
\r
2462 curColor = ColorChannel1;
\r
2464 Colorize(ColorChannel, FALSE);
\r
2465 curColor = ColorChannel;
\r
2469 curColor = ColorNormal;
\r
2473 if (started == STARTED_NONE && appData.autoComment &&
\r
2474 (gameMode == IcsObserving ||
\r
2475 gameMode == IcsPlayingWhite ||
\r
2476 gameMode == IcsPlayingBlack)) {
\r
2477 parse_pos = i - oldi;
\r
2478 memcpy(parse, &buf[oldi], parse_pos);
\r
2479 parse[parse_pos] = NULLCHAR;
\r
2480 started = STARTED_COMMENT;
\r
2481 savingComment = TRUE;
\r
2483 started = STARTED_CHATTER;
\r
2484 savingComment = FALSE;
\r
2491 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2492 looking_at(buf, &i, "* c-shouts: ")) {
\r
2493 if (appData.colorize) {
\r
2494 if (oldi > next_out) {
\r
2495 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2498 Colorize(ColorSShout, FALSE);
\r
2499 curColor = ColorSShout;
\r
2502 started = STARTED_CHATTER;
\r
2506 if (looking_at(buf, &i, "--->")) {
\r
2511 if (looking_at(buf, &i, "* shouts: ") ||
\r
2512 looking_at(buf, &i, "--> ")) {
\r
2513 if (appData.colorize) {
\r
2514 if (oldi > next_out) {
\r
2515 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2518 Colorize(ColorShout, FALSE);
\r
2519 curColor = ColorShout;
\r
2522 started = STARTED_CHATTER;
\r
2526 if (looking_at( buf, &i, "Challenge:")) {
\r
2527 if (appData.colorize) {
\r
2528 if (oldi > next_out) {
\r
2529 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2532 Colorize(ColorChallenge, FALSE);
\r
2533 curColor = ColorChallenge;
\r
2539 if (looking_at(buf, &i, "* offers you") ||
\r
2540 looking_at(buf, &i, "* offers to be") ||
\r
2541 looking_at(buf, &i, "* would like to") ||
\r
2542 looking_at(buf, &i, "* requests to") ||
\r
2543 looking_at(buf, &i, "Your opponent offers") ||
\r
2544 looking_at(buf, &i, "Your opponent requests")) {
\r
2546 if (appData.colorize) {
\r
2547 if (oldi > next_out) {
\r
2548 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2551 Colorize(ColorRequest, FALSE);
\r
2552 curColor = ColorRequest;
\r
2557 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2558 if (appData.colorize) {
\r
2559 if (oldi > next_out) {
\r
2560 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2563 Colorize(ColorSeek, FALSE);
\r
2564 curColor = ColorSeek;
\r
2569 if (looking_at(buf, &i, "\\ ")) {
\r
2570 if (prevColor != ColorNormal) {
\r
2571 if (oldi > next_out) {
\r
2572 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2575 Colorize(prevColor, TRUE);
\r
2576 curColor = prevColor;
\r
2578 if (savingComment) {
\r
2579 parse_pos = i - oldi;
\r
2580 memcpy(parse, &buf[oldi], parse_pos);
\r
2581 parse[parse_pos] = NULLCHAR;
\r
2582 started = STARTED_COMMENT;
\r
2584 started = STARTED_CHATTER;
\r
2589 if (looking_at(buf, &i, "Black Strength :") ||
\r
2590 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2591 looking_at(buf, &i, "<10>") ||
\r
2592 looking_at(buf, &i, "#@#")) {
\r
2593 /* Wrong board style */
\r
2595 SendToICS(ics_prefix);
\r
2596 SendToICS("set style 12\n");
\r
2597 SendToICS(ics_prefix);
\r
2598 SendToICS("refresh\n");
\r
2602 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2604 have_sent_ICS_logon = 1;
\r
2608 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2609 (looking_at(buf, &i, "\n<12> ") ||
\r
2610 looking_at(buf, &i, "<12> "))) {
\r
2612 if (oldi > next_out) {
\r
2613 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2616 started = STARTED_BOARD;
\r
2621 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2622 looking_at(buf, &i, "<b1> ")) {
\r
2623 if (oldi > next_out) {
\r
2624 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2627 started = STARTED_HOLDINGS;
\r
2632 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2634 /* Header for a move list -- first line */
\r
2636 switch (ics_getting_history) {
\r
2638 switch (gameMode) {
\r
2640 case BeginningOfGame:
\r
2641 /* User typed "moves" or "oldmoves" while we
\r
2642 were idle. Pretend we asked for these
\r
2643 moves and soak them up so user can step
\r
2644 through them and/or save them.
\r
2646 Reset(FALSE, TRUE);
\r
2647 gameMode = IcsObserving;
\r
2650 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2652 case EditGame: /*?*/
\r
2653 case EditPosition: /*?*/
\r
2654 /* Should above feature work in these modes too? */
\r
2655 /* For now it doesn't */
\r
2656 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2659 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2664 /* Is this the right one? */
\r
2665 if (gameInfo.white && gameInfo.black &&
\r
2666 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2667 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2669 ics_getting_history = H_GOT_REQ_HEADER;
\r
2672 case H_GOT_REQ_HEADER:
\r
2673 case H_GOT_UNREQ_HEADER:
\r
2674 case H_GOT_UNWANTED_HEADER:
\r
2675 case H_GETTING_MOVES:
\r
2676 /* Should not happen */
\r
2677 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2678 ics_getting_history = H_FALSE;
\r
2682 /* Save player ratings into gameInfo if needed */
\r
2683 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2684 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2685 (gameInfo.whiteRating == -1 ||
\r
2686 gameInfo.blackRating == -1)) {
\r
2688 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2689 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2690 if (appData.debugMode)
\r
2691 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2692 gameInfo.whiteRating, gameInfo.blackRating);
\r
2697 if (looking_at(buf, &i,
\r
2698 "* * match, initial time: * minute*, increment: * second")) {
\r
2699 /* Header for a move list -- second line */
\r
2700 /* Initial board will follow if this is a wild game */
\r
2701 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2702 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2703 gameInfo.event = StrSave(str);
\r
2704 /* [HGM] we switched variant. Translate boards if needed. */
\r
2705 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2709 if (looking_at(buf, &i, "Move ")) {
\r
2710 /* Beginning of a move list */
\r
2711 switch (ics_getting_history) {
\r
2713 /* Normally should not happen */
\r
2714 /* Maybe user hit reset while we were parsing */
\r
2717 /* Happens if we are ignoring a move list that is not
\r
2718 * the one we just requested. Common if the user
\r
2719 * tries to observe two games without turning off
\r
2722 case H_GETTING_MOVES:
\r
2723 /* Should not happen */
\r
2724 DisplayError(_("Error gathering move list: nested"), 0);
\r
2725 ics_getting_history = H_FALSE;
\r
2727 case H_GOT_REQ_HEADER:
\r
2728 ics_getting_history = H_GETTING_MOVES;
\r
2729 started = STARTED_MOVES;
\r
2731 if (oldi > next_out) {
\r
2732 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2735 case H_GOT_UNREQ_HEADER:
\r
2736 ics_getting_history = H_GETTING_MOVES;
\r
2737 started = STARTED_MOVES_NOHIDE;
\r
2740 case H_GOT_UNWANTED_HEADER:
\r
2741 ics_getting_history = H_FALSE;
\r
2747 if (looking_at(buf, &i, "% ") ||
\r
2748 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2749 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2750 savingComment = FALSE;
\r
2751 switch (started) {
\r
2752 case STARTED_MOVES:
\r
2753 case STARTED_MOVES_NOHIDE:
\r
2754 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2755 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2756 ParseGameHistory(parse);
\r
2758 if (appData.zippyPlay && first.initDone) {
\r
2759 FeedMovesToProgram(&first, forwardMostMove);
\r
2760 if (gameMode == IcsPlayingWhite) {
\r
2761 if (WhiteOnMove(forwardMostMove)) {
\r
2762 if (first.sendTime) {
\r
2763 if (first.useColors) {
\r
2764 SendToProgram("black\n", &first);
\r
2766 SendTimeRemaining(&first, TRUE);
\r
2769 if (first.useColors) {
\r
2770 SendToProgram("white\ngo\n", &first);
\r
2772 SendToProgram("go\n", &first);
\r
2775 if (first.useColors) {
\r
2776 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2778 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2780 first.maybeThinking = TRUE;
\r
2782 if (first.usePlayother) {
\r
2783 if (first.sendTime) {
\r
2784 SendTimeRemaining(&first, TRUE);
\r
2786 SendToProgram("playother\n", &first);
\r
2787 firstMove = FALSE;
\r
2792 } else if (gameMode == IcsPlayingBlack) {
\r
2793 if (!WhiteOnMove(forwardMostMove)) {
\r
2794 if (first.sendTime) {
\r
2795 if (first.useColors) {
\r
2796 SendToProgram("white\n", &first);
\r
2798 SendTimeRemaining(&first, FALSE);
\r
2801 if (first.useColors) {
\r
2802 SendToProgram("black\ngo\n", &first);
\r
2804 SendToProgram("go\n", &first);
\r
2807 if (first.useColors) {
\r
2808 SendToProgram("black\n", &first);
\r
2810 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2812 first.maybeThinking = TRUE;
\r
2814 if (first.usePlayother) {
\r
2815 if (first.sendTime) {
\r
2816 SendTimeRemaining(&first, FALSE);
\r
2818 SendToProgram("playother\n", &first);
\r
2819 firstMove = FALSE;
\r
2827 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2828 /* Moves came from oldmoves or moves command
\r
2829 while we weren't doing anything else.
\r
2831 currentMove = forwardMostMove;
\r
2832 ClearHighlights();/*!!could figure this out*/
\r
2833 flipView = appData.flipView;
\r
2834 DrawPosition(FALSE, boards[currentMove]);
\r
2835 DisplayBothClocks();
\r
2836 sprintf(str, "%s vs. %s",
\r
2837 gameInfo.white, gameInfo.black);
\r
2838 DisplayTitle(str);
\r
2839 gameMode = IcsIdle;
\r
2841 /* Moves were history of an active game */
\r
2842 if (gameInfo.resultDetails != NULL) {
\r
2843 free(gameInfo.resultDetails);
\r
2844 gameInfo.resultDetails = NULL;
\r
2847 HistorySet(parseList, backwardMostMove,
\r
2848 forwardMostMove, currentMove-1);
\r
2849 DisplayMove(currentMove - 1);
\r
2850 if (started == STARTED_MOVES) next_out = i;
\r
2851 started = STARTED_NONE;
\r
2852 ics_getting_history = H_FALSE;
\r
2855 case STARTED_OBSERVE:
\r
2856 started = STARTED_NONE;
\r
2857 SendToICS(ics_prefix);
\r
2858 SendToICS("refresh\n");
\r
2864 if(bookHit) { // [HGM] book: simulate book reply
\r
2865 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2867 programStats.nodes = programStats.depth = programStats.time =
\r
2868 programStats.score = programStats.got_only_move = 0;
\r
2869 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2871 strcpy(bookMove, "move ");
\r
2872 strcat(bookMove, bookHit);
\r
2873 HandleMachineMove(bookMove, &first);
\r
2878 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2879 started == STARTED_HOLDINGS ||
\r
2880 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2881 /* Accumulate characters in move list or board */
\r
2882 parse[parse_pos++] = buf[i];
\r
2885 /* Start of game messages. Mostly we detect start of game
\r
2886 when the first board image arrives. On some versions
\r
2887 of the ICS, though, we need to do a "refresh" after starting
\r
2888 to observe in order to get the current board right away. */
\r
2889 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2890 started = STARTED_OBSERVE;
\r
2894 /* Handle auto-observe */
\r
2895 if (appData.autoObserve &&
\r
2896 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2897 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2899 /* Choose the player that was highlighted, if any. */
\r
2900 if (star_match[0][0] == '\033' ||
\r
2901 star_match[1][0] != '\033') {
\r
2902 player = star_match[0];
\r
2904 player = star_match[2];
\r
2906 sprintf(str, "%sobserve %s\n",
\r
2907 ics_prefix, StripHighlightAndTitle(player));
\r
2910 /* Save ratings from notify string */
\r
2911 strcpy(player1Name, star_match[0]);
\r
2912 player1Rating = string_to_rating(star_match[1]);
\r
2913 strcpy(player2Name, star_match[2]);
\r
2914 player2Rating = string_to_rating(star_match[3]);
\r
2916 if (appData.debugMode)
\r
2918 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2919 player1Name, player1Rating,
\r
2920 player2Name, player2Rating);
\r
2925 /* Deal with automatic examine mode after a game,
\r
2926 and with IcsObserving -> IcsExamining transition */
\r
2927 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2928 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2930 int gamenum = atoi(star_match[0]);
\r
2931 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2932 gamenum == ics_gamenum) {
\r
2933 /* We were already playing or observing this game;
\r
2934 no need to refetch history */
\r
2935 gameMode = IcsExamining;
\r
2937 pauseExamForwardMostMove = forwardMostMove;
\r
2938 } else if (currentMove < forwardMostMove) {
\r
2939 ForwardInner(forwardMostMove);
\r
2942 /* I don't think this case really can happen */
\r
2943 SendToICS(ics_prefix);
\r
2944 SendToICS("refresh\n");
\r
2949 /* Error messages */
\r
2950 if (ics_user_moved) {
\r
2951 if (looking_at(buf, &i, "Illegal move") ||
\r
2952 looking_at(buf, &i, "Not a legal move") ||
\r
2953 looking_at(buf, &i, "Your king is in check") ||
\r
2954 looking_at(buf, &i, "It isn't your turn") ||
\r
2955 looking_at(buf, &i, "It is not your move")) {
\r
2956 /* Illegal move */
\r
2957 ics_user_moved = 0;
\r
2958 if (forwardMostMove > backwardMostMove) {
\r
2959 currentMove = --forwardMostMove;
\r
2960 DisplayMove(currentMove - 1); /* before DMError */
\r
2961 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2962 DrawPosition(FALSE, boards[currentMove]);
\r
2964 DisplayBothClocks();
\r
2970 if (looking_at(buf, &i, "still have time") ||
\r
2971 looking_at(buf, &i, "not out of time") ||
\r
2972 looking_at(buf, &i, "either player is out of time") ||
\r
2973 looking_at(buf, &i, "has timeseal; checking")) {
\r
2974 /* We must have called his flag a little too soon */
\r
2975 whiteFlag = blackFlag = FALSE;
\r
2979 if (looking_at(buf, &i, "added * seconds to") ||
\r
2980 looking_at(buf, &i, "seconds were added to")) {
\r
2981 /* Update the clocks */
\r
2982 SendToICS(ics_prefix);
\r
2983 SendToICS("refresh\n");
\r
2987 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2988 ics_clock_paused = TRUE;
\r
2993 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2994 ics_clock_paused = FALSE;
\r
2999 /* Grab player ratings from the Creating: message.
\r
3000 Note we have to check for the special case when
\r
3001 the ICS inserts things like [white] or [black]. */
\r
3002 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3003 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3005 0 player 1 name (not necessarily white)
\r
3007 2 empty, white, or black (IGNORED)
\r
3008 3 player 2 name (not necessarily black)
\r
3011 The names/ratings are sorted out when the game
\r
3012 actually starts (below).
\r
3014 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3015 player1Rating = string_to_rating(star_match[1]);
\r
3016 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3017 player2Rating = string_to_rating(star_match[4]);
\r
3019 if (appData.debugMode)
\r
3021 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3022 player1Name, player1Rating,
\r
3023 player2Name, player2Rating);
\r
3028 /* Improved generic start/end-of-game messages */
\r
3029 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3030 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3031 /* If tkind == 0: */
\r
3032 /* star_match[0] is the game number */
\r
3033 /* [1] is the white player's name */
\r
3034 /* [2] is the black player's name */
\r
3035 /* For end-of-game: */
\r
3036 /* [3] is the reason for the game end */
\r
3037 /* [4] is a PGN end game-token, preceded by " " */
\r
3038 /* For start-of-game: */
\r
3039 /* [3] begins with "Creating" or "Continuing" */
\r
3040 /* [4] is " *" or empty (don't care). */
\r
3041 int gamenum = atoi(star_match[0]);
\r
3042 char *whitename, *blackname, *why, *endtoken;
\r
3043 ChessMove endtype = (ChessMove) 0;
\r
3046 whitename = star_match[1];
\r
3047 blackname = star_match[2];
\r
3048 why = star_match[3];
\r
3049 endtoken = star_match[4];
\r
3051 whitename = star_match[1];
\r
3052 blackname = star_match[3];
\r
3053 why = star_match[5];
\r
3054 endtoken = star_match[6];
\r
3057 /* Game start messages */
\r
3058 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3059 strncmp(why, "Continuing ", 11) == 0) {
\r
3060 gs_gamenum = gamenum;
\r
3061 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3063 if (appData.zippyPlay) {
\r
3064 ZippyGameStart(whitename, blackname);
\r
3070 /* Game end messages */
\r
3071 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3072 ics_gamenum != gamenum) {
\r
3075 while (endtoken[0] == ' ') endtoken++;
\r
3076 switch (endtoken[0]) {
\r
3079 endtype = GameUnfinished;
\r
3082 endtype = BlackWins;
\r
3085 if (endtoken[1] == '/')
\r
3086 endtype = GameIsDrawn;
\r
3088 endtype = WhiteWins;
\r
3091 GameEnds(endtype, why, GE_ICS);
\r
3093 if (appData.zippyPlay && first.initDone) {
\r
3094 ZippyGameEnd(endtype, why);
\r
3095 if (first.pr == NULL) {
\r
3096 /* Start the next process early so that we'll
\r
3097 be ready for the next challenge */
\r
3098 StartChessProgram(&first);
\r
3100 /* Send "new" early, in case this command takes
\r
3101 a long time to finish, so that we'll be ready
\r
3102 for the next challenge. */
\r
3103 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3104 Reset(TRUE, TRUE);
\r
3110 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3111 looking_at(buf, &i, "no longer observing game *") ||
\r
3112 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3113 if (gameMode == IcsObserving &&
\r
3114 atoi(star_match[0]) == ics_gamenum)
\r
3116 /* icsEngineAnalyze */
\r
3117 if (appData.icsEngineAnalyze) {
\r
3118 ExitAnalyzeMode();
\r
3122 gameMode = IcsIdle;
\r
3124 ics_user_moved = FALSE;
\r
3129 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3130 if (gameMode == IcsExamining &&
\r
3131 atoi(star_match[0]) == ics_gamenum)
\r
3133 gameMode = IcsIdle;
\r
3135 ics_user_moved = FALSE;
\r
3140 /* Advance leftover_start past any newlines we find,
\r
3141 so only partial lines can get reparsed */
\r
3142 if (looking_at(buf, &i, "\n")) {
\r
3143 prevColor = curColor;
\r
3144 if (curColor != ColorNormal) {
\r
3145 if (oldi > next_out) {
\r
3146 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3149 Colorize(ColorNormal, FALSE);
\r
3150 curColor = ColorNormal;
\r
3152 if (started == STARTED_BOARD) {
\r
3153 started = STARTED_NONE;
\r
3154 parse[parse_pos] = NULLCHAR;
\r
3155 ParseBoard12(parse);
\r
3156 ics_user_moved = 0;
\r
3158 /* Send premove here */
\r
3159 if (appData.premove) {
\r
3160 char str[MSG_SIZ];
\r
3161 if (currentMove == 0 &&
\r
3162 gameMode == IcsPlayingWhite &&
\r
3163 appData.premoveWhite) {
\r
3164 sprintf(str, "%s%s\n", ics_prefix,
\r
3165 appData.premoveWhiteText);
\r
3166 if (appData.debugMode)
\r
3167 fprintf(debugFP, "Sending premove:\n");
\r
3169 } else if (currentMove == 1 &&
\r
3170 gameMode == IcsPlayingBlack &&
\r
3171 appData.premoveBlack) {
\r
3172 sprintf(str, "%s%s\n", ics_prefix,
\r
3173 appData.premoveBlackText);
\r
3174 if (appData.debugMode)
\r
3175 fprintf(debugFP, "Sending premove:\n");
\r
3177 } else if (gotPremove) {
\r
3179 ClearPremoveHighlights();
\r
3180 if (appData.debugMode)
\r
3181 fprintf(debugFP, "Sending premove:\n");
\r
3182 UserMoveEvent(premoveFromX, premoveFromY,
\r
3183 premoveToX, premoveToY,
\r
3184 premovePromoChar);
\r
3188 /* Usually suppress following prompt */
\r
3189 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3190 if (looking_at(buf, &i, "*% ")) {
\r
3191 savingComment = FALSE;
\r
3195 } else if (started == STARTED_HOLDINGS) {
\r
3197 char new_piece[MSG_SIZ];
\r
3198 started = STARTED_NONE;
\r
3199 parse[parse_pos] = NULLCHAR;
\r
3200 if (appData.debugMode)
\r
3201 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3202 parse, currentMove);
\r
3203 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3204 gamenum == ics_gamenum) {
\r
3205 if (gameInfo.variant == VariantNormal) {
\r
3206 /* [HGM] We seem to switch variant during a game!
\r
3207 * Presumably no holdings were displayed, so we have
\r
3208 * to move the position two files to the right to
\r
3209 * create room for them!
\r
3211 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3212 /* Get a move list just to see the header, which
\r
3213 will tell us whether this is really bug or zh */
\r
3214 if (ics_getting_history == H_FALSE) {
\r
3215 ics_getting_history = H_REQUESTED;
\r
3216 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3220 new_piece[0] = NULLCHAR;
\r
3221 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3222 &gamenum, white_holding, black_holding,
\r
3224 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3225 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3226 /* [HGM] copy holdings to board holdings area */
\r
3227 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3228 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3230 if (appData.zippyPlay && first.initDone) {
\r
3231 ZippyHoldings(white_holding, black_holding,
\r
3235 if (tinyLayout || smallLayout) {
\r
3236 char wh[16], bh[16];
\r
3237 PackHolding(wh, white_holding);
\r
3238 PackHolding(bh, black_holding);
\r
3239 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3240 gameInfo.white, gameInfo.black);
\r
3242 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3243 gameInfo.white, white_holding,
\r
3244 gameInfo.black, black_holding);
\r
3247 DrawPosition(FALSE, boards[currentMove]);
\r
3248 DisplayTitle(str);
\r
3250 /* Suppress following prompt */
\r
3251 if (looking_at(buf, &i, "*% ")) {
\r
3252 savingComment = FALSE;
\r
3259 i++; /* skip unparsed character and loop back */
\r
3262 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3263 started != STARTED_HOLDINGS && i > next_out) {
\r
3264 SendToPlayer(&buf[next_out], i - next_out);
\r
3267 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3269 leftover_len = buf_len - leftover_start;
\r
3270 /* if buffer ends with something we couldn't parse,
\r
3271 reparse it after appending the next read */
\r
3273 } else if (count == 0) {
\r
3274 RemoveInputSource(isr);
\r
3275 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3277 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3282 /* Board style 12 looks like this:
\r
3284 <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
3286 * The "<12> " is stripped before it gets to this routine. The two
\r
3287 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3288 * some chess servers may not have them, or may have only the first.
\r
3289 * Additional trailing fields may be added in the future.
\r
3292 #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
3294 #define RELATION_OBSERVING_PLAYED 0
\r
3295 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3296 #define RELATION_PLAYING_MYMOVE 1
\r
3297 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3298 #define RELATION_EXAMINING 2
\r
3299 #define RELATION_ISOLATED_BOARD -3
\r
3300 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3303 ParseBoard12(string)
\r
3306 GameMode newGameMode;
\r
3307 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3308 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3309 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3310 char to_play, board_chars[200];
\r
3311 char move_str[500], str[500], elapsed_time[500];
\r
3312 char black[32], white[32];
\r
3314 int prevMove = currentMove;
\r
3316 ChessMove moveType;
\r
3317 int fromX, fromY, toX, toY;
\r
3319 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3320 char *bookHit = NULL; // [HGM] book
\r
3322 fromX = fromY = toX = toY = -1;
\r
3326 if (appData.debugMode)
\r
3327 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3329 move_str[0] = NULLCHAR;
\r
3330 elapsed_time[0] = NULLCHAR;
\r
3331 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3333 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3334 if(string[i] == ' ') { ranks++; files = 0; }
\r
3338 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3339 board_chars[i] = '\0';
\r
3342 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3343 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3344 &gamenum, white, black, &relation, &basetime, &increment,
\r
3345 &white_stren, &black_stren, &white_time, &black_time,
\r
3346 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3350 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3351 DisplayError(str, 0);
\r
3355 /* Convert the move number to internal form */
\r
3356 moveNum = (moveNum - 1) * 2;
\r
3357 if (to_play == 'B') moveNum++;
\r
3358 if (moveNum >= MAX_MOVES) {
\r
3359 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3364 switch (relation) {
\r
3365 case RELATION_OBSERVING_PLAYED:
\r
3366 case RELATION_OBSERVING_STATIC:
\r
3367 if (gamenum == -1) {
\r
3368 /* Old ICC buglet */
\r
3369 relation = RELATION_OBSERVING_STATIC;
\r
3371 newGameMode = IcsObserving;
\r
3373 case RELATION_PLAYING_MYMOVE:
\r
3374 case RELATION_PLAYING_NOTMYMOVE:
\r
3376 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3377 IcsPlayingWhite : IcsPlayingBlack;
\r
3379 case RELATION_EXAMINING:
\r
3380 newGameMode = IcsExamining;
\r
3382 case RELATION_ISOLATED_BOARD:
\r
3384 /* Just display this board. If user was doing something else,
\r
3385 we will forget about it until the next board comes. */
\r
3386 newGameMode = IcsIdle;
\r
3388 case RELATION_STARTING_POSITION:
\r
3389 newGameMode = gameMode;
\r
3393 /* Modify behavior for initial board display on move listing
\r
3396 switch (ics_getting_history) {
\r
3400 case H_GOT_REQ_HEADER:
\r
3401 case H_GOT_UNREQ_HEADER:
\r
3402 /* This is the initial position of the current game */
\r
3403 gamenum = ics_gamenum;
\r
3404 moveNum = 0; /* old ICS bug workaround */
\r
3405 if (to_play == 'B') {
\r
3406 startedFromSetupPosition = TRUE;
\r
3407 blackPlaysFirst = TRUE;
\r
3409 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3410 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3411 if (currentMove == 0) currentMove = 1;
\r
3413 newGameMode = gameMode;
\r
3414 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3416 case H_GOT_UNWANTED_HEADER:
\r
3417 /* This is an initial board that we don't want */
\r
3419 case H_GETTING_MOVES:
\r
3420 /* Should not happen */
\r
3421 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3422 ics_getting_history = H_FALSE;
\r
3426 /* Take action if this is the first board of a new game, or of a
\r
3427 different game than is currently being displayed. */
\r
3428 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3429 relation == RELATION_ISOLATED_BOARD) {
\r
3431 /* Forget the old game and get the history (if any) of the new one */
\r
3432 if (gameMode != BeginningOfGame) {
\r
3433 Reset(FALSE, TRUE);
\r
3436 if (appData.autoRaiseBoard) BoardToTop();
\r
3438 if (gamenum == -1) {
\r
3439 newGameMode = IcsIdle;
\r
3440 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3441 appData.getMoveList) {
\r
3442 /* Need to get game history */
\r
3443 ics_getting_history = H_REQUESTED;
\r
3444 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3448 /* Initially flip the board to have black on the bottom if playing
\r
3449 black or if the ICS flip flag is set, but let the user change
\r
3450 it with the Flip View button. */
\r
3451 flipView = appData.autoFlipView ?
\r
3452 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3455 /* Done with values from previous mode; copy in new ones */
\r
3456 gameMode = newGameMode;
\r
3458 ics_gamenum = gamenum;
\r
3459 if (gamenum == gs_gamenum) {
\r
3460 int klen = strlen(gs_kind);
\r
3461 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3462 sprintf(str, "ICS %s", gs_kind);
\r
3463 gameInfo.event = StrSave(str);
\r
3465 gameInfo.event = StrSave("ICS game");
\r
3467 gameInfo.site = StrSave(appData.icsHost);
\r
3468 gameInfo.date = PGNDate();
\r
3469 gameInfo.round = StrSave("-");
\r
3470 gameInfo.white = StrSave(white);
\r
3471 gameInfo.black = StrSave(black);
\r
3472 timeControl = basetime * 60 * 1000;
\r
3473 timeControl_2 = 0;
\r
3474 timeIncrement = increment * 1000;
\r
3475 movesPerSession = 0;
\r
3476 gameInfo.timeControl = TimeControlTagValue();
\r
3477 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3478 if (appData.debugMode) {
\r
3479 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3480 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3481 setbuf(debugFP, NULL);
\r
3484 gameInfo.outOfBook = NULL;
\r
3486 /* Do we have the ratings? */
\r
3487 if (strcmp(player1Name, white) == 0 &&
\r
3488 strcmp(player2Name, black) == 0) {
\r
3489 if (appData.debugMode)
\r
3490 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3491 player1Rating, player2Rating);
\r
3492 gameInfo.whiteRating = player1Rating;
\r
3493 gameInfo.blackRating = player2Rating;
\r
3494 } else if (strcmp(player2Name, white) == 0 &&
\r
3495 strcmp(player1Name, black) == 0) {
\r
3496 if (appData.debugMode)
\r
3497 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3498 player2Rating, player1Rating);
\r
3499 gameInfo.whiteRating = player2Rating;
\r
3500 gameInfo.blackRating = player1Rating;
\r
3502 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3504 /* Silence shouts if requested */
\r
3505 if (appData.quietPlay &&
\r
3506 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3507 SendToICS(ics_prefix);
\r
3508 SendToICS("set shout 0\n");
\r
3512 /* Deal with midgame name changes */
\r
3514 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3515 if (gameInfo.white) free(gameInfo.white);
\r
3516 gameInfo.white = StrSave(white);
\r
3518 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3519 if (gameInfo.black) free(gameInfo.black);
\r
3520 gameInfo.black = StrSave(black);
\r
3524 /* Throw away game result if anything actually changes in examine mode */
\r
3525 if (gameMode == IcsExamining && !newGame) {
\r
3526 gameInfo.result = GameUnfinished;
\r
3527 if (gameInfo.resultDetails != NULL) {
\r
3528 free(gameInfo.resultDetails);
\r
3529 gameInfo.resultDetails = NULL;
\r
3533 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3534 in if they are in a different variation than we are. */
\r
3535 if (pauseExamInvalid) return;
\r
3536 if (pausing && gameMode == IcsExamining) {
\r
3537 if (moveNum <= pauseExamForwardMostMove) {
\r
3538 pauseExamInvalid = TRUE;
\r
3539 forwardMostMove = pauseExamForwardMostMove;
\r
3544 if (appData.debugMode) {
\r
3545 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3547 /* Parse the board */
\r
3548 for (k = 0; k < ranks; k++) {
\r
3549 for (j = 0; j < files; j++)
\r
3550 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3551 if(gameInfo.holdingsWidth > 1) {
\r
3552 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3553 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3556 CopyBoard(boards[moveNum], board);
\r
3557 if (moveNum == 0) {
\r
3558 startedFromSetupPosition =
\r
3559 !CompareBoards(board, initialPosition);
\r
3560 if(startedFromSetupPosition)
\r
3561 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3564 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3565 to make it also work for FRC opening positions. Note that board12
\r
3566 is really defective for later FRC positions, as it has no way to
\r
3567 indicate which Rook can castle if they are on the same side of King.
\r
3568 For the initial position we grant rights to the outermost Rooks,
\r
3569 and remember thos rights, and we then copy them on positions
\r
3570 later in an FRC game. This means WB might not recognize castlings with
\r
3571 Rooks that have moved back to their original position as illegal,
\r
3572 but in ICS mode that is not its job anyway.
\r
3574 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3575 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3577 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3578 if(board[0][i] == WhiteRook) j = i;
\r
3579 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3580 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3581 if(board[0][i] == WhiteRook) j = i;
\r
3582 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3584 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3585 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3587 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3588 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3590 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3592 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3593 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3594 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3595 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3597 r = castlingRights[moveNum][0] = initialRights[0];
\r
3598 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3599 r = castlingRights[moveNum][1] = initialRights[1];
\r
3600 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3601 r = castlingRights[moveNum][3] = initialRights[3];
\r
3602 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3603 r = castlingRights[moveNum][4] = initialRights[4];
\r
3604 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3605 /* wildcastle kludge: always assume King has rights */
\r
3606 r = castlingRights[moveNum][2] = initialRights[2];
\r
3607 r = castlingRights[moveNum][5] = initialRights[5];
\r
3609 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3610 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3613 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3614 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3615 /* This was an initial position from a move list, not
\r
3616 the current position */
\r
3620 /* Update currentMove and known move number limits */
\r
3621 newMove = newGame || moveNum > forwardMostMove;
\r
3623 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3624 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3625 takeback = forwardMostMove - moveNum;
\r
3626 for (i = 0; i < takeback; i++) {
\r
3627 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3628 SendToProgram("undo\n", &first);
\r
3633 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3634 if (gameMode == IcsExamining && moveNum == 0) {
\r
3635 /* Workaround for ICS limitation: we are not told the wild
\r
3636 type when starting to examine a game. But if we ask for
\r
3637 the move list, the move list header will tell us */
\r
3638 ics_getting_history = H_REQUESTED;
\r
3639 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3642 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3643 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3644 forwardMostMove = moveNum;
\r
3645 if (!pausing || currentMove > forwardMostMove)
\r
3646 currentMove = forwardMostMove;
\r
3648 /* New part of history that is not contiguous with old part */
\r
3649 if (pausing && gameMode == IcsExamining) {
\r
3650 pauseExamInvalid = TRUE;
\r
3651 forwardMostMove = pauseExamForwardMostMove;
\r
3654 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3655 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3656 ics_getting_history = H_REQUESTED;
\r
3657 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3662 /* Update the clocks */
\r
3663 if (strchr(elapsed_time, '.')) {
\r
3664 /* Time is in ms */
\r
3665 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3666 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3668 /* Time is in seconds */
\r
3669 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3670 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3675 if (appData.zippyPlay && newGame &&
\r
3676 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3677 gameMode != IcsExamining)
\r
3678 ZippyFirstBoard(moveNum, basetime, increment);
\r
3681 /* Put the move on the move list, first converting
\r
3682 to canonical algebraic form. */
\r
3683 if (moveNum > 0) {
\r
3684 if (appData.debugMode) {
\r
3685 if (appData.debugMode) { int f = forwardMostMove;
\r
3686 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3687 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3689 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3690 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3691 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3692 setbuf(debugFP, NULL);
\r
3694 if (moveNum <= backwardMostMove) {
\r
3695 /* We don't know what the board looked like before
\r
3696 this move. Punt. */
\r
3697 strcpy(parseList[moveNum - 1], move_str);
\r
3698 strcat(parseList[moveNum - 1], " ");
\r
3699 strcat(parseList[moveNum - 1], elapsed_time);
\r
3700 moveList[moveNum - 1][0] = NULLCHAR;
\r
3701 } else if (strcmp(move_str, "none") == 0) {
\r
3702 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3703 /* Again, we don't know what the board looked like;
\r
3704 this is really the start of the game. */
\r
3705 parseList[moveNum - 1][0] = NULLCHAR;
\r
3706 moveList[moveNum - 1][0] = NULLCHAR;
\r
3707 backwardMostMove = moveNum;
\r
3708 startedFromSetupPosition = TRUE;
\r
3709 fromX = fromY = toX = toY = -1;
\r
3711 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3712 // So we parse the long-algebraic move string in stead of the SAN move
\r
3713 int valid; char buf[MSG_SIZ], *prom;
\r
3715 // str looks something like "Q/a1-a2"; kill the slash
\r
3716 if(str[1] == '/')
\r
3717 sprintf(buf, "%c%s", str[0], str+2);
\r
3718 else strcpy(buf, str); // might be castling
\r
3719 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3720 strcat(buf, prom); // long move lacks promo specification!
\r
3721 if(!appData.testLegality) {
\r
3722 if(appData.debugMode)
\r
3723 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3724 strcpy(move_str, buf);
\r
3726 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3727 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3728 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3729 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3730 // end of long SAN patch
\r
3732 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3733 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3734 fromY, fromX, toY, toX, promoChar,
\r
3735 parseList[moveNum-1]);
\r
3736 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3737 castlingRights[moveNum]) ) {
\r
3739 case MT_STALEMATE:
\r
3743 if(gameInfo.variant != VariantShogi)
\r
3744 strcat(parseList[moveNum - 1], "+");
\r
3746 case MT_CHECKMATE:
\r
3747 strcat(parseList[moveNum - 1], "#");
\r
3750 strcat(parseList[moveNum - 1], " ");
\r
3751 strcat(parseList[moveNum - 1], elapsed_time);
\r
3752 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3753 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3754 strcat(moveList[moveNum - 1], "\n");
\r
3756 /* Move from ICS was illegal!? Punt. */
\r
3757 if (appData.debugMode) {
\r
3758 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3759 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3762 if (appData.testLegality && appData.debugMode) {
\r
3763 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3764 DisplayError(str, 0);
\r
3767 strcpy(parseList[moveNum - 1], move_str);
\r
3768 strcat(parseList[moveNum - 1], " ");
\r
3769 strcat(parseList[moveNum - 1], elapsed_time);
\r
3770 moveList[moveNum - 1][0] = NULLCHAR;
\r
3771 fromX = fromY = toX = toY = -1;
\r
3774 if (appData.debugMode) {
\r
3775 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3776 setbuf(debugFP, NULL);
\r
3780 /* Send move to chess program (BEFORE animating it). */
\r
3781 if (appData.zippyPlay && !newGame && newMove &&
\r
3782 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3784 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3785 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3786 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3787 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3789 DisplayError(str, 0);
\r
3791 if (first.sendTime) {
\r
3792 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3794 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3795 if (firstMove && !bookHit) {
\r
3796 firstMove = FALSE;
\r
3797 if (first.useColors) {
\r
3798 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3800 "black\ngo\n", &first);
\r
3802 SendToProgram("go\n", &first);
\r
3804 first.maybeThinking = TRUE;
\r
3807 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3808 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3809 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3810 DisplayError(str, 0);
\r
3812 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3813 SendMoveToProgram(moveNum - 1, &first);
\r
3820 if (moveNum > 0 && !gotPremove) {
\r
3821 /* If move comes from a remote source, animate it. If it
\r
3822 isn't remote, it will have already been animated. */
\r
3823 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3824 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3826 if (!pausing && appData.highlightLastMove) {
\r
3827 SetHighlights(fromX, fromY, toX, toY);
\r
3831 /* Start the clocks */
\r
3832 whiteFlag = blackFlag = FALSE;
\r
3833 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3834 if (ticking == 0) {
\r
3835 ics_clock_paused = TRUE;
\r
3837 } else if (ticking == 1) {
\r
3838 ics_clock_paused = FALSE;
\r
3840 if (gameMode == IcsIdle ||
\r
3841 relation == RELATION_OBSERVING_STATIC ||
\r
3842 relation == RELATION_EXAMINING ||
\r
3844 DisplayBothClocks();
\r
3848 /* Display opponents and material strengths */
\r
3849 if (gameInfo.variant != VariantBughouse &&
\r
3850 gameInfo.variant != VariantCrazyhouse) {
\r
3851 if (tinyLayout || smallLayout) {
\r
3852 if(gameInfo.variant == VariantNormal)
\r
3853 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3854 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3855 basetime, increment);
\r
3857 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3858 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3859 basetime, increment, (int) gameInfo.variant);
\r
3861 if(gameInfo.variant == VariantNormal)
\r
3862 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3863 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3864 basetime, increment);
\r
3866 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3867 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3868 basetime, increment, VariantName(gameInfo.variant));
\r
3870 DisplayTitle(str);
\r
3871 if (appData.debugMode) {
\r
3872 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3877 /* Display the board */
\r
3880 if (appData.premove)
\r
3881 if (!gotPremove ||
\r
3882 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3883 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3884 ClearPremoveHighlights();
\r
3886 DrawPosition(FALSE, boards[currentMove]);
\r
3887 DisplayMove(moveNum - 1);
\r
3888 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3892 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3894 if(bookHit) { // [HGM] book: simulate book reply
\r
3895 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3897 programStats.nodes = programStats.depth = programStats.time =
\r
3898 programStats.score = programStats.got_only_move = 0;
\r
3899 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3901 strcpy(bookMove, "move ");
\r
3902 strcat(bookMove, bookHit);
\r
3903 HandleMachineMove(bookMove, &first);
\r
3909 GetMoveListEvent()
\r
3911 char buf[MSG_SIZ];
\r
3912 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3913 ics_getting_history = H_REQUESTED;
\r
3914 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3920 AnalysisPeriodicEvent(force)
\r
3923 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3924 && !force) || !appData.periodicUpdates)
\r
3927 /* Send . command to Crafty to collect stats */
\r
3928 SendToProgram(".\n", &first);
\r
3930 /* Don't send another until we get a response (this makes
\r
3931 us stop sending to old Crafty's which don't understand
\r
3932 the "." command (sending illegal cmds resets node count & time,
\r
3933 which looks bad)) */
\r
3934 programStats.ok_to_send = 0;
\r
3938 SendMoveToProgram(moveNum, cps)
\r
3940 ChessProgramState *cps;
\r
3942 char buf[MSG_SIZ];
\r
3944 if (cps->useUsermove) {
\r
3945 SendToProgram("usermove ", cps);
\r
3947 if (cps->useSAN) {
\r
3949 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3950 int len = space - parseList[moveNum];
\r
3951 memcpy(buf, parseList[moveNum], len);
\r
3952 buf[len++] = '\n';
\r
3953 buf[len] = NULLCHAR;
\r
3955 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3957 SendToProgram(buf, cps);
\r
3959 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3960 AlphaRank(moveList[moveNum], 4);
\r
3961 SendToProgram(moveList[moveNum], cps);
\r
3962 AlphaRank(moveList[moveNum], 4); // and back
\r
3964 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3965 * the engine. It would be nice to have a better way to identify castle
\r
3967 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3968 && cps->useOOCastle) {
\r
3969 int fromX = moveList[moveNum][0] - AAA;
\r
3970 int fromY = moveList[moveNum][1] - ONE;
\r
3971 int toX = moveList[moveNum][2] - AAA;
\r
3972 int toY = moveList[moveNum][3] - ONE;
\r
3973 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3974 && boards[moveNum][toY][toX] == WhiteRook)
\r
3975 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3976 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3977 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3978 else SendToProgram("O-O-O\n", cps);
\r
3980 else SendToProgram(moveList[moveNum], cps);
\r
3982 else SendToProgram(moveList[moveNum], cps);
\r
3983 /* End of additions by Tord */
\r
3986 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3987 /* Send 'go' if we are in a mode where machine should play. */
\r
3988 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3989 (gameMode == TwoMachinesPlay ||
\r
3991 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3993 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3994 SendToProgram("go\n", cps);
\r
3995 if (appData.debugMode) {
\r
3996 fprintf(debugFP, "(extra)\n");
\r
3999 setboardSpoiledMachineBlack = 0;
\r
4003 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4004 ChessMove moveType;
\r
4005 int fromX, fromY, toX, toY;
\r
4007 char user_move[MSG_SIZ];
\r
4009 switch (moveType) {
\r
4011 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4012 (int)moveType, fromX, fromY, toX, toY);
\r
4013 DisplayError(user_move + strlen("say "), 0);
\r
4015 case WhiteKingSideCastle:
\r
4016 case BlackKingSideCastle:
\r
4017 case WhiteQueenSideCastleWild:
\r
4018 case BlackQueenSideCastleWild:
\r
4020 case WhiteHSideCastleFR:
\r
4021 case BlackHSideCastleFR:
\r
4023 sprintf(user_move, "o-o\n");
\r
4025 case WhiteQueenSideCastle:
\r
4026 case BlackQueenSideCastle:
\r
4027 case WhiteKingSideCastleWild:
\r
4028 case BlackKingSideCastleWild:
\r
4030 case WhiteASideCastleFR:
\r
4031 case BlackASideCastleFR:
\r
4033 sprintf(user_move, "o-o-o\n");
\r
4035 case WhitePromotionQueen:
\r
4036 case BlackPromotionQueen:
\r
4037 case WhitePromotionRook:
\r
4038 case BlackPromotionRook:
\r
4039 case WhitePromotionBishop:
\r
4040 case BlackPromotionBishop:
\r
4041 case WhitePromotionKnight:
\r
4042 case BlackPromotionKnight:
\r
4043 case WhitePromotionKing:
\r
4044 case BlackPromotionKing:
\r
4045 case WhitePromotionChancellor:
\r
4046 case BlackPromotionChancellor:
\r
4047 case WhitePromotionArchbishop:
\r
4048 case BlackPromotionArchbishop:
\r
4049 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4050 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4051 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4052 PieceToChar(WhiteFerz));
\r
4053 else if(gameInfo.variant == VariantGreat)
\r
4054 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4055 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4056 PieceToChar(WhiteMan));
\r
4058 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4059 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4060 PieceToChar(PromoPiece(moveType)));
\r
4064 sprintf(user_move, "%c@%c%c\n",
\r
4065 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4066 AAA + toX, ONE + toY);
\r
4069 case WhiteCapturesEnPassant:
\r
4070 case BlackCapturesEnPassant:
\r
4071 case IllegalMove: /* could be a variant we don't quite understand */
\r
4072 sprintf(user_move, "%c%c%c%c\n",
\r
4073 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4076 SendToICS(user_move);
\r
4080 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4081 int rf, ff, rt, ft;
\r
4085 if (rf == DROP_RANK) {
\r
4086 sprintf(move, "%c@%c%c\n",
\r
4087 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4089 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4090 sprintf(move, "%c%c%c%c\n",
\r
4091 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4093 sprintf(move, "%c%c%c%c%c\n",
\r
4094 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4100 ProcessICSInitScript(f)
\r
4103 char buf[MSG_SIZ];
\r
4105 while (fgets(buf, MSG_SIZ, f)) {
\r
4106 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4113 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4115 AlphaRank(char *move, int n)
\r
4117 // char *p = move, c; int x, y;
\r
4119 if (appData.debugMode) {
\r
4120 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4123 if(move[1]=='*' &&
\r
4124 move[2]>='0' && move[2]<='9' &&
\r
4125 move[3]>='a' && move[3]<='x' ) {
\r
4127 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4128 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4130 if(move[0]>='0' && move[0]<='9' &&
\r
4131 move[1]>='a' && move[1]<='x' &&
\r
4132 move[2]>='0' && move[2]<='9' &&
\r
4133 move[3]>='a' && move[3]<='x' ) {
\r
4134 /* input move, Shogi -> normal */
\r
4135 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4136 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4137 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4138 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4140 if(move[1]=='@' &&
\r
4141 move[3]>='0' && move[3]<='9' &&
\r
4142 move[2]>='a' && move[2]<='x' ) {
\r
4144 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4145 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4148 move[0]>='a' && move[0]<='x' &&
\r
4149 move[3]>='0' && move[3]<='9' &&
\r
4150 move[2]>='a' && move[2]<='x' ) {
\r
4151 /* output move, normal -> Shogi */
\r
4152 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4153 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4154 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4155 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4156 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4158 if (appData.debugMode) {
\r
4159 fprintf(debugFP, " out = '%s'\n", move);
\r
4163 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4165 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4168 ChessMove *moveType;
\r
4169 int *fromX, *fromY, *toX, *toY;
\r
4172 if (appData.debugMode) {
\r
4173 fprintf(debugFP, "move to parse: %s\n", move);
\r
4175 *moveType = yylexstr(moveNum, move);
\r
4177 switch (*moveType) {
\r
4178 case WhitePromotionChancellor:
\r
4179 case BlackPromotionChancellor:
\r
4180 case WhitePromotionArchbishop:
\r
4181 case BlackPromotionArchbishop:
\r
4182 case WhitePromotionQueen:
\r
4183 case BlackPromotionQueen:
\r
4184 case WhitePromotionRook:
\r
4185 case BlackPromotionRook:
\r
4186 case WhitePromotionBishop:
\r
4187 case BlackPromotionBishop:
\r
4188 case WhitePromotionKnight:
\r
4189 case BlackPromotionKnight:
\r
4190 case WhitePromotionKing:
\r
4191 case BlackPromotionKing:
\r
4193 case WhiteCapturesEnPassant:
\r
4194 case BlackCapturesEnPassant:
\r
4195 case WhiteKingSideCastle:
\r
4196 case WhiteQueenSideCastle:
\r
4197 case BlackKingSideCastle:
\r
4198 case BlackQueenSideCastle:
\r
4199 case WhiteKingSideCastleWild:
\r
4200 case WhiteQueenSideCastleWild:
\r
4201 case BlackKingSideCastleWild:
\r
4202 case BlackQueenSideCastleWild:
\r
4203 /* Code added by Tord: */
\r
4204 case WhiteHSideCastleFR:
\r
4205 case WhiteASideCastleFR:
\r
4206 case BlackHSideCastleFR:
\r
4207 case BlackASideCastleFR:
\r
4208 /* End of code added by Tord */
\r
4209 case IllegalMove: /* bug or odd chess variant */
\r
4210 *fromX = currentMoveString[0] - AAA;
\r
4211 *fromY = currentMoveString[1] - ONE;
\r
4212 *toX = currentMoveString[2] - AAA;
\r
4213 *toY = currentMoveString[3] - ONE;
\r
4214 *promoChar = currentMoveString[4];
\r
4215 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4216 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4217 if (appData.debugMode) {
\r
4218 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4220 *fromX = *fromY = *toX = *toY = 0;
\r
4223 if (appData.testLegality) {
\r
4224 return (*moveType != IllegalMove);
\r
4226 return !(fromX == fromY && toX == toY);
\r
4231 *fromX = *moveType == WhiteDrop ?
\r
4232 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4233 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4234 *fromY = DROP_RANK;
\r
4235 *toX = currentMoveString[2] - AAA;
\r
4236 *toY = currentMoveString[3] - ONE;
\r
4237 *promoChar = NULLCHAR;
\r
4240 case AmbiguousMove:
\r
4241 case ImpossibleMove:
\r
4242 case (ChessMove) 0: /* end of file */
\r
4251 if (appData.debugMode) {
\r
4252 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4255 *fromX = *fromY = *toX = *toY = 0;
\r
4256 *promoChar = NULLCHAR;
\r
4261 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
\r
4262 // All positions will have equal probability, but the current method will not provide a unique
\r
4263 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4268 int squaresLeft[4];
\r
4269 int piecesLeft[(int)BlackPawn];
\r
4270 int seed, nrOfShuffles;
\r
4272 void GetPositionNumber()
\r
4273 { // sets global variable seed
\r
4276 seed = appData.defaultFrcPosition;
\r
4277 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4278 for(i=0; i<50; i++) seed += random();
\r
4279 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4280 if(seed<0) seed = -seed;
\r
4284 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4285 // put the piece on the (n-1)-th empty squares of the given shade
\r
4289 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4290 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4291 board[rank][i] = (ChessSquare) pieceType;
\r
4292 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4293 squaresLeft[ANY]--;
\r
4294 piecesLeft[pieceType]--;
\r
4302 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4303 // calculate where the next piece goes, (any empty square), and put it there
\r
4307 i = seed % squaresLeft[shade];
\r
4308 nrOfShuffles *= squaresLeft[shade];
\r
4309 seed /= squaresLeft[shade];
\r
4310 put(board, pieceType, rank, i, shade);
\r
4313 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4314 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4316 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4318 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4319 i = seed % k; // pick one
\r
4320 nrOfShuffles *= k;
\r
4322 while(i >= j) i -= j--;
\r
4323 j = n - 1 - j; i += j;
\r
4324 put(board, pieceType, rank, j, ANY);
\r
4325 put(board, pieceType, rank, i, ANY);
\r
4328 void SetUpShuffle(Board board, int number)
\r
4330 int i, p, first=1;
\r
4332 GetPositionNumber(); nrOfShuffles = 1;
\r
4334 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4335 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4336 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4338 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4340 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4341 p = (int) board[0][i];
\r
4342 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4343 board[0][i] = EmptySquare;
\r
4346 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4347 // shuffles restricted to allow normal castling put KRR first
\r
4348 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4349 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4350 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4351 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4352 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4353 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4354 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4355 put(board, WhiteRook, 0, 0, ANY);
\r
4356 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4359 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4360 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4361 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4362 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4363 while(piecesLeft[p] >= 2) {
\r
4364 AddOnePiece(board, p, 0, LITE);
\r
4365 AddOnePiece(board, p, 0, DARK);
\r
4367 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4370 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4371 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4372 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4373 if(p == (int)WhiteRook) continue;
\r
4374 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4375 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4378 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4380 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4381 // Last King gets castling rights
\r
4382 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4383 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4384 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4387 while(piecesLeft[(int)WhiteKing]) {
\r
4388 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4389 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4394 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4395 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4398 // Only Rooks can be left; simply place them all
\r
4399 while(piecesLeft[(int)WhiteRook]) {
\r
4400 i = put(board, WhiteRook, 0, 0, ANY);
\r
4401 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4404 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4406 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4409 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4410 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4413 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4416 int SetCharTable( char *table, const char * map )
\r
4417 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4418 /* Basically a safe strcpy that uses the last character as King */
\r
4420 int result = FALSE; int NrPieces;
\r
4422 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4423 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4424 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4426 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4427 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4428 table[i] = map[i];
\r
4429 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4431 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4432 table[(int) BlackKing] = map[NrPieces-1];
\r
4440 void Prelude(Board board)
\r
4441 { // [HGM] superchess: random selection of exo-pieces
\r
4442 int i, j, k; ChessSquare p;
\r
4443 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4445 GetPositionNumber(); // use FRC position number
\r
4447 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4448 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4449 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4450 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4453 j = seed%4; seed /= 4;
\r
4454 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4455 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4456 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4457 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4458 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4459 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4460 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4461 j = seed%3; seed /= 3;
\r
4462 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4463 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4464 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4465 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4466 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4467 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4468 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4469 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4470 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4471 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4472 put(board, exoPieces[0], 0, 0, ANY);
\r
4473 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4477 InitPosition(redraw)
\r
4480 ChessSquare (* pieces)[BOARD_SIZE];
\r
4481 int i, j, pawnRow, overrule,
\r
4482 oldx = gameInfo.boardWidth,
\r
4483 oldy = gameInfo.boardHeight,
\r
4484 oldh = gameInfo.holdingsWidth,
\r
4485 oldv = gameInfo.variant;
\r
4487 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4488 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4490 /* [AS] Initialize pv info list [HGM] and game status */
\r
4492 for( i=0; i<MAX_MOVES; i++ ) {
\r
4493 pvInfoList[i].depth = 0;
\r
4494 epStatus[i]=EP_NONE;
\r
4495 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4498 initialRulePlies = 0; /* 50-move counter start */
\r
4500 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4501 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4505 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4506 /* the initialized data only consist of the two backranks. The switch */
\r
4507 /* selects which one we will use, which is than copied to the Board */
\r
4508 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4509 /* empty squares. This initial position is then copied to boards[0], */
\r
4510 /* possibly after shuffling, so that it remains available. */
\r
4512 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4513 gameInfo.boardWidth = 8;
\r
4514 gameInfo.boardHeight = 8;
\r
4515 gameInfo.holdingsSize = 0;
\r
4516 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4517 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4518 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4520 switch (gameInfo.variant) {
\r
4521 case VariantFischeRandom:
\r
4522 shuffleOpenings = TRUE;
\r
4524 pieces = FIDEArray;
\r
4526 case VariantShatranj:
\r
4527 pieces = ShatranjArray;
\r
4528 nrCastlingRights = 0;
\r
4529 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4531 case VariantTwoKings:
\r
4532 pieces = twoKingsArray;
\r
4534 case VariantCapaRandom:
\r
4535 shuffleOpenings = TRUE;
\r
4536 case VariantCapablanca:
\r
4537 pieces = CapablancaArray;
\r
4538 gameInfo.boardWidth = 10;
\r
4539 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4541 case VariantGothic:
\r
4542 pieces = GothicArray;
\r
4543 gameInfo.boardWidth = 10;
\r
4544 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4546 case VariantJanus:
\r
4547 pieces = JanusArray;
\r
4548 gameInfo.boardWidth = 10;
\r
4549 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4550 nrCastlingRights = 6;
\r
4551 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4552 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4553 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4554 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4555 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4556 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4558 case VariantFalcon:
\r
4559 pieces = FalconArray;
\r
4560 gameInfo.boardWidth = 10;
\r
4561 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4563 case VariantXiangqi:
\r
4564 pieces = XiangqiArray;
\r
4565 gameInfo.boardWidth = 9;
\r
4566 gameInfo.boardHeight = 10;
\r
4567 nrCastlingRights = 0;
\r
4568 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4570 case VariantShogi:
\r
4571 pieces = ShogiArray;
\r
4572 gameInfo.boardWidth = 9;
\r
4573 gameInfo.boardHeight = 9;
\r
4574 gameInfo.holdingsSize = 7;
\r
4575 nrCastlingRights = 0;
\r
4576 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4578 case VariantCourier:
\r
4579 pieces = CourierArray;
\r
4580 gameInfo.boardWidth = 12;
\r
4581 nrCastlingRights = 0;
\r
4582 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4583 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4585 case VariantKnightmate:
\r
4586 pieces = KnightmateArray;
\r
4587 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4589 case VariantFairy:
\r
4590 pieces = fairyArray;
\r
4591 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4593 case VariantGreat:
\r
4594 pieces = GreatArray;
\r
4595 gameInfo.boardWidth = 10;
\r
4596 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4597 gameInfo.holdingsSize = 8;
\r
4599 case VariantSuper:
\r
4600 pieces = FIDEArray;
\r
4601 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4602 gameInfo.holdingsSize = 8;
\r
4603 startedFromSetupPosition = TRUE;
\r
4605 case VariantCrazyhouse:
\r
4606 case VariantBughouse:
\r
4607 pieces = FIDEArray;
\r
4608 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4609 gameInfo.holdingsSize = 5;
\r
4611 case VariantWildCastle:
\r
4612 pieces = FIDEArray;
\r
4613 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4614 shuffleOpenings = 1;
\r
4616 case VariantNoCastle:
\r
4617 pieces = FIDEArray;
\r
4618 nrCastlingRights = 0;
\r
4619 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4620 /* !!?unconstrained back-rank shuffle */
\r
4621 shuffleOpenings = 1;
\r
4626 if(appData.NrFiles >= 0) {
\r
4627 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4628 gameInfo.boardWidth = appData.NrFiles;
\r
4630 if(appData.NrRanks >= 0) {
\r
4631 gameInfo.boardHeight = appData.NrRanks;
\r
4633 if(appData.holdingsSize >= 0) {
\r
4634 i = appData.holdingsSize;
\r
4635 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4636 gameInfo.holdingsSize = i;
\r
4638 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4639 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4640 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4642 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4643 if(pawnRow < 1) pawnRow = 1;
\r
4645 /* User pieceToChar list overrules defaults */
\r
4646 if(appData.pieceToCharTable != NULL)
\r
4647 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4649 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4651 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4652 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4653 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4654 initialPosition[i][j] = s;
\r
4656 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4657 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4658 initialPosition[pawnRow][j] = WhitePawn;
\r
4659 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4660 if(gameInfo.variant == VariantXiangqi) {
\r
4662 initialPosition[pawnRow][j] =
\r
4663 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4664 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4665 initialPosition[2][j] = WhiteCannon;
\r
4666 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4670 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4672 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4675 initialPosition[1][j] = WhiteBishop;
\r
4676 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4678 initialPosition[1][j] = WhiteRook;
\r
4679 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4682 if( nrCastlingRights == -1) {
\r
4683 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4684 /* This sets default castling rights from none to normal corners */
\r
4685 /* Variants with other castling rights must set them themselves above */
\r
4686 nrCastlingRights = 6;
\r
4688 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4689 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4690 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4691 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4692 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4693 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4696 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4697 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4698 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4699 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4700 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4701 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4704 if(gameInfo.variant == VariantFischeRandom) {
\r
4705 if( appData.defaultFrcPosition < 0 ) {
\r
4706 ShuffleFRC( initialPosition );
\r
4709 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4711 startedFromSetupPosition = TRUE;
\r
4714 if (appData.debugMode) {
\r
4715 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4717 if(shuffleOpenings) {
\r
4718 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4719 startedFromSetupPosition = TRUE;
\r
4722 if(startedFromPositionFile) {
\r
4723 /* [HGM] loadPos: use PositionFile for every new game */
\r
4724 CopyBoard(initialPosition, filePosition);
\r
4725 for(i=0; i<nrCastlingRights; i++)
\r
4726 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4727 startedFromSetupPosition = TRUE;
\r
4730 CopyBoard(boards[0], initialPosition);
\r
4732 if(oldx != gameInfo.boardWidth ||
\r
4733 oldy != gameInfo.boardHeight ||
\r
4734 oldh != gameInfo.holdingsWidth
\r
4736 || oldv == VariantGothic || // For licensing popups
\r
4737 gameInfo.variant == VariantGothic
\r
4740 || oldv == VariantFalcon ||
\r
4741 gameInfo.variant == VariantFalcon
\r
4744 InitDrawingSizes(-2 ,0);
\r
4747 DrawPosition(TRUE, boards[currentMove]);
\r
4751 SendBoard(cps, moveNum)
\r
4752 ChessProgramState *cps;
\r
4755 char message[MSG_SIZ];
\r
4757 if (cps->useSetboard) {
\r
4758 char* fen = PositionToFEN(moveNum, cps->fenOverride);
\r
4759 sprintf(message, "setboard %s\n", fen);
\r
4760 SendToProgram(message, cps);
\r
4766 /* Kludge to set black to move, avoiding the troublesome and now
\r
4767 * deprecated "black" command.
\r
4769 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4771 SendToProgram("edit\n", cps);
\r
4772 SendToProgram("#\n", cps);
\r
4773 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4774 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4775 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4776 if ((int) *bp < (int) BlackPawn) {
\r
4777 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4778 AAA + j, ONE + i);
\r
4779 if(message[0] == '+' || message[0] == '~') {
\r
4780 sprintf(message, "%c%c%c+\n",
\r
4781 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4782 AAA + j, ONE + i);
\r
4784 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4785 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4786 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4788 SendToProgram(message, cps);
\r
4793 SendToProgram("c\n", cps);
\r
4794 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4795 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4796 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4797 if (((int) *bp != (int) EmptySquare)
\r
4798 && ((int) *bp >= (int) BlackPawn)) {
\r
4799 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4800 AAA + j, ONE + i);
\r
4801 if(message[0] == '+' || message[0] == '~') {
\r
4802 sprintf(message, "%c%c%c+\n",
\r
4803 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4804 AAA + j, ONE + i);
\r
4806 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4807 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4808 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4810 SendToProgram(message, cps);
\r
4815 SendToProgram(".\n", cps);
\r
4817 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4821 IsPromotion(fromX, fromY, toX, toY)
\r
4822 int fromX, fromY, toX, toY;
\r
4824 /* [HGM] add Shogi promotions */
\r
4825 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4826 ChessSquare piece;
\r
4828 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4829 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4830 /* [HGM] Note to self: line above also weeds out drops */
\r
4831 piece = boards[currentMove][fromY][fromX];
\r
4832 if(gameInfo.variant == VariantShogi) {
\r
4833 promotionZoneSize = 3;
\r
4834 highestPromotingPiece = (int)WhiteKing;
\r
4835 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4836 and if in normal chess we then allow promotion to King, why not
\r
4837 allow promotion of other piece in Shogi? */
\r
4839 if((int)piece >= BlackPawn) {
\r
4840 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4842 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4844 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4845 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4847 return ( (int)piece <= highestPromotingPiece );
\r
4851 InPalace(row, column)
\r
4853 { /* [HGM] for Xiangqi */
\r
4854 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4855 column < (BOARD_WIDTH + 4)/2 &&
\r
4856 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4861 PieceForSquare (x, y)
\r
4865 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4868 return boards[currentMove][y][x];
\r
4872 OKToStartUserMove(x, y)
\r
4875 ChessSquare from_piece;
\r
4878 if (matchMode) return FALSE;
\r
4879 if (gameMode == EditPosition) return TRUE;
\r
4881 if (x >= 0 && y >= 0)
\r
4882 from_piece = boards[currentMove][y][x];
\r
4884 from_piece = EmptySquare;
\r
4886 if (from_piece == EmptySquare) return FALSE;
\r
4888 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4889 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4891 switch (gameMode) {
\r
4892 case PlayFromGameFile:
\r
4894 case TwoMachinesPlay:
\r
4898 case IcsObserving:
\r
4902 case MachinePlaysWhite:
\r
4903 case IcsPlayingBlack:
\r
4904 if (appData.zippyPlay) return FALSE;
\r
4905 if (white_piece) {
\r
4906 DisplayMoveError(_("You are playing Black"));
\r
4911 case MachinePlaysBlack:
\r
4912 case IcsPlayingWhite:
\r
4913 if (appData.zippyPlay) return FALSE;
\r
4914 if (!white_piece) {
\r
4915 DisplayMoveError(_("You are playing White"));
\r
4921 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4922 DisplayMoveError(_("It is White's turn"));
\r
4925 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4926 DisplayMoveError(_("It is Black's turn"));
\r
4929 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4930 /* Editing correspondence game history */
\r
4931 /* Could disallow this or prompt for confirmation */
\r
4932 cmailOldMove = -1;
\r
4934 if (currentMove < forwardMostMove) {
\r
4935 /* Discarding moves */
\r
4936 /* Could prompt for confirmation here,
\r
4937 but I don't think that's such a good idea */
\r
4938 forwardMostMove = currentMove;
\r
4942 case BeginningOfGame:
\r
4943 if (appData.icsActive) return FALSE;
\r
4944 if (!appData.noChessProgram) {
\r
4945 if (!white_piece) {
\r
4946 DisplayMoveError(_("You are playing White"));
\r
4953 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4954 DisplayMoveError(_("It is White's turn"));
\r
4957 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4958 DisplayMoveError(_("It is Black's turn"));
\r
4964 case IcsExamining:
\r
4967 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4968 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4969 DisplayMoveError(_("Displayed position is not current"));
\r
4975 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4976 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4977 int lastLoadGameUseList = FALSE;
\r
4978 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4979 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4983 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4984 int fromX, fromY, toX, toY;
\r
4987 ChessMove moveType;
\r
4988 ChessSquare pdown, pup;
\r
4990 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4991 if ((fromX == toX) && (fromY == toY)) {
\r
4992 return ImpossibleMove;
\r
4995 /* [HGM] suppress all moves into holdings area and guard band */
\r
4996 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4997 return ImpossibleMove;
\r
4999 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5000 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5001 pdown = boards[currentMove][fromY][fromX];
\r
5002 pup = boards[currentMove][toY][toX];
\r
5003 if ( gameMode != EditPosition &&
\r
5004 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5005 WhitePawn <= pup && pup < BlackPawn ||
\r
5006 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5007 BlackPawn <= pup && pup < EmptySquare
\r
5008 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5009 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5010 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5012 return ImpossibleMove;
\r
5014 /* Check if the user is playing in turn. This is complicated because we
\r
5015 let the user "pick up" a piece before it is his turn. So the piece he
\r
5016 tried to pick up may have been captured by the time he puts it down!
\r
5017 Therefore we use the color the user is supposed to be playing in this
\r
5018 test, not the color of the piece that is currently on the starting
\r
5019 square---except in EditGame mode, where the user is playing both
\r
5020 sides; fortunately there the capture race can't happen. (It can
\r
5021 now happen in IcsExamining mode, but that's just too bad. The user
\r
5022 will get a somewhat confusing message in that case.)
\r
5025 switch (gameMode) {
\r
5026 case PlayFromGameFile:
\r
5028 case TwoMachinesPlay:
\r
5030 case IcsObserving:
\r
5032 /* We switched into a game mode where moves are not accepted,
\r
5033 perhaps while the mouse button was down. */
\r
5034 return ImpossibleMove;
\r
5036 case MachinePlaysWhite:
\r
5037 /* User is moving for Black */
\r
5038 if (WhiteOnMove(currentMove)) {
\r
5039 DisplayMoveError(_("It is White's turn"));
\r
5040 return ImpossibleMove;
\r
5044 case MachinePlaysBlack:
\r
5045 /* User is moving for White */
\r
5046 if (!WhiteOnMove(currentMove)) {
\r
5047 DisplayMoveError(_("It is Black's turn"));
\r
5048 return ImpossibleMove;
\r
5053 case IcsExamining:
\r
5054 case BeginningOfGame:
\r
5057 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5058 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5059 /* User is moving for Black */
\r
5060 if (WhiteOnMove(currentMove)) {
\r
5061 DisplayMoveError(_("It is White's turn"));
\r
5062 return ImpossibleMove;
\r
5065 /* User is moving for White */
\r
5066 if (!WhiteOnMove(currentMove)) {
\r
5067 DisplayMoveError(_("It is Black's turn"));
\r
5068 return ImpossibleMove;
\r
5073 case IcsPlayingBlack:
\r
5074 /* User is moving for Black */
\r
5075 if (WhiteOnMove(currentMove)) {
\r
5076 if (!appData.premove) {
\r
5077 DisplayMoveError(_("It is White's turn"));
\r
5078 } else if (toX >= 0 && toY >= 0) {
\r
5081 premoveFromX = fromX;
\r
5082 premoveFromY = fromY;
\r
5083 premovePromoChar = promoChar;
\r
5085 if (appData.debugMode)
\r
5086 fprintf(debugFP, "Got premove: fromX %d,"
\r
5087 "fromY %d, toX %d, toY %d\n",
\r
5088 fromX, fromY, toX, toY);
\r
5090 return ImpossibleMove;
\r
5094 case IcsPlayingWhite:
\r
5095 /* User is moving for White */
\r
5096 if (!WhiteOnMove(currentMove)) {
\r
5097 if (!appData.premove) {
\r
5098 DisplayMoveError(_("It is Black's turn"));
\r
5099 } else if (toX >= 0 && toY >= 0) {
\r
5102 premoveFromX = fromX;
\r
5103 premoveFromY = fromY;
\r
5104 premovePromoChar = promoChar;
\r
5106 if (appData.debugMode)
\r
5107 fprintf(debugFP, "Got premove: fromX %d,"
\r
5108 "fromY %d, toX %d, toY %d\n",
\r
5109 fromX, fromY, toX, toY);
\r
5111 return ImpossibleMove;
\r
5118 case EditPosition:
\r
5119 /* EditPosition, empty square, or different color piece;
\r
5120 click-click move is possible */
\r
5121 if (toX == -2 || toY == -2) {
\r
5122 boards[0][fromY][fromX] = EmptySquare;
\r
5123 return AmbiguousMove;
\r
5124 } else if (toX >= 0 && toY >= 0) {
\r
5125 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5126 boards[0][fromY][fromX] = EmptySquare;
\r
5127 return AmbiguousMove;
\r
5129 return ImpossibleMove;
\r
5132 /* [HGM] If move started in holdings, it means a drop */
\r
5133 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5134 if( pup != EmptySquare ) return ImpossibleMove;
\r
5135 if(appData.testLegality) {
\r
5136 /* it would be more logical if LegalityTest() also figured out
\r
5137 * which drops are legal. For now we forbid pawns on back rank.
\r
5138 * Shogi is on its own here...
\r
5140 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5141 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5142 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5144 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5147 userOfferedDraw = FALSE;
\r
5149 /* [HGM] always test for legality, to get promotion info */
\r
5150 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5151 epStatus[currentMove], castlingRights[currentMove],
\r
5152 fromY, fromX, toY, toX, promoChar);
\r
5154 /* [HGM] but possibly ignore an IllegalMove result */
\r
5155 if (appData.testLegality) {
\r
5156 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5157 DisplayMoveError(_("Illegal move"));
\r
5158 return ImpossibleMove;
\r
5161 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5163 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5164 function is made into one that returns an OK move type if FinishMove
\r
5165 should be called. This to give the calling driver routine the
\r
5166 opportunity to finish the userMove input with a promotion popup,
\r
5167 without bothering the user with this for invalid or illegal moves */
\r
5169 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5172 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5174 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5175 ChessMove moveType;
\r
5176 int fromX, fromY, toX, toY;
\r
5177 /*char*/int promoChar;
\r
5179 char *bookHit = 0;
\r
5180 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5181 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5182 // [HGM] superchess: suppress promotions to non-available piece
\r
5183 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5184 if(WhiteOnMove(currentMove)) {
\r
5185 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5187 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5191 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5192 move type in caller when we know the move is a legal promotion */
\r
5193 if(moveType == NormalMove && promoChar)
\r
5194 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5195 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5196 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5197 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5198 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5199 fromX = boards[currentMove][fromY][fromX];
\r
5200 fromY = DROP_RANK;
\r
5203 /* [HGM] <popupFix> The following if has been moved here from
\r
5204 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5205 piece drops in training games?), and because it can only be
\r
5206 performed after it is known to what we promote. */
\r
5207 if (gameMode == Training) {
\r
5208 /* compare the move played on the board to the next move in the
\r
5209 * game. If they match, display the move and the opponent's response.
\r
5210 * If they don't match, display an error message.
\r
5214 CopyBoard(testBoard, boards[currentMove]);
\r
5215 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5217 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5218 ForwardInner(currentMove+1);
\r
5220 /* Autoplay the opponent's response.
\r
5221 * if appData.animate was TRUE when Training mode was entered,
\r
5222 * the response will be animated.
\r
5224 saveAnimate = appData.animate;
\r
5225 appData.animate = animateTraining;
\r
5226 ForwardInner(currentMove+1);
\r
5227 appData.animate = saveAnimate;
\r
5229 /* check for the end of the game */
\r
5230 if (currentMove >= forwardMostMove) {
\r
5231 gameMode = PlayFromGameFile;
\r
5233 SetTrainingModeOff();
\r
5234 DisplayInformation(_("End of game"));
\r
5237 DisplayError(_("Incorrect move"), 0);
\r
5242 /* Ok, now we know that the move is good, so we can kill
\r
5243 the previous line in Analysis Mode */
\r
5244 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5245 forwardMostMove = currentMove;
\r
5248 /* If we need the chess program but it's dead, restart it */
\r
5249 ResurrectChessProgram();
\r
5251 /* A user move restarts a paused game*/
\r
5255 thinkOutput[0] = NULLCHAR;
\r
5257 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5259 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5260 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5261 // [HGM] superchess: take promotion piece out of holdings
\r
5262 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5263 if(WhiteOnMove(forwardMostMove-1)) {
\r
5264 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5265 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5267 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5268 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5272 if (gameMode == BeginningOfGame) {
\r
5273 if (appData.noChessProgram) {
\r
5274 gameMode = EditGame;
\r
5277 char buf[MSG_SIZ];
\r
5278 gameMode = MachinePlaysBlack;
\r
5281 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5282 DisplayTitle(buf);
\r
5283 if (first.sendName) {
\r
5284 sprintf(buf, "name %s\n", gameInfo.white);
\r
5285 SendToProgram(buf, &first);
\r
5291 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5292 /* Relay move to ICS or chess engine */
\r
5293 if (appData.icsActive) {
\r
5294 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5295 gameMode == IcsExamining) {
\r
5296 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5297 ics_user_moved = 1;
\r
5300 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5301 gameMode == MachinePlaysWhite ||
\r
5302 gameMode == MachinePlaysBlack)) {
\r
5303 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5305 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5306 // [HGM] book: if program might be playing, let it use book
\r
5307 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5308 first.maybeThinking = TRUE;
\r
5309 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5310 if (currentMove == cmailOldMove + 1) {
\r
5311 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5315 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5317 switch (gameMode) {
\r
5319 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5320 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5324 case MT_CHECKMATE:
\r
5325 if (WhiteOnMove(currentMove)) {
\r
5326 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5328 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5331 case MT_STALEMATE:
\r
5332 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5337 case MachinePlaysBlack:
\r
5338 case MachinePlaysWhite:
\r
5339 /* disable certain menu options while machine is thinking */
\r
5340 SetMachineThinkingEnables();
\r
5347 if(bookHit) { // [HGM] book: simulate book reply
\r
5348 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5350 programStats.nodes = programStats.depth = programStats.time =
\r
5351 programStats.score = programStats.got_only_move = 0;
\r
5352 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5354 strcpy(bookMove, "move ");
\r
5355 strcat(bookMove, bookHit);
\r
5356 HandleMachineMove(bookMove, &first);
\r
5362 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5363 int fromX, fromY, toX, toY;
\r
5366 /* [HGM] This routine was added to allow calling of its two logical
\r
5367 parts from other modules in the old way. Before, UserMoveEvent()
\r
5368 automatically called FinishMove() if the move was OK, and returned
\r
5369 otherwise. I separated the two, in order to make it possible to
\r
5370 slip a promotion popup in between. But that it always needs two
\r
5371 calls, to the first part, (now called UserMoveTest() ), and to
\r
5372 FinishMove if the first part succeeded. Calls that do not need
\r
5373 to do anything in between, can call this routine the old way.
\r
5375 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5376 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5377 if(moveType != ImpossibleMove)
\r
5378 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5381 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5383 // char * hint = lastHint;
\r
5384 FrontEndProgramStats stats;
\r
5386 stats.which = cps == &first ? 0 : 1;
\r
5387 stats.depth = cpstats->depth;
\r
5388 stats.nodes = cpstats->nodes;
\r
5389 stats.score = cpstats->score;
\r
5390 stats.time = cpstats->time;
\r
5391 stats.pv = cpstats->movelist;
\r
5392 stats.hint = lastHint;
\r
5393 stats.an_move_index = 0;
\r
5394 stats.an_move_count = 0;
\r
5396 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5397 stats.hint = cpstats->move_name;
\r
5398 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5399 stats.an_move_count = cpstats->nr_moves;
\r
5402 SetProgramStats( &stats );
\r
5405 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5406 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5407 char *bookHit = NULL;
\r
5409 //first determine if the incoming move brings opponent into his book
\r
5410 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5411 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5412 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5413 if(bookHit != NULL && !cps->bookSuspend) {
\r
5414 // make sure opponent is not going to reply after receiving move to book position
\r
5415 SendToProgram("force\n", cps);
\r
5416 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5418 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5419 // now arrange restart after book miss
\r
5421 // after a book hit we never send 'go', and the code after the call to this routine
\r
5422 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5423 char buf[MSG_SIZ];
\r
5424 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5425 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5426 SendToProgram(buf, cps);
\r
5427 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5428 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5429 SendToProgram("go\n", cps);
\r
5430 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5431 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5432 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5433 SendToProgram("go\n", cps);
\r
5434 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5436 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5439 char *savedMessage;
\r
5440 ChessProgramState *savedState;
\r
5441 void DeferredBookMove(void)
\r
5443 if(savedState->lastPing != savedState->lastPong)
\r
5444 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5446 HandleMachineMove(savedMessage, savedState);
\r
5450 HandleMachineMove(message, cps)
\r
5452 ChessProgramState *cps;
\r
5454 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5455 char realname[MSG_SIZ];
\r
5456 int fromX, fromY, toX, toY;
\r
5457 ChessMove moveType;
\r
5463 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5465 * Kludge to ignore BEL characters
\r
5467 while (*message == '\007') message++;
\r
5470 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5472 if(cps->debug && *message == '#') return;
\r
5475 * Look for book output
\r
5477 if (cps == &first && bookRequested) {
\r
5478 if (message[0] == '\t' || message[0] == ' ') {
\r
5479 /* Part of the book output is here; append it */
\r
5480 strcat(bookOutput, message);
\r
5481 strcat(bookOutput, " \n");
\r
5483 } else if (bookOutput[0] != NULLCHAR) {
\r
5484 /* All of book output has arrived; display it */
\r
5485 char *p = bookOutput;
\r
5486 while (*p != NULLCHAR) {
\r
5487 if (*p == '\t') *p = ' ';
\r
5490 DisplayInformation(bookOutput);
\r
5491 bookRequested = FALSE;
\r
5492 /* Fall through to parse the current output */
\r
5497 * Look for machine move.
\r
5499 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5500 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5502 /* This method is only useful on engines that support ping */
\r
5503 if (cps->lastPing != cps->lastPong) {
\r
5504 if (gameMode == BeginningOfGame) {
\r
5505 /* Extra move from before last new; ignore */
\r
5506 if (appData.debugMode) {
\r
5507 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5510 if (appData.debugMode) {
\r
5511 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5512 cps->which, gameMode);
\r
5515 SendToProgram("undo\n", cps);
\r
5520 switch (gameMode) {
\r
5521 case BeginningOfGame:
\r
5522 /* Extra move from before last reset; ignore */
\r
5523 if (appData.debugMode) {
\r
5524 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5531 /* Extra move after we tried to stop. The mode test is
\r
5532 not a reliable way of detecting this problem, but it's
\r
5533 the best we can do on engines that don't support ping.
\r
5535 if (appData.debugMode) {
\r
5536 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5537 cps->which, gameMode);
\r
5539 SendToProgram("undo\n", cps);
\r
5542 case MachinePlaysWhite:
\r
5543 case IcsPlayingWhite:
\r
5544 machineWhite = TRUE;
\r
5547 case MachinePlaysBlack:
\r
5548 case IcsPlayingBlack:
\r
5549 machineWhite = FALSE;
\r
5552 case TwoMachinesPlay:
\r
5553 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5556 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5557 if (appData.debugMode) {
\r
5559 "Ignoring move out of turn by %s, gameMode %d"
\r
5560 ", forwardMost %d\n",
\r
5561 cps->which, gameMode, forwardMostMove);
\r
5566 if (appData.debugMode) { int f = forwardMostMove;
\r
5567 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5568 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5570 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5571 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5572 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5573 /* Machine move could not be parsed; ignore it. */
\r
5574 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5575 machineMove, cps->which);
\r
5576 DisplayError(buf1, 0);
\r
5577 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5578 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5579 if (gameMode == TwoMachinesPlay) {
\r
5580 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5586 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5587 /* So we have to redo legality test with true e.p. status here, */
\r
5588 /* to make sure an illegal e.p. capture does not slip through, */
\r
5589 /* to cause a forfeit on a justified illegal-move complaint */
\r
5590 /* of the opponent. */
\r
5591 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5592 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5594 ChessMove moveType;
\r
5595 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5596 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5597 fromY, fromX, toY, toX, promoChar);
\r
5598 if (appData.debugMode) {
\r
5600 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5601 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5602 fprintf(debugFP, "castling rights\n");
\r
5604 if(moveType == IllegalMove) {
\r
5605 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5606 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5607 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5609 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5610 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5611 when they shouldn't (like TSCP-Gothic) */
\r
5612 switch(moveType) {
\r
5613 case WhiteASideCastleFR:
\r
5614 case BlackASideCastleFR:
\r
5616 currentMoveString[2]++;
\r
5618 case WhiteHSideCastleFR:
\r
5619 case BlackHSideCastleFR:
\r
5621 currentMoveString[2]--;
\r
5623 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5626 hintRequested = FALSE;
\r
5627 lastHint[0] = NULLCHAR;
\r
5628 bookRequested = FALSE;
\r
5629 /* Program may be pondering now */
\r
5630 cps->maybeThinking = TRUE;
\r
5631 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5632 if (cps->offeredDraw) cps->offeredDraw--;
\r
5635 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5637 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5638 ics_user_moved = 1;
\r
5639 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5640 char buf[3*MSG_SIZ];
\r
5642 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5643 programStats.depth,
\r
5644 programStats.score / 100.,
\r
5645 programStats.time / 100.,
\r
5646 u64ToDouble(programStats.nodes),
\r
5647 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5648 programStats.movelist);
\r
5653 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5654 strcpy(machineMove, currentMoveString);
\r
5655 strcat(machineMove, "\n");
\r
5656 strcpy(moveList[forwardMostMove], machineMove);
\r
5658 /* [AS] Save move info and clear stats for next move */
\r
5659 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5660 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5661 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5662 ClearProgramStats();
\r
5663 thinkOutput[0] = NULLCHAR;
\r
5664 hiddenThinkOutputState = 0;
\r
5666 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5668 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5669 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5672 while( count < adjudicateLossPlies ) {
\r
5673 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5676 score = -score; /* Flip score for winning side */
\r
5679 if( score > adjudicateLossThreshold ) {
\r
5686 if( count >= adjudicateLossPlies ) {
\r
5687 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5689 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5690 "Xboard adjudication",
\r
5697 if( gameMode == TwoMachinesPlay ) {
\r
5698 // [HGM] some adjudications useful with buggy engines
\r
5699 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5700 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5702 if(appData.testLegality)
\r
5703 // don't wait for engine to announce game end if we can judge ourselves
\r
5704 switch (MateTest(boards[forwardMostMove],
\r
5705 PosFlags(forwardMostMove), epFile,
\r
5706 castlingRights[forwardMostMove]) ) {
\r
5711 case MT_STALEMATE:
\r
5712 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5713 if(appData.checkMates) {
\r
5714 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5715 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5716 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide
\r
5717 || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5718 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!
\r
5719 "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5721 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5725 case MT_CHECKMATE:
\r
5726 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5727 if(appData.checkMates) {
\r
5728 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5729 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5730 GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:
\r
5731 ? BlackWins : WhiteWins, // reverse the result ( A!=1 is !A for a boolean)
\r
5732 "Xboard adjudication: Checkmate", GE_XBOARD );
\r
5738 if( appData.testLegality )
\r
5739 { /* [HGM] Some more adjudications for obstinate engines */
\r
5740 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5741 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5742 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5743 static int moveCount = 6;
\r
5745 /* First absolutely insufficient mating material. Count what is on board. */
\r
5746 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5747 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5751 { /* count B,N,R and other of each side */
\r
5754 NrK++; break; // [HGM] atomic: count Kings
\r
5758 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5759 bishopsColor |= 1 << ((i^j)&1);
\r
5764 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5765 bishopsColor |= 1 << ((i^j)&1);
\r
5775 case EmptySquare:
\r
5780 PawnAdvance += m; NrPawns++;
\r
5782 NrPieces += (p != EmptySquare);
\r
5783 NrW += ((int)p < (int)BlackPawn);
\r
5784 if(gameInfo.variant == VariantXiangqi &&
\r
5785 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5786 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5787 NrW -= ((int)p < (int)BlackPawn);
\r
5791 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5792 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5793 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5794 if(appData.checkMates) {
\r
5795 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5796 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5797 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5798 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5803 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5804 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5805 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5806 epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win
\r
5807 if(appData.checkMates) {
\r
5808 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5809 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5810 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5811 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5815 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5817 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm
\r
5818 if(appData.checkMates) {
\r
5819 /* but only adjudicate if adjudication enabled */
\r
5820 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5821 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5822 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5823 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5830 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5831 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5832 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5833 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5834 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5836 /* always flag draws, for judging claims */
\r
5837 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5839 if(appData.materialDraws) {
\r
5840 /* but only adjudicate them if adjudication enabled */
\r
5841 SendToProgram("force\n", cps->other); // suppress reply
\r
5842 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5843 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5844 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5849 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5850 if(NrPieces == 4 &&
\r
5851 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5852 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5853 || NrWN==2 || NrBN==2 /* KNNK */
\r
5854 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5856 if(--moveCount < 0 && appData.trivialDraws)
\r
5857 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5858 SendToProgram("force\n", cps->other); // suppress reply
\r
5859 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5860 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5861 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5864 } else moveCount = 6;
\r
5868 if (appData.debugMode) { int i;
\r
5869 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5870 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5871 appData.drawRepeats);
\r
5872 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5873 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5877 /* Check for rep-draws */
\r
5879 for(k = forwardMostMove-2;
\r
5880 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5881 epStatus[k] < EP_UNKNOWN &&
\r
5882 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5886 if (appData.debugMode) {
\r
5887 fprintf(debugFP, " loop\n");
\r
5890 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5892 if (appData.debugMode) {
\r
5893 fprintf(debugFP, "match\n");
\r
5896 /* compare castling rights */
\r
5897 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5898 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5899 rights++; /* King lost rights, while rook still had them */
\r
5900 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5901 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5902 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5903 rights++; /* but at least one rook lost them */
\r
5905 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5906 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5908 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5909 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5910 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5914 if (appData.debugMode) {
\r
5915 for(i=0; i<nrCastlingRights; i++)
\r
5916 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5919 if (appData.debugMode) {
\r
5920 fprintf(debugFP, " %d %d\n", rights, k);
\r
5923 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5924 && appData.drawRepeats > 1) {
\r
5925 /* adjudicate after user-specified nr of repeats */
\r
5926 SendToProgram("force\n", cps->other); // suppress reply
\r
5927 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5928 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5929 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5930 // [HGM] xiangqi: check for forbidden perpetuals
\r
5931 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5932 for(m=forwardMostMove; m>k; m-=2) {
\r
5933 if(MateTest(boards[m], PosFlags(m),
\r
5934 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5935 ourPerpetual = 0; // the current mover did not always check
\r
5936 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5937 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5938 hisPerpetual = 0; // the opponent did not always check
\r
5940 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5941 ourPerpetual, hisPerpetual);
\r
5942 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5943 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5944 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5947 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5948 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5949 // Now check for perpetual chases
\r
5950 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5951 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5952 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5953 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5954 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5955 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5958 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5959 break; // Abort repetition-checking loop.
\r
5961 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5963 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5966 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5967 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5971 /* Now we test for 50-move draws. Determine ply count */
\r
5972 count = forwardMostMove;
\r
5973 /* look for last irreversble move */
\r
5974 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5976 /* if we hit starting position, add initial plies */
\r
5977 if( count == backwardMostMove )
\r
5978 count -= initialRulePlies;
\r
5979 count = forwardMostMove - count;
\r
5981 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5982 /* this is used to judge if draw claims are legal */
\r
5983 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5984 SendToProgram("force\n", cps->other); // suppress reply
\r
5985 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5986 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5987 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5991 /* if draw offer is pending, treat it as a draw claim
\r
5992 * when draw condition present, to allow engines a way to
\r
5993 * claim draws before making their move to avoid a race
\r
5994 * condition occurring after their move
\r
5996 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
5998 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
5999 p = "Draw claim: 50-move rule";
\r
6000 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6001 p = "Draw claim: 3-fold repetition";
\r
6002 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6003 p = "Draw claim: insufficient mating material";
\r
6005 SendToProgram("force\n", cps->other); // suppress reply
\r
6006 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6007 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6008 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6014 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\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 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6019 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6026 if (gameMode == TwoMachinesPlay) {
\r
6027 /* [HGM] relaying draw offers moved to after reception of move */
\r
6028 /* and interpreting offer as claim if it brings draw condition */
\r
6029 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6030 SendToProgram("draw\n", cps->other);
\r
6032 if (cps->other->sendTime) {
\r
6033 SendTimeRemaining(cps->other,
\r
6034 cps->other->twoMachinesColor[0] == 'w');
\r
6036 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6037 if (firstMove && !bookHit) {
\r
6038 firstMove = FALSE;
\r
6039 if (cps->other->useColors) {
\r
6040 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6042 SendToProgram("go\n", cps->other);
\r
6044 cps->other->maybeThinking = TRUE;
\r
6047 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6049 if (!pausing && appData.ringBellAfterMoves) {
\r
6054 * Reenable menu items that were disabled while
\r
6055 * machine was thinking
\r
6057 if (gameMode != TwoMachinesPlay)
\r
6058 SetUserThinkingEnables();
\r
6060 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6061 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6062 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6064 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6066 strcpy(bookMove, "move ");
\r
6067 strcat(bookMove, bookHit);
\r
6068 message = bookMove;
\r
6070 programStats.nodes = programStats.depth = programStats.time =
\r
6071 programStats.score = programStats.got_only_move = 0;
\r
6072 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6074 if(cps->lastPing != cps->lastPong) {
\r
6075 savedMessage = message; // args for deferred call
\r
6077 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6080 goto FakeBookMove;
\r
6086 /* Set special modes for chess engines. Later something general
\r
6087 * could be added here; for now there is just one kludge feature,
\r
6088 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6089 * when "xboard" is given as an interactive command.
\r
6091 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6092 cps->useSigint = FALSE;
\r
6093 cps->useSigterm = FALSE;
\r
6096 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6097 * want this, I was asked to put it in, and obliged.
\r
6099 if (!strncmp(message, "setboard ", 9)) {
\r
6100 Board initial_position; int i;
\r
6102 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6104 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6105 DisplayError(_("Bad FEN received from engine"), 0);
\r
6108 Reset(FALSE, FALSE);
\r
6109 CopyBoard(boards[0], initial_position);
\r
6110 initialRulePlies = FENrulePlies;
\r
6111 epStatus[0] = FENepStatus;
\r
6112 for( i=0; i<nrCastlingRights; i++ )
\r
6113 castlingRights[0][i] = FENcastlingRights[i];
\r
6114 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6115 else gameMode = MachinePlaysBlack;
\r
6116 DrawPosition(FALSE, boards[currentMove]);
\r
6122 * Look for communication commands
\r
6124 if (!strncmp(message, "telluser ", 9)) {
\r
6125 DisplayNote(message + 9);
\r
6128 if (!strncmp(message, "tellusererror ", 14)) {
\r
6129 DisplayError(message + 14, 0);
\r
6132 if (!strncmp(message, "tellopponent ", 13)) {
\r
6133 if (appData.icsActive) {
\r
6135 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6139 DisplayNote(message + 13);
\r
6143 if (!strncmp(message, "tellothers ", 11)) {
\r
6144 if (appData.icsActive) {
\r
6146 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6152 if (!strncmp(message, "tellall ", 8)) {
\r
6153 if (appData.icsActive) {
\r
6155 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6159 DisplayNote(message + 8);
\r
6163 if (strncmp(message, "warning", 7) == 0) {
\r
6164 /* Undocumented feature, use tellusererror in new code */
\r
6165 DisplayError(message, 0);
\r
6168 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6169 strcpy(realname, cps->tidy);
\r
6170 strcat(realname, " query");
\r
6171 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6174 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6175 * sent until we are logged on. Crafty kibitzes have been known to
\r
6176 * interfere with the login process.
\r
6179 if (!strncmp(message, "tellics ", 8)) {
\r
6180 SendToICS(message + 8);
\r
6184 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6185 SendToICS(ics_prefix);
\r
6186 SendToICS(message + 15);
\r
6190 /* The following are for backward compatibility only */
\r
6191 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6192 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6193 SendToICS(ics_prefix);
\r
6194 SendToICS(message);
\r
6199 if (strncmp(message, "feature ", 8) == 0) {
\r
6200 ParseFeatures(message+8, cps);
\r
6202 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6206 * If the move is illegal, cancel it and redraw the board.
\r
6207 * Also deal with other error cases. Matching is rather loose
\r
6208 * here to accommodate engines written before the spec.
\r
6210 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6211 strncmp(message, "Error", 5) == 0) {
\r
6212 if (StrStr(message, "name") ||
\r
6213 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6214 StrStr(message, "result") || StrStr(message, "board") ||
\r
6215 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6216 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6217 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6218 StrStr(message, "accepted")) {
\r
6221 if (StrStr(message, "protover")) {
\r
6222 /* Program is responding to input, so it's apparently done
\r
6223 initializing, and this error message indicates it is
\r
6224 protocol version 1. So we don't need to wait any longer
\r
6225 for it to initialize and send feature commands. */
\r
6226 FeatureDone(cps, 1);
\r
6227 cps->protocolVersion = 1;
\r
6230 cps->maybeThinking = FALSE;
\r
6232 if (StrStr(message, "draw")) {
\r
6233 /* Program doesn't have "draw" command */
\r
6234 cps->sendDrawOffers = 0;
\r
6237 if (cps->sendTime != 1 &&
\r
6238 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6239 /* Program apparently doesn't have "time" or "otim" command */
\r
6240 cps->sendTime = 0;
\r
6243 if (StrStr(message, "analyze")) {
\r
6244 cps->analysisSupport = FALSE;
\r
6245 cps->analyzing = FALSE;
\r
6246 Reset(FALSE, TRUE);
\r
6247 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6248 DisplayError(buf2, 0);
\r
6251 if (StrStr(message, "(no matching move)st")) {
\r
6252 /* Special kludge for GNU Chess 4 only */
\r
6253 cps->stKludge = TRUE;
\r
6254 SendTimeControl(cps, movesPerSession, timeControl,
\r
6255 timeIncrement, appData.searchDepth,
\r
6259 if (StrStr(message, "(no matching move)sd")) {
\r
6260 /* Special kludge for GNU Chess 4 only */
\r
6261 cps->sdKludge = TRUE;
\r
6262 SendTimeControl(cps, movesPerSession, timeControl,
\r
6263 timeIncrement, appData.searchDepth,
\r
6267 if (!StrStr(message, "llegal")) {
\r
6270 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6271 gameMode == IcsIdle) return;
\r
6272 if (forwardMostMove <= backwardMostMove) return;
\r
6274 /* Following removed: it caused a bug where a real illegal move
\r
6275 message in analyze mored would be ignored. */
\r
6276 if (cps == &first && programStats.ok_to_send == 0) {
\r
6277 /* Bogus message from Crafty responding to "." This filtering
\r
6278 can miss some of the bad messages, but fortunately the bug
\r
6279 is fixed in current Crafty versions, so it doesn't matter. */
\r
6283 if (pausing) PauseEvent();
\r
6284 if (gameMode == PlayFromGameFile) {
\r
6285 /* Stop reading this game file */
\r
6286 gameMode = EditGame;
\r
6289 currentMove = --forwardMostMove;
\r
6290 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6292 DisplayBothClocks();
\r
6293 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6294 parseList[currentMove], cps->which);
\r
6295 DisplayMoveError(buf1);
\r
6296 DrawPosition(FALSE, boards[currentMove]);
\r
6298 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6299 /* only passes fully legal moves */
\r
6300 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6301 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6302 "False illegal-move claim", GE_XBOARD );
\r
6306 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6307 /* Program has a broken "time" command that
\r
6308 outputs a string not ending in newline.
\r
6310 cps->sendTime = 0;
\r
6314 * If chess program startup fails, exit with an error message.
\r
6315 * Attempts to recover here are futile.
\r
6317 if ((StrStr(message, "unknown host") != NULL)
\r
6318 || (StrStr(message, "No remote directory") != NULL)
\r
6319 || (StrStr(message, "not found") != NULL)
\r
6320 || (StrStr(message, "No such file") != NULL)
\r
6321 || (StrStr(message, "can't alloc") != NULL)
\r
6322 || (StrStr(message, "Permission denied") != NULL)) {
\r
6324 cps->maybeThinking = FALSE;
\r
6325 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6326 cps->which, cps->program, cps->host, message);
\r
6327 RemoveInputSource(cps->isr);
\r
6328 DisplayFatalError(buf1, 0, 1);
\r
6333 * Look for hint output
\r
6335 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6336 if (cps == &first && hintRequested) {
\r
6337 hintRequested = FALSE;
\r
6338 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6339 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6340 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6341 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6342 fromY, fromX, toY, toX, promoChar, buf1);
\r
6343 sprintf(buf2, _("Hint: %s"), buf1);
\r
6344 DisplayInformation(buf2);
\r
6346 /* Hint move could not be parsed!? */
\r
6348 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6349 buf1, cps->which);
\r
6350 DisplayError(buf2, 0);
\r
6353 strcpy(lastHint, buf1);
\r
6359 * Ignore other messages if game is not in progress
\r
6361 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6362 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6365 * look for win, lose, draw, or draw offer
\r
6367 if (strncmp(message, "1-0", 3) == 0) {
\r
6368 char *p, *q, *r = "";
\r
6369 p = strchr(message, '{');
\r
6371 q = strchr(p, '}');
\r
6377 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6379 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6380 char *p, *q, *r = "";
\r
6381 p = strchr(message, '{');
\r
6383 q = strchr(p, '}');
\r
6389 /* Kludge for Arasan 4.1 bug */
\r
6390 if (strcmp(r, "Black resigns") == 0) {
\r
6391 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6394 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6396 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6397 char *p, *q, *r = "";
\r
6398 p = strchr(message, '{');
\r
6400 q = strchr(p, '}');
\r
6407 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6410 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6411 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6413 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6414 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6416 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6417 strncmp(message, "Black matches", 13) == 0 ) {
\r
6418 /* [HGM] ignore GNUShogi noises */
\r
6420 } else if (strncmp(message, "White", 5) == 0 &&
\r
6421 message[5] != '(' &&
\r
6422 StrStr(message, "Black") == NULL) {
\r
6423 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6425 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6426 message[5] != '(') {
\r
6427 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6429 } else if (strcmp(message, "resign") == 0 ||
\r
6430 strcmp(message, "computer resigns") == 0) {
\r
6431 switch (gameMode) {
\r
6432 case MachinePlaysBlack:
\r
6433 case IcsPlayingBlack:
\r
6434 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6436 case MachinePlaysWhite:
\r
6437 case IcsPlayingWhite:
\r
6438 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6440 case TwoMachinesPlay:
\r
6441 if (cps->twoMachinesColor[0] == 'w')
\r
6442 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6444 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6447 /* can't happen */
\r
6451 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6452 switch (gameMode) {
\r
6453 case MachinePlaysBlack:
\r
6454 case IcsPlayingBlack:
\r
6455 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6457 case MachinePlaysWhite:
\r
6458 case IcsPlayingWhite:
\r
6459 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6461 case TwoMachinesPlay:
\r
6462 if (cps->twoMachinesColor[0] == 'w')
\r
6463 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6465 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6468 /* can't happen */
\r
6472 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6473 switch (gameMode) {
\r
6474 case MachinePlaysBlack:
\r
6475 case IcsPlayingBlack:
\r
6476 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6478 case MachinePlaysWhite:
\r
6479 case IcsPlayingWhite:
\r
6480 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6482 case TwoMachinesPlay:
\r
6483 if (cps->twoMachinesColor[0] == 'w')
\r
6484 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6489 /* can't happen */
\r
6493 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6494 if (WhiteOnMove(forwardMostMove)) {
\r
6495 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6497 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6500 } else if (strstr(message, "Draw") != NULL ||
\r
6501 strstr(message, "game is a draw") != NULL) {
\r
6502 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6504 } else if (strstr(message, "offer") != NULL &&
\r
6505 strstr(message, "draw") != NULL) {
\r
6507 if (appData.zippyPlay && first.initDone) {
\r
6508 /* Relay offer to ICS */
\r
6509 SendToICS(ics_prefix);
\r
6510 SendToICS("draw\n");
\r
6513 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6514 if (gameMode == TwoMachinesPlay) {
\r
6515 if (cps->other->offeredDraw) {
\r
6516 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6517 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6518 /* until after we also have move, to see if it is really claim */
\r
6522 if (cps->other->sendDrawOffers) {
\r
6523 SendToProgram("draw\n", cps->other);
\r
6527 } else if (gameMode == MachinePlaysWhite ||
\r
6528 gameMode == MachinePlaysBlack) {
\r
6529 if (userOfferedDraw) {
\r
6530 DisplayInformation(_("Machine accepts your draw offer"));
\r
6531 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6533 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6540 * Look for thinking output
\r
6542 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6543 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6545 int plylev, mvleft, mvtot, curscore, time;
\r
6546 char mvname[MOVE_LEN];
\r
6547 u64 nodes; // [DM]
\r
6549 int ignore = FALSE;
\r
6550 int prefixHint = FALSE;
\r
6551 mvname[0] = NULLCHAR;
\r
6553 switch (gameMode) {
\r
6554 case MachinePlaysBlack:
\r
6555 case IcsPlayingBlack:
\r
6556 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6558 case MachinePlaysWhite:
\r
6559 case IcsPlayingWhite:
\r
6560 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6565 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6566 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6568 case TwoMachinesPlay:
\r
6569 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6579 buf1[0] = NULLCHAR;
\r
6580 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6581 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6583 if (plyext != ' ' && plyext != '\t') {
\r
6587 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6588 if( cps->scoreIsAbsolute &&
\r
6589 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6591 curscore = -curscore;
\r
6595 programStats.depth = plylev;
\r
6596 programStats.nodes = nodes;
\r
6597 programStats.time = time;
\r
6598 programStats.score = curscore;
\r
6599 programStats.got_only_move = 0;
\r
6601 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6604 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6605 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6606 if(WhiteOnMove(forwardMostMove))
\r
6607 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6608 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6611 /* Buffer overflow protection */
\r
6612 if (buf1[0] != NULLCHAR) {
\r
6613 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6614 && appData.debugMode) {
\r
6616 "PV is too long; using the first %d bytes.\n",
\r
6617 sizeof(programStats.movelist) - 1);
\r
6620 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6622 sprintf(programStats.movelist, " no PV\n");
\r
6625 if (programStats.seen_stat) {
\r
6626 programStats.ok_to_send = 1;
\r
6629 if (strchr(programStats.movelist, '(') != NULL) {
\r
6630 programStats.line_is_book = 1;
\r
6631 programStats.nr_moves = 0;
\r
6632 programStats.moves_left = 0;
\r
6634 programStats.line_is_book = 0;
\r
6637 SendProgramStatsToFrontend( cps, &programStats );
\r
6640 [AS] Protect the thinkOutput buffer from overflow... this
\r
6641 is only useful if buf1 hasn't overflowed first!
\r
6643 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6645 (gameMode == TwoMachinesPlay ?
\r
6646 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6647 ((double) curscore) / 100.0,
\r
6648 prefixHint ? lastHint : "",
\r
6649 prefixHint ? " " : "" );
\r
6651 if( buf1[0] != NULLCHAR ) {
\r
6652 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6654 if( strlen(buf1) > max_len ) {
\r
6655 if( appData.debugMode) {
\r
6656 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6658 buf1[max_len+1] = '\0';
\r
6661 strcat( thinkOutput, buf1 );
\r
6664 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6665 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6666 DisplayMove(currentMove - 1);
\r
6667 DisplayAnalysis();
\r
6671 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6672 /* crafty (9.25+) says "(only move) <move>"
\r
6673 * if there is only 1 legal move
\r
6675 sscanf(p, "(only move) %s", buf1);
\r
6676 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6677 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6678 programStats.depth = 1;
\r
6679 programStats.nr_moves = 1;
\r
6680 programStats.moves_left = 1;
\r
6681 programStats.nodes = 1;
\r
6682 programStats.time = 1;
\r
6683 programStats.got_only_move = 1;
\r
6685 /* Not really, but we also use this member to
\r
6686 mean "line isn't going to change" (Crafty
\r
6687 isn't searching, so stats won't change) */
\r
6688 programStats.line_is_book = 1;
\r
6690 SendProgramStatsToFrontend( cps, &programStats );
\r
6692 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6693 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6694 DisplayMove(currentMove - 1);
\r
6695 DisplayAnalysis();
\r
6698 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6699 &time, &nodes, &plylev, &mvleft,
\r
6700 &mvtot, mvname) >= 5) {
\r
6701 /* The stat01: line is from Crafty (9.29+) in response
\r
6702 to the "." command */
\r
6703 programStats.seen_stat = 1;
\r
6704 cps->maybeThinking = TRUE;
\r
6706 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6709 programStats.depth = plylev;
\r
6710 programStats.time = time;
\r
6711 programStats.nodes = nodes;
\r
6712 programStats.moves_left = mvleft;
\r
6713 programStats.nr_moves = mvtot;
\r
6714 strcpy(programStats.move_name, mvname);
\r
6715 programStats.ok_to_send = 1;
\r
6716 programStats.movelist[0] = '\0';
\r
6718 SendProgramStatsToFrontend( cps, &programStats );
\r
6720 DisplayAnalysis();
\r
6723 } else if (strncmp(message,"++",2) == 0) {
\r
6724 /* Crafty 9.29+ outputs this */
\r
6725 programStats.got_fail = 2;
\r
6728 } else if (strncmp(message,"--",2) == 0) {
\r
6729 /* Crafty 9.29+ outputs this */
\r
6730 programStats.got_fail = 1;
\r
6733 } else if (thinkOutput[0] != NULLCHAR &&
\r
6734 strncmp(message, " ", 4) == 0) {
\r
6735 unsigned message_len;
\r
6738 while (*p && *p == ' ') p++;
\r
6740 message_len = strlen( p );
\r
6742 /* [AS] Avoid buffer overflow */
\r
6743 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6744 strcat(thinkOutput, " ");
\r
6745 strcat(thinkOutput, p);
\r
6748 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6749 strcat(programStats.movelist, " ");
\r
6750 strcat(programStats.movelist, p);
\r
6753 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6754 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6755 DisplayMove(currentMove - 1);
\r
6756 DisplayAnalysis();
\r
6762 buf1[0] = NULLCHAR;
\r
6764 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6765 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6767 ChessProgramStats cpstats;
\r
6769 if (plyext != ' ' && plyext != '\t') {
\r
6773 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6774 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6775 curscore = -curscore;
\r
6778 cpstats.depth = plylev;
\r
6779 cpstats.nodes = nodes;
\r
6780 cpstats.time = time;
\r
6781 cpstats.score = curscore;
\r
6782 cpstats.got_only_move = 0;
\r
6783 cpstats.movelist[0] = '\0';
\r
6785 if (buf1[0] != NULLCHAR) {
\r
6786 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6789 cpstats.ok_to_send = 0;
\r
6790 cpstats.line_is_book = 0;
\r
6791 cpstats.nr_moves = 0;
\r
6792 cpstats.moves_left = 0;
\r
6794 SendProgramStatsToFrontend( cps, &cpstats );
\r
6801 /* Parse a game score from the character string "game", and
\r
6802 record it as the history of the current game. The game
\r
6803 score is NOT assumed to start from the standard position.
\r
6804 The display is not updated in any way.
\r
6807 ParseGameHistory(game)
\r
6810 ChessMove moveType;
\r
6811 int fromX, fromY, toX, toY, boardIndex;
\r
6814 char buf[MSG_SIZ];
\r
6816 if (appData.debugMode)
\r
6817 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6819 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6820 gameInfo.site = StrSave(appData.icsHost);
\r
6821 gameInfo.date = PGNDate();
\r
6822 gameInfo.round = StrSave("-");
\r
6824 /* Parse out names of players */
\r
6825 while (*game == ' ') game++;
\r
6827 while (*game != ' ') *p++ = *game++;
\r
6829 gameInfo.white = StrSave(buf);
\r
6830 while (*game == ' ') game++;
\r
6832 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6834 gameInfo.black = StrSave(buf);
\r
6837 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6840 yyboardindex = boardIndex;
\r
6841 moveType = (ChessMove) yylex();
\r
6842 switch (moveType) {
\r
6843 case IllegalMove: /* maybe suicide chess, etc. */
\r
6844 if (appData.debugMode) {
\r
6845 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6846 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6847 setbuf(debugFP, NULL);
\r
6849 case WhitePromotionChancellor:
\r
6850 case BlackPromotionChancellor:
\r
6851 case WhitePromotionArchbishop:
\r
6852 case BlackPromotionArchbishop:
\r
6853 case WhitePromotionQueen:
\r
6854 case BlackPromotionQueen:
\r
6855 case WhitePromotionRook:
\r
6856 case BlackPromotionRook:
\r
6857 case WhitePromotionBishop:
\r
6858 case BlackPromotionBishop:
\r
6859 case WhitePromotionKnight:
\r
6860 case BlackPromotionKnight:
\r
6861 case WhitePromotionKing:
\r
6862 case BlackPromotionKing:
\r
6864 case WhiteCapturesEnPassant:
\r
6865 case BlackCapturesEnPassant:
\r
6866 case WhiteKingSideCastle:
\r
6867 case WhiteQueenSideCastle:
\r
6868 case BlackKingSideCastle:
\r
6869 case BlackQueenSideCastle:
\r
6870 case WhiteKingSideCastleWild:
\r
6871 case WhiteQueenSideCastleWild:
\r
6872 case BlackKingSideCastleWild:
\r
6873 case BlackQueenSideCastleWild:
\r
6875 case WhiteHSideCastleFR:
\r
6876 case WhiteASideCastleFR:
\r
6877 case BlackHSideCastleFR:
\r
6878 case BlackASideCastleFR:
\r
6880 fromX = currentMoveString[0] - AAA;
\r
6881 fromY = currentMoveString[1] - ONE;
\r
6882 toX = currentMoveString[2] - AAA;
\r
6883 toY = currentMoveString[3] - ONE;
\r
6884 promoChar = currentMoveString[4];
\r
6888 fromX = moveType == WhiteDrop ?
\r
6889 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6890 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6891 fromY = DROP_RANK;
\r
6892 toX = currentMoveString[2] - AAA;
\r
6893 toY = currentMoveString[3] - ONE;
\r
6894 promoChar = NULLCHAR;
\r
6896 case AmbiguousMove:
\r
6898 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6899 if (appData.debugMode) {
\r
6900 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6901 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6902 setbuf(debugFP, NULL);
\r
6904 DisplayError(buf, 0);
\r
6906 case ImpossibleMove:
\r
6908 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6909 if (appData.debugMode) {
\r
6910 fprintf(debugFP, "Impossible 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 (ChessMove) 0: /* end of file */
\r
6917 if (boardIndex < backwardMostMove) {
\r
6918 /* Oops, gap. How did that happen? */
\r
6919 DisplayError(_("Gap in move list"), 0);
\r
6922 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6923 if (boardIndex > forwardMostMove) {
\r
6924 forwardMostMove = boardIndex;
\r
6928 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6929 strcat(parseList[boardIndex-1], " ");
\r
6930 strcat(parseList[boardIndex-1], yy_text);
\r
6942 case GameUnfinished:
\r
6943 if (gameMode == IcsExamining) {
\r
6944 if (boardIndex < backwardMostMove) {
\r
6945 /* Oops, gap. How did that happen? */
\r
6948 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6951 gameInfo.result = moveType;
\r
6952 p = strchr(yy_text, '{');
\r
6953 if (p == NULL) p = strchr(yy_text, '(');
\r
6956 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6958 q = strchr(p, *p == '{' ? '}' : ')');
\r
6959 if (q != NULL) *q = NULLCHAR;
\r
6962 gameInfo.resultDetails = StrSave(p);
\r
6965 if (boardIndex >= forwardMostMove &&
\r
6966 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6967 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6970 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6971 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6972 parseList[boardIndex]);
\r
6973 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6974 /* currentMoveString is set as a side-effect of yylex */
\r
6975 strcpy(moveList[boardIndex], currentMoveString);
\r
6976 strcat(moveList[boardIndex], "\n");
\r
6978 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6979 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6980 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6982 case MT_STALEMATE:
\r
6986 if(gameInfo.variant != VariantShogi)
\r
6987 strcat(parseList[boardIndex - 1], "+");
\r
6989 case MT_CHECKMATE:
\r
6990 strcat(parseList[boardIndex - 1], "#");
\r
6997 /* Apply a move to the given board */
\r
6999 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7000 int fromX, fromY, toX, toY;
\r
7004 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7006 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7007 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7008 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7011 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7012 oldEP = epStatus[p-1];
\r
7013 epStatus[p] = EP_NONE;
\r
7015 if( board[toY][toX] != EmptySquare )
\r
7016 epStatus[p] = EP_CAPTURE;
\r
7018 if( board[fromY][fromX] == WhitePawn ) {
\r
7019 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7020 epStatus[p] = EP_PAWN_MOVE;
\r
7021 if( toY-fromY==2) {
\r
7022 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7023 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7024 epStatus[p] = toX | berolina;
\r
7025 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7026 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7027 epStatus[p] = toX;
\r
7030 if( board[fromY][fromX] == BlackPawn ) {
\r
7031 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7032 epStatus[p] = EP_PAWN_MOVE;
\r
7033 if( toY-fromY== -2) {
\r
7034 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7035 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7036 epStatus[p] = toX | berolina;
\r
7037 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7038 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7039 epStatus[p] = toX;
\r
7043 for(i=0; i<nrCastlingRights; i++) {
\r
7044 castlingRights[p][i] = castlingRights[p-1][i];
\r
7045 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7046 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7047 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7052 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7053 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7054 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7056 if (fromX == toX && fromY == toY) return;
\r
7058 if (fromY == DROP_RANK) {
\r
7059 /* must be first */
\r
7060 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7062 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7063 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7064 if(gameInfo.variant == VariantKnightmate)
\r
7065 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7067 /* Code added by Tord: */
\r
7068 /* FRC castling assumed when king captures friendly rook. */
\r
7069 if (board[fromY][fromX] == WhiteKing &&
\r
7070 board[toY][toX] == WhiteRook) {
\r
7071 board[fromY][fromX] = EmptySquare;
\r
7072 board[toY][toX] = EmptySquare;
\r
7074 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7076 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7078 } else if (board[fromY][fromX] == BlackKing &&
\r
7079 board[toY][toX] == BlackRook) {
\r
7080 board[fromY][fromX] = EmptySquare;
\r
7081 board[toY][toX] = EmptySquare;
\r
7083 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7085 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7087 /* End of code added by Tord */
\r
7089 } else if (board[fromY][fromX] == king
\r
7090 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7091 && toY == fromY && toX > fromX+1) {
\r
7092 board[fromY][fromX] = EmptySquare;
\r
7093 board[toY][toX] = king;
\r
7094 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7095 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7096 } else if (board[fromY][fromX] == king
\r
7097 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7098 && toY == fromY && toX < fromX-1) {
\r
7099 board[fromY][fromX] = EmptySquare;
\r
7100 board[toY][toX] = king;
\r
7101 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7102 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7103 } else if (board[fromY][fromX] == WhitePawn
\r
7104 && toY == BOARD_HEIGHT-1
\r
7105 && gameInfo.variant != VariantXiangqi
\r
7107 /* white pawn promotion */
\r
7108 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7109 if (board[toY][toX] == EmptySquare) {
\r
7110 board[toY][toX] = WhiteQueen;
\r
7112 if(gameInfo.variant==VariantBughouse ||
\r
7113 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7114 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7115 board[fromY][fromX] = EmptySquare;
\r
7116 } else if ((fromY == BOARD_HEIGHT-4)
\r
7118 && gameInfo.variant != VariantXiangqi
\r
7119 && gameInfo.variant != VariantBerolina
\r
7120 && (board[fromY][fromX] == WhitePawn)
\r
7121 && (board[toY][toX] == EmptySquare)) {
\r
7122 board[fromY][fromX] = EmptySquare;
\r
7123 board[toY][toX] = WhitePawn;
\r
7124 captured = board[toY - 1][toX];
\r
7125 board[toY - 1][toX] = EmptySquare;
\r
7126 } else if ((fromY == BOARD_HEIGHT-4)
\r
7128 && gameInfo.variant == VariantBerolina
\r
7129 && (board[fromY][fromX] == WhitePawn)
\r
7130 && (board[toY][toX] == EmptySquare)) {
\r
7131 board[fromY][fromX] = EmptySquare;
\r
7132 board[toY][toX] = WhitePawn;
\r
7133 if(oldEP & EP_BEROLIN_A) {
\r
7134 captured = board[fromY][fromX-1];
\r
7135 board[fromY][fromX-1] = EmptySquare;
\r
7136 }else{ captured = board[fromY][fromX+1];
\r
7137 board[fromY][fromX+1] = EmptySquare;
\r
7139 } else if (board[fromY][fromX] == king
\r
7140 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7141 && toY == fromY && toX > fromX+1) {
\r
7142 board[fromY][fromX] = EmptySquare;
\r
7143 board[toY][toX] = king;
\r
7144 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7145 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7146 } else if (board[fromY][fromX] == king
\r
7147 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7148 && toY == fromY && toX < fromX-1) {
\r
7149 board[fromY][fromX] = EmptySquare;
\r
7150 board[toY][toX] = king;
\r
7151 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7152 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7153 } else if (fromY == 7 && fromX == 3
\r
7154 && board[fromY][fromX] == BlackKing
\r
7155 && toY == 7 && toX == 5) {
\r
7156 board[fromY][fromX] = EmptySquare;
\r
7157 board[toY][toX] = BlackKing;
\r
7158 board[fromY][7] = EmptySquare;
\r
7159 board[toY][4] = BlackRook;
\r
7160 } else if (fromY == 7 && fromX == 3
\r
7161 && board[fromY][fromX] == BlackKing
\r
7162 && toY == 7 && toX == 1) {
\r
7163 board[fromY][fromX] = EmptySquare;
\r
7164 board[toY][toX] = BlackKing;
\r
7165 board[fromY][0] = EmptySquare;
\r
7166 board[toY][2] = BlackRook;
\r
7167 } else if (board[fromY][fromX] == BlackPawn
\r
7169 && gameInfo.variant != VariantXiangqi
\r
7171 /* black pawn promotion */
\r
7172 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7173 if (board[0][toX] == EmptySquare) {
\r
7174 board[0][toX] = BlackQueen;
\r
7176 if(gameInfo.variant==VariantBughouse ||
\r
7177 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7178 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7179 board[fromY][fromX] = EmptySquare;
\r
7180 } else if ((fromY == 3)
\r
7182 && gameInfo.variant != VariantXiangqi
\r
7183 && gameInfo.variant != VariantBerolina
\r
7184 && (board[fromY][fromX] == BlackPawn)
\r
7185 && (board[toY][toX] == EmptySquare)) {
\r
7186 board[fromY][fromX] = EmptySquare;
\r
7187 board[toY][toX] = BlackPawn;
\r
7188 captured = board[toY + 1][toX];
\r
7189 board[toY + 1][toX] = EmptySquare;
\r
7190 } else if ((fromY == 3)
\r
7192 && gameInfo.variant == VariantBerolina
\r
7193 && (board[fromY][fromX] == BlackPawn)
\r
7194 && (board[toY][toX] == EmptySquare)) {
\r
7195 board[fromY][fromX] = EmptySquare;
\r
7196 board[toY][toX] = BlackPawn;
\r
7197 if(oldEP & EP_BEROLIN_A) {
\r
7198 captured = board[fromY][fromX-1];
\r
7199 board[fromY][fromX-1] = EmptySquare;
\r
7200 }else{ captured = board[fromY][fromX+1];
\r
7201 board[fromY][fromX+1] = EmptySquare;
\r
7204 board[toY][toX] = board[fromY][fromX];
\r
7205 board[fromY][fromX] = EmptySquare;
\r
7208 /* [HGM] now we promote for Shogi, if needed */
\r
7209 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7210 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7213 if (gameInfo.holdingsWidth != 0) {
\r
7215 /* !!A lot more code needs to be written to support holdings */
\r
7216 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7217 /* penultimate board files, so they are automaticlly stored */
\r
7218 /* in the game history. */
\r
7219 if (fromY == DROP_RANK) {
\r
7220 /* Delete from holdings, by decreasing count */
\r
7221 /* and erasing image if necessary */
\r
7223 if(p < (int) BlackPawn) { /* white drop */
\r
7224 p -= (int)WhitePawn;
\r
7225 if(p >= gameInfo.holdingsSize) p = 0;
\r
7226 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7227 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7228 } else { /* black drop */
\r
7229 p -= (int)BlackPawn;
\r
7230 if(p >= gameInfo.holdingsSize) p = 0;
\r
7231 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7232 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7235 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7236 && gameInfo.variant != VariantBughouse ) {
\r
7237 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7238 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7239 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7240 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7242 p = (int) captured;
\r
7243 if (p >= (int) BlackPawn) {
\r
7244 p -= (int)BlackPawn;
\r
7245 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7246 /* in Shogi restore piece to its original first */
\r
7247 captured = (ChessSquare) (DEMOTED captured);
\r
7250 p = PieceToNumber((ChessSquare)p);
\r
7251 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7252 board[p][BOARD_WIDTH-2]++;
\r
7253 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7255 p -= (int)WhitePawn;
\r
7256 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7257 captured = (ChessSquare) (DEMOTED captured);
\r
7260 p = PieceToNumber((ChessSquare)p);
\r
7261 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7262 board[BOARD_HEIGHT-1-p][1]++;
\r
7263 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7267 } else if (gameInfo.variant == VariantAtomic) {
\r
7268 if (captured != EmptySquare) {
\r
7270 for (y = toY-1; y <= toY+1; y++) {
\r
7271 for (x = toX-1; x <= toX+1; x++) {
\r
7272 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7273 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7274 board[y][x] = EmptySquare;
\r
7278 board[toY][toX] = EmptySquare;
\r
7281 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7282 /* [HGM] Shogi promotions */
\r
7283 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7288 /* Updates forwardMostMove */
\r
7290 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7291 int fromX, fromY, toX, toY;
\r
7294 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7296 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7297 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7298 piece = boards[forwardMostMove][fromY][fromX];
\r
7299 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7300 if(gameInfo.variant == VariantKnightmate)
\r
7301 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7302 if(forwardMostMove == 0) {
\r
7303 if(blackPlaysFirst)
\r
7304 fprintf(serverMoves, "%s;", second.tidy);
\r
7305 fprintf(serverMoves, "%s;", first.tidy);
\r
7306 if(!blackPlaysFirst)
\r
7307 fprintf(serverMoves, "%s;", second.tidy);
\r
7308 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7309 lastLoadFlag = loadFlag;
\r
7310 // print base move
\r
7311 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7312 // print castling suffix
\r
7313 if( toY == fromY && piece == king ) {
\r
7315 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7317 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7320 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7321 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7322 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7324 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7325 // promotion suffix
\r
7326 if(promoChar != NULLCHAR)
\r
7327 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7329 fprintf(serverMoves, "/%d/%d",
\r
7330 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7331 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7332 else timeLeft = blackTimeRemaining/1000;
\r
7333 fprintf(serverMoves, "/%d", timeLeft);
\r
7335 fflush(serverMoves);
\r
7338 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7339 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7344 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7345 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7346 if (commentList[forwardMostMove+1] != NULL) {
\r
7347 free(commentList[forwardMostMove+1]);
\r
7348 commentList[forwardMostMove+1] = NULL;
\r
7350 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7351 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7352 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7353 gameInfo.result = GameUnfinished;
\r
7354 if (gameInfo.resultDetails != NULL) {
\r
7355 free(gameInfo.resultDetails);
\r
7356 gameInfo.resultDetails = NULL;
\r
7358 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7359 moveList[forwardMostMove - 1]);
\r
7360 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7361 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7362 fromY, fromX, toY, toX, promoChar,
\r
7363 parseList[forwardMostMove - 1]);
\r
7364 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7365 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7366 castlingRights[forwardMostMove]) ) {
\r
7368 case MT_STALEMATE:
\r
7372 if(gameInfo.variant != VariantShogi)
\r
7373 strcat(parseList[forwardMostMove - 1], "+");
\r
7375 case MT_CHECKMATE:
\r
7376 strcat(parseList[forwardMostMove - 1], "#");
\r
7379 if (appData.debugMode) {
\r
7380 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7385 /* Updates currentMove if not pausing */
\r
7387 ShowMove(fromX, fromY, toX, toY)
\r
7389 int instant = (gameMode == PlayFromGameFile) ?
\r
7390 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7391 if(appData.noGUI) return;
\r
7392 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7394 if (forwardMostMove == currentMove + 1) {
\r
7395 AnimateMove(boards[forwardMostMove - 1],
\r
7396 fromX, fromY, toX, toY);
\r
7398 if (appData.highlightLastMove) {
\r
7399 SetHighlights(fromX, fromY, toX, toY);
\r
7402 currentMove = forwardMostMove;
\r
7405 if (instant) return;
\r
7407 DisplayMove(currentMove - 1);
\r
7408 DrawPosition(FALSE, boards[currentMove]);
\r
7409 DisplayBothClocks();
\r
7410 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7413 void SendEgtPath(ChessProgramState *cps)
\r
7414 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7415 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7417 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7420 char c, *q = name+1, *r, *s;
\r
7422 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7423 while(*p && *p != ',') *q++ = *p++;
\r
7424 *q++ = ':'; *q = 0;
\r
7425 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7426 strcmp(name, ",nalimov:") == 0 ) {
\r
7427 // take nalimov path from the menu-changeable option first, if it is defined
\r
7428 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7429 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7431 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7432 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7433 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7434 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7435 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7436 c = *r; *r = 0; // temporarily null-terminate path info
\r
7437 *--q = 0; // strip of trailig ':' from name
\r
7438 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7440 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7442 if(*p == ',') p++; // read away comma to position for next format name
\r
7447 InitChessProgram(cps, setup)
\r
7448 ChessProgramState *cps;
\r
7449 int setup; /* [HGM] needed to setup FRC opening position */
\r
7451 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7452 if (appData.noChessProgram) return;
\r
7453 hintRequested = FALSE;
\r
7454 bookRequested = FALSE;
\r
7456 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7457 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7458 if(cps->memSize) { /* [HGM] memory */
\r
7459 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7460 SendToProgram(buf, cps);
\r
7462 SendEgtPath(cps); /* [HGM] EGT */
\r
7463 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7464 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7465 SendToProgram(buf, cps);
\r
7468 SendToProgram(cps->initString, cps);
\r
7469 if (gameInfo.variant != VariantNormal &&
\r
7470 gameInfo.variant != VariantLoadable
\r
7471 /* [HGM] also send variant if board size non-standard */
\r
7472 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7474 char *v = VariantName(gameInfo.variant);
\r
7475 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7476 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7477 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7478 DisplayFatalError(buf, 0, 1);
\r
7482 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7483 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7484 if( gameInfo.variant == VariantXiangqi )
\r
7485 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7486 if( gameInfo.variant == VariantShogi )
\r
7487 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7488 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7489 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7490 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7491 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7492 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7493 if( gameInfo.variant == VariantCourier )
\r
7494 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7495 if( gameInfo.variant == VariantSuper )
\r
7496 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7497 if( gameInfo.variant == VariantGreat )
\r
7498 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7501 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7502 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7503 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7504 if(StrStr(cps->variants, b) == NULL) {
\r
7505 // specific sized variant not known, check if general sizing allowed
\r
7506 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7507 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7508 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7509 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7510 DisplayFatalError(buf, 0, 1);
\r
7513 /* [HGM] here we really should compare with the maximum supported board size */
\r
7516 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7517 sprintf(buf, "variant %s\n", b);
\r
7518 SendToProgram(buf, cps);
\r
7520 currentlyInitializedVariant = gameInfo.variant;
\r
7522 /* [HGM] send opening position in FRC to first engine */
\r
7524 SendToProgram("force\n", cps);
\r
7525 SendBoard(cps, 0);
\r
7526 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7527 setboardSpoiledMachineBlack = 1;
\r
7530 if (cps->sendICS) {
\r
7531 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7532 SendToProgram(buf, cps);
\r
7534 cps->maybeThinking = FALSE;
\r
7535 cps->offeredDraw = 0;
\r
7536 if (!appData.icsActive) {
\r
7537 SendTimeControl(cps, movesPerSession, timeControl,
\r
7538 timeIncrement, appData.searchDepth,
\r
7541 if (appData.showThinking
\r
7542 // [HGM] thinking: four options require thinking output to be sent
\r
7543 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7545 SendToProgram("post\n", cps);
\r
7547 SendToProgram("hard\n", cps);
\r
7548 if (!appData.ponderNextMove) {
\r
7549 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7550 it without being sure what state we are in first. "hard"
\r
7551 is not a toggle, so that one is OK.
\r
7553 SendToProgram("easy\n", cps);
\r
7555 if (cps->usePing) {
\r
7556 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7557 SendToProgram(buf, cps);
\r
7559 cps->initDone = TRUE;
\r
7564 StartChessProgram(cps)
\r
7565 ChessProgramState *cps;
\r
7567 char buf[MSG_SIZ];
\r
7570 if (appData.noChessProgram) return;
\r
7571 cps->initDone = FALSE;
\r
7573 if (strcmp(cps->host, "localhost") == 0) {
\r
7574 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7575 } else if (*appData.remoteShell == NULLCHAR) {
\r
7576 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7578 if (*appData.remoteUser == NULLCHAR) {
\r
7579 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7582 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7583 cps->host, appData.remoteUser, cps->program);
\r
7585 err = StartChildProcess(buf, "", &cps->pr);
\r
7589 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7590 DisplayFatalError(buf, err, 1);
\r
7596 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7597 if (cps->protocolVersion > 1) {
\r
7598 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7599 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7600 cps->comboCnt = 0; // and values of combo boxes
\r
7601 SendToProgram(buf, cps);
\r
7603 SendToProgram("xboard\n", cps);
\r
7609 TwoMachinesEventIfReady P((void))
\r
7611 if (first.lastPing != first.lastPong) {
\r
7612 DisplayMessage("", _("Waiting for first chess program"));
\r
7613 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7616 if (second.lastPing != second.lastPong) {
\r
7617 DisplayMessage("", _("Waiting for second chess program"));
\r
7618 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7622 TwoMachinesEvent();
\r
7626 NextMatchGame P((void))
\r
7628 int index; /* [HGM] autoinc: step lod index during match */
\r
7629 Reset(FALSE, TRUE);
\r
7630 if (*appData.loadGameFile != NULLCHAR) {
\r
7631 index = appData.loadGameIndex;
\r
7632 if(index < 0) { // [HGM] autoinc
\r
7633 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7634 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7636 LoadGameFromFile(appData.loadGameFile,
\r
7638 appData.loadGameFile, FALSE);
\r
7639 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7640 index = appData.loadPositionIndex;
\r
7641 if(index < 0) { // [HGM] autoinc
\r
7642 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7643 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7645 LoadPositionFromFile(appData.loadPositionFile,
\r
7647 appData.loadPositionFile);
\r
7649 TwoMachinesEventIfReady();
\r
7652 void UserAdjudicationEvent( int result )
\r
7654 ChessMove gameResult = GameIsDrawn;
\r
7656 if( result > 0 ) {
\r
7657 gameResult = WhiteWins;
\r
7659 else if( result < 0 ) {
\r
7660 gameResult = BlackWins;
\r
7663 if( gameMode == TwoMachinesPlay ) {
\r
7664 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7670 GameEnds(result, resultDetails, whosays)
\r
7672 char *resultDetails;
\r
7675 GameMode nextGameMode;
\r
7677 char buf[MSG_SIZ];
\r
7679 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7682 if (appData.debugMode) {
\r
7683 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7684 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7687 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7688 /* If we are playing on ICS, the server decides when the
\r
7689 game is over, but the engine can offer to draw, claim
\r
7690 a draw, or resign.
\r
7693 if (appData.zippyPlay && first.initDone) {
\r
7694 if (result == GameIsDrawn) {
\r
7695 /* In case draw still needs to be claimed */
\r
7696 SendToICS(ics_prefix);
\r
7697 SendToICS("draw\n");
\r
7698 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7699 SendToICS(ics_prefix);
\r
7700 SendToICS("resign\n");
\r
7704 endingGame = 0; /* [HGM] crash */
\r
7708 /* If we're loading the game from a file, stop */
\r
7709 if (whosays == GE_FILE) {
\r
7710 (void) StopLoadGameTimer();
\r
7711 gameFileFP = NULL;
\r
7714 /* Cancel draw offers */
\r
7715 first.offeredDraw = second.offeredDraw = 0;
\r
7717 /* If this is an ICS game, only ICS can really say it's done;
\r
7718 if not, anyone can. */
\r
7719 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7720 gameMode == IcsPlayingBlack ||
\r
7721 gameMode == IcsObserving ||
\r
7722 gameMode == IcsExamining);
\r
7724 if (!isIcsGame || whosays == GE_ICS) {
\r
7725 /* OK -- not an ICS game, or ICS said it was done */
\r
7727 if (!isIcsGame && !appData.noChessProgram)
\r
7728 SetUserThinkingEnables();
\r
7730 /* [HGM] if a machine claims the game end we verify this claim */
\r
7731 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7732 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7734 ChessMove trueResult = (ChessMove) -1;
\r
7736 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7737 first.twoMachinesColor[0] :
\r
7738 second.twoMachinesColor[0] ;
\r
7740 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7741 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7742 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7743 trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)
\r
7744 ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!
\r
7746 if(epStatus[forwardMostMove] == EP_STALEMATE) {
\r
7747 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7748 if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide ||
\r
7749 gameInfo.variant == VariantLosers) // [HGM] losers: in giveaway variants stalemate wins
\r
7750 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7753 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7754 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7755 || gameInfo.variant == VariantGreat) &&
\r
7756 (result == WhiteWins && claimer == 'w' ||
\r
7757 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7758 if (appData.debugMode) {
\r
7759 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7760 result, epStatus[forwardMostMove], forwardMostMove);
\r
7762 if(result != trueResult) {
\r
7763 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7764 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7765 resultDetails = buf;
\r
7768 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7769 && (forwardMostMove <= backwardMostMove ||
\r
7770 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7771 (claimer=='b')==(forwardMostMove&1))
\r
7773 /* [HGM] verify: draws that were not flagged are false claims */
\r
7774 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7775 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7776 resultDetails = buf;
\r
7778 /* (Claiming a loss is accepted no questions asked!) */
\r
7780 /* [HGM] bare: don't allow bare King to win */
\r
7781 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7782 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7783 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7784 && result != GameIsDrawn)
\r
7785 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7786 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7787 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7788 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7790 if (appData.debugMode) {
\r
7791 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7792 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7795 result = GameIsDrawn;
\r
7796 sprintf(buf, "%s but bare king", resultDetails);
\r
7797 resultDetails = buf;
\r
7803 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7804 if(result==WhiteWins) c = '+';
\r
7805 if(result==BlackWins) c = '-';
\r
7806 if(resultDetails != NULL)
\r
7807 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7809 if (resultDetails != NULL) {
\r
7810 gameInfo.result = result;
\r
7811 gameInfo.resultDetails = StrSave(resultDetails);
\r
7813 /* display last move only if game was not loaded from file */
\r
7814 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7815 DisplayMove(currentMove - 1);
\r
7817 if (forwardMostMove != 0) {
\r
7818 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7819 if (*appData.saveGameFile != NULLCHAR) {
\r
7820 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7821 } else if (appData.autoSaveGames) {
\r
7824 if (*appData.savePositionFile != NULLCHAR) {
\r
7825 SavePositionToFile(appData.savePositionFile);
\r
7830 /* Tell program how game ended in case it is learning */
\r
7831 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7832 /* engine died and we got here through time loss. In that */
\r
7833 /* case we will get a fatal error writing the pipe, which */
\r
7834 /* would otherwise lose us the PGN. */
\r
7835 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7836 /* output during GameEnds should never be fatal anymore */
\r
7837 if (gameMode == MachinePlaysWhite ||
\r
7838 gameMode == MachinePlaysBlack ||
\r
7839 gameMode == TwoMachinesPlay ||
\r
7840 gameMode == IcsPlayingWhite ||
\r
7841 gameMode == IcsPlayingBlack ||
\r
7842 gameMode == BeginningOfGame) {
\r
7843 char buf[MSG_SIZ];
\r
7844 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7846 if (first.pr != NoProc) {
\r
7847 SendToProgram(buf, &first);
\r
7849 if (second.pr != NoProc &&
\r
7850 gameMode == TwoMachinesPlay) {
\r
7851 SendToProgram(buf, &second);
\r
7856 if (appData.icsActive) {
\r
7857 if (appData.quietPlay &&
\r
7858 (gameMode == IcsPlayingWhite ||
\r
7859 gameMode == IcsPlayingBlack)) {
\r
7860 SendToICS(ics_prefix);
\r
7861 SendToICS("set shout 1\n");
\r
7863 nextGameMode = IcsIdle;
\r
7864 ics_user_moved = FALSE;
\r
7865 /* clean up premove. It's ugly when the game has ended and the
\r
7866 * premove highlights are still on the board.
\r
7869 gotPremove = FALSE;
\r
7870 ClearPremoveHighlights();
\r
7871 DrawPosition(FALSE, boards[currentMove]);
\r
7873 if (whosays == GE_ICS) {
\r
7876 if (gameMode == IcsPlayingWhite)
\r
7877 PlayIcsWinSound();
\r
7878 else if(gameMode == IcsPlayingBlack)
\r
7879 PlayIcsLossSound();
\r
7882 if (gameMode == IcsPlayingBlack)
\r
7883 PlayIcsWinSound();
\r
7884 else if(gameMode == IcsPlayingWhite)
\r
7885 PlayIcsLossSound();
\r
7888 PlayIcsDrawSound();
\r
7891 PlayIcsUnfinishedSound();
\r
7894 } else if (gameMode == EditGame ||
\r
7895 gameMode == PlayFromGameFile ||
\r
7896 gameMode == AnalyzeMode ||
\r
7897 gameMode == AnalyzeFile) {
\r
7898 nextGameMode = gameMode;
\r
7900 nextGameMode = EndOfGame;
\r
7905 nextGameMode = gameMode;
\r
7908 if (appData.noChessProgram) {
\r
7909 gameMode = nextGameMode;
\r
7911 endingGame = 0; /* [HGM] crash */
\r
7915 if (first.reuse) {
\r
7916 /* Put first chess program into idle state */
\r
7917 if (first.pr != NoProc &&
\r
7918 (gameMode == MachinePlaysWhite ||
\r
7919 gameMode == MachinePlaysBlack ||
\r
7920 gameMode == TwoMachinesPlay ||
\r
7921 gameMode == IcsPlayingWhite ||
\r
7922 gameMode == IcsPlayingBlack ||
\r
7923 gameMode == BeginningOfGame)) {
\r
7924 SendToProgram("force\n", &first);
\r
7925 if (first.usePing) {
\r
7926 char buf[MSG_SIZ];
\r
7927 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7928 SendToProgram(buf, &first);
\r
7931 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7932 /* Kill off first chess program */
\r
7933 if (first.isr != NULL)
\r
7934 RemoveInputSource(first.isr);
\r
7937 if (first.pr != NoProc) {
\r
7938 ExitAnalyzeMode();
\r
7939 DoSleep( appData.delayBeforeQuit );
\r
7940 SendToProgram("quit\n", &first);
\r
7941 DoSleep( appData.delayAfterQuit );
\r
7942 DestroyChildProcess(first.pr, first.useSigterm);
\r
7944 first.pr = NoProc;
\r
7946 if (second.reuse) {
\r
7947 /* Put second chess program into idle state */
\r
7948 if (second.pr != NoProc &&
\r
7949 gameMode == TwoMachinesPlay) {
\r
7950 SendToProgram("force\n", &second);
\r
7951 if (second.usePing) {
\r
7952 char buf[MSG_SIZ];
\r
7953 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7954 SendToProgram(buf, &second);
\r
7957 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7958 /* Kill off second chess program */
\r
7959 if (second.isr != NULL)
\r
7960 RemoveInputSource(second.isr);
\r
7961 second.isr = NULL;
\r
7963 if (second.pr != NoProc) {
\r
7964 DoSleep( appData.delayBeforeQuit );
\r
7965 SendToProgram("quit\n", &second);
\r
7966 DoSleep( appData.delayAfterQuit );
\r
7967 DestroyChildProcess(second.pr, second.useSigterm);
\r
7969 second.pr = NoProc;
\r
7972 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7975 if (first.twoMachinesColor[0] == 'w') {
\r
7976 first.matchWins++;
\r
7978 second.matchWins++;
\r
7982 if (first.twoMachinesColor[0] == 'b') {
\r
7983 first.matchWins++;
\r
7985 second.matchWins++;
\r
7991 if (matchGame < appData.matchGames) {
\r
7993 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
7994 tmp = first.twoMachinesColor;
\r
7995 first.twoMachinesColor = second.twoMachinesColor;
\r
7996 second.twoMachinesColor = tmp;
\r
7998 gameMode = nextGameMode;
\r
8000 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8001 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8002 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8003 endingGame = 0; /* [HGM] crash */
\r
8006 char buf[MSG_SIZ];
\r
8007 gameMode = nextGameMode;
\r
8008 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8009 first.tidy, second.tidy,
\r
8010 first.matchWins, second.matchWins,
\r
8011 appData.matchGames - (first.matchWins + second.matchWins));
\r
8012 DisplayFatalError(buf, 0, 0);
\r
8015 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8016 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8017 ExitAnalyzeMode();
\r
8018 gameMode = nextGameMode;
\r
8020 endingGame = 0; /* [HGM] crash */
\r
8023 /* Assumes program was just initialized (initString sent).
\r
8024 Leaves program in force mode. */
\r
8026 FeedMovesToProgram(cps, upto)
\r
8027 ChessProgramState *cps;
\r
8032 if (appData.debugMode)
\r
8033 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8034 startedFromSetupPosition ? "position and " : "",
\r
8035 backwardMostMove, upto, cps->which);
\r
8036 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8037 // [HGM] variantswitch: make engine aware of new variant
\r
8038 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8039 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8040 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8041 SendToProgram(buf, cps);
\r
8042 currentlyInitializedVariant = gameInfo.variant;
\r
8044 SendToProgram("force\n", cps);
\r
8045 if (startedFromSetupPosition) {
\r
8046 SendBoard(cps, backwardMostMove);
\r
8047 if (appData.debugMode) {
\r
8048 fprintf(debugFP, "feedMoves\n");
\r
8051 for (i = backwardMostMove; i < upto; i++) {
\r
8052 SendMoveToProgram(i, cps);
\r
8058 ResurrectChessProgram()
\r
8060 /* The chess program may have exited.
\r
8061 If so, restart it and feed it all the moves made so far. */
\r
8063 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8065 StartChessProgram(&first);
\r
8066 InitChessProgram(&first, FALSE);
\r
8067 FeedMovesToProgram(&first, currentMove);
\r
8069 if (!first.sendTime) {
\r
8070 /* can't tell gnuchess what its clock should read,
\r
8071 so we bow to its notion. */
\r
8073 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8074 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8077 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8078 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8079 SendToProgram("analyze\n", &first);
\r
8080 first.analyzing = TRUE;
\r
8085 * Button procedures
\r
8088 Reset(redraw, init)
\r
8093 if (appData.debugMode) {
\r
8094 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8095 redraw, init, gameMode);
\r
8097 pausing = pauseExamInvalid = FALSE;
\r
8098 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8100 whiteFlag = blackFlag = FALSE;
\r
8101 userOfferedDraw = FALSE;
\r
8102 hintRequested = bookRequested = FALSE;
\r
8103 first.maybeThinking = FALSE;
\r
8104 second.maybeThinking = FALSE;
\r
8105 first.bookSuspend = FALSE; // [HGM] book
\r
8106 second.bookSuspend = FALSE;
\r
8107 thinkOutput[0] = NULLCHAR;
\r
8108 lastHint[0] = NULLCHAR;
\r
8109 ClearGameInfo(&gameInfo);
\r
8110 gameInfo.variant = StringToVariant(appData.variant);
\r
8111 ics_user_moved = ics_clock_paused = FALSE;
\r
8112 ics_getting_history = H_FALSE;
\r
8114 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8115 ClearProgramStats();
\r
8116 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8119 ClearHighlights();
\r
8120 flipView = appData.flipView;
\r
8121 ClearPremoveHighlights();
\r
8122 gotPremove = FALSE;
\r
8123 alarmSounded = FALSE;
\r
8125 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8126 if(appData.serverMovesName != NULL) {
\r
8127 /* [HGM] prepare to make moves file for broadcasting */
\r
8128 clock_t t = clock();
\r
8129 if(serverMoves != NULL) fclose(serverMoves);
\r
8130 serverMoves = fopen(appData.serverMovesName, "r");
\r
8131 if(serverMoves != NULL) {
\r
8132 fclose(serverMoves);
\r
8133 /* delay 15 sec before overwriting, so all clients can see end */
\r
8134 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8136 serverMoves = fopen(appData.serverMovesName, "w");
\r
8139 ExitAnalyzeMode();
\r
8140 gameMode = BeginningOfGame;
\r
8142 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8143 InitPosition(redraw);
\r
8144 for (i = 0; i < MAX_MOVES; i++) {
\r
8145 if (commentList[i] != NULL) {
\r
8146 free(commentList[i]);
\r
8147 commentList[i] = NULL;
\r
8151 timeRemaining[0][0] = whiteTimeRemaining;
\r
8152 timeRemaining[1][0] = blackTimeRemaining;
\r
8153 if (first.pr == NULL) {
\r
8154 StartChessProgram(&first);
\r
8157 InitChessProgram(&first, startedFromSetupPosition);
\r
8160 DisplayMessage("", "");
\r
8161 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8165 AutoPlayGameLoop()
\r
8168 if (!AutoPlayOneMove())
\r
8170 if (matchMode || appData.timeDelay == 0)
\r
8172 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8174 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8183 int fromX, fromY, toX, toY;
\r
8185 if (appData.debugMode) {
\r
8186 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8189 if (gameMode != PlayFromGameFile)
\r
8192 if (currentMove >= forwardMostMove) {
\r
8193 gameMode = EditGame;
\r
8196 /* [AS] Clear current move marker at the end of a game */
\r
8197 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8202 toX = moveList[currentMove][2] - AAA;
\r
8203 toY = moveList[currentMove][3] - ONE;
\r
8205 if (moveList[currentMove][1] == '@') {
\r
8206 if (appData.highlightLastMove) {
\r
8207 SetHighlights(-1, -1, toX, toY);
\r
8210 fromX = moveList[currentMove][0] - AAA;
\r
8211 fromY = moveList[currentMove][1] - ONE;
\r
8213 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8215 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8217 if (appData.highlightLastMove) {
\r
8218 SetHighlights(fromX, fromY, toX, toY);
\r
8221 DisplayMove(currentMove);
\r
8222 SendMoveToProgram(currentMove++, &first);
\r
8223 DisplayBothClocks();
\r
8224 DrawPosition(FALSE, boards[currentMove]);
\r
8225 // [HGM] PV info: always display, routine tests if empty
\r
8226 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8232 LoadGameOneMove(readAhead)
\r
8233 ChessMove readAhead;
\r
8235 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8236 char promoChar = NULLCHAR;
\r
8237 ChessMove moveType;
\r
8238 char move[MSG_SIZ];
\r
8241 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8242 gameMode != AnalyzeMode && gameMode != Training) {
\r
8243 gameFileFP = NULL;
\r
8247 yyboardindex = forwardMostMove;
\r
8248 if (readAhead != (ChessMove)0) {
\r
8249 moveType = readAhead;
\r
8251 if (gameFileFP == NULL)
\r
8253 moveType = (ChessMove) yylex();
\r
8257 switch (moveType) {
\r
8259 if (appData.debugMode)
\r
8260 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8262 if (*p == '{' || *p == '[' || *p == '(') {
\r
8263 p[strlen(p) - 1] = NULLCHAR;
\r
8267 /* append the comment but don't display it */
\r
8268 while (*p == '\n') p++;
\r
8269 AppendComment(currentMove, p);
\r
8272 case WhiteCapturesEnPassant:
\r
8273 case BlackCapturesEnPassant:
\r
8274 case WhitePromotionChancellor:
\r
8275 case BlackPromotionChancellor:
\r
8276 case WhitePromotionArchbishop:
\r
8277 case BlackPromotionArchbishop:
\r
8278 case WhitePromotionCentaur:
\r
8279 case BlackPromotionCentaur:
\r
8280 case WhitePromotionQueen:
\r
8281 case BlackPromotionQueen:
\r
8282 case WhitePromotionRook:
\r
8283 case BlackPromotionRook:
\r
8284 case WhitePromotionBishop:
\r
8285 case BlackPromotionBishop:
\r
8286 case WhitePromotionKnight:
\r
8287 case BlackPromotionKnight:
\r
8288 case WhitePromotionKing:
\r
8289 case BlackPromotionKing:
\r
8291 case WhiteKingSideCastle:
\r
8292 case WhiteQueenSideCastle:
\r
8293 case BlackKingSideCastle:
\r
8294 case BlackQueenSideCastle:
\r
8295 case WhiteKingSideCastleWild:
\r
8296 case WhiteQueenSideCastleWild:
\r
8297 case BlackKingSideCastleWild:
\r
8298 case BlackQueenSideCastleWild:
\r
8300 case WhiteHSideCastleFR:
\r
8301 case WhiteASideCastleFR:
\r
8302 case BlackHSideCastleFR:
\r
8303 case BlackASideCastleFR:
\r
8305 if (appData.debugMode)
\r
8306 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8307 fromX = currentMoveString[0] - AAA;
\r
8308 fromY = currentMoveString[1] - ONE;
\r
8309 toX = currentMoveString[2] - AAA;
\r
8310 toY = currentMoveString[3] - ONE;
\r
8311 promoChar = currentMoveString[4];
\r
8316 if (appData.debugMode)
\r
8317 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8318 fromX = moveType == WhiteDrop ?
\r
8319 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8320 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8321 fromY = DROP_RANK;
\r
8322 toX = currentMoveString[2] - AAA;
\r
8323 toY = currentMoveString[3] - ONE;
\r
8329 case GameUnfinished:
\r
8330 if (appData.debugMode)
\r
8331 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8332 p = strchr(yy_text, '{');
\r
8333 if (p == NULL) p = strchr(yy_text, '(');
\r
8336 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8338 q = strchr(p, *p == '{' ? '}' : ')');
\r
8339 if (q != NULL) *q = NULLCHAR;
\r
8342 GameEnds(moveType, p, GE_FILE);
\r
8344 if (cmailMsgLoaded) {
\r
8345 ClearHighlights();
\r
8346 flipView = WhiteOnMove(currentMove);
\r
8347 if (moveType == GameUnfinished) flipView = !flipView;
\r
8348 if (appData.debugMode)
\r
8349 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8353 case (ChessMove) 0: /* end of file */
\r
8354 if (appData.debugMode)
\r
8355 fprintf(debugFP, "Parser hit end of file\n");
\r
8356 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8357 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8361 case MT_CHECKMATE:
\r
8362 if (WhiteOnMove(currentMove)) {
\r
8363 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8365 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8368 case MT_STALEMATE:
\r
8369 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8375 case MoveNumberOne:
\r
8376 if (lastLoadGameStart == GNUChessGame) {
\r
8377 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8378 if (appData.debugMode)
\r
8379 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8380 yy_text, (int) moveType);
\r
8381 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8383 /* else fall thru */
\r
8386 case GNUChessGame:
\r
8388 /* Reached start of next game in file */
\r
8389 if (appData.debugMode)
\r
8390 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8391 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8392 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8396 case MT_CHECKMATE:
\r
8397 if (WhiteOnMove(currentMove)) {
\r
8398 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8400 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8403 case MT_STALEMATE:
\r
8404 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8410 case PositionDiagram: /* should not happen; ignore */
\r
8411 case ElapsedTime: /* ignore */
\r
8412 case NAG: /* ignore */
\r
8413 if (appData.debugMode)
\r
8414 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8415 yy_text, (int) moveType);
\r
8416 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8419 if (appData.testLegality) {
\r
8420 if (appData.debugMode)
\r
8421 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8422 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8423 (forwardMostMove / 2) + 1,
\r
8424 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8425 DisplayError(move, 0);
\r
8428 if (appData.debugMode)
\r
8429 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8430 yy_text, currentMoveString);
\r
8431 fromX = currentMoveString[0] - AAA;
\r
8432 fromY = currentMoveString[1] - ONE;
\r
8433 toX = currentMoveString[2] - AAA;
\r
8434 toY = currentMoveString[3] - ONE;
\r
8435 promoChar = currentMoveString[4];
\r
8439 case AmbiguousMove:
\r
8440 if (appData.debugMode)
\r
8441 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8442 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8443 (forwardMostMove / 2) + 1,
\r
8444 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8445 DisplayError(move, 0);
\r
8450 case ImpossibleMove:
\r
8451 if (appData.debugMode)
\r
8452 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, 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
8462 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8463 DrawPosition(FALSE, boards[currentMove]);
\r
8464 DisplayBothClocks();
\r
8465 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8466 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8468 (void) StopLoadGameTimer();
\r
8469 gameFileFP = NULL;
\r
8470 cmailOldMove = forwardMostMove;
\r
8473 /* currentMoveString is set as a side-effect of yylex */
\r
8474 strcat(currentMoveString, "\n");
\r
8475 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8477 thinkOutput[0] = NULLCHAR;
\r
8478 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8479 currentMove = forwardMostMove;
\r
8484 /* Load the nth game from the given file */
\r
8486 LoadGameFromFile(filename, n, title, useList)
\r
8490 /*Boolean*/ int useList;
\r
8493 char buf[MSG_SIZ];
\r
8495 if (strcmp(filename, "-") == 0) {
\r
8499 f = fopen(filename, "rb");
\r
8501 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8502 DisplayError(buf, errno);
\r
8506 if (fseek(f, 0, 0) == -1) {
\r
8507 /* f is not seekable; probably a pipe */
\r
8510 if (useList && n == 0) {
\r
8511 int error = GameListBuild(f);
\r
8513 DisplayError(_("Cannot build game list"), error);
\r
8514 } else if (!ListEmpty(&gameList) &&
\r
8515 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8516 GameListPopUp(f, title);
\r
8519 GameListDestroy();
\r
8522 if (n == 0) n = 1;
\r
8523 return LoadGame(f, n, title, FALSE);
\r
8528 MakeRegisteredMove()
\r
8530 int fromX, fromY, toX, toY;
\r
8532 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8533 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8536 if (appData.debugMode)
\r
8537 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8538 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8540 thinkOutput[0] = NULLCHAR;
\r
8541 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8542 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8543 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8544 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8545 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8546 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8547 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8548 ShowMove(fromX, fromY, toX, toY);
\r
8550 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8551 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8556 case MT_CHECKMATE:
\r
8557 if (WhiteOnMove(currentMove)) {
\r
8558 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8560 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8564 case MT_STALEMATE:
\r
8565 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8571 case CMAIL_RESIGN:
\r
8572 if (WhiteOnMove(currentMove)) {
\r
8573 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8575 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8579 case CMAIL_ACCEPT:
\r
8580 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8591 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8593 CmailLoadGame(f, gameNumber, title, useList)
\r
8601 if (gameNumber > nCmailGames) {
\r
8602 DisplayError(_("No more games in this message"), 0);
\r
8605 if (f == lastLoadGameFP) {
\r
8606 int offset = gameNumber - lastLoadGameNumber;
\r
8607 if (offset == 0) {
\r
8608 cmailMsg[0] = NULLCHAR;
\r
8609 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8610 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8611 nCmailMovesRegistered--;
\r
8613 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8614 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8615 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8618 if (! RegisterMove()) return FALSE;
\r
8622 retVal = LoadGame(f, gameNumber, title, useList);
\r
8624 /* Make move registered during previous look at this game, if any */
\r
8625 MakeRegisteredMove();
\r
8627 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8628 commentList[currentMove]
\r
8629 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8630 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8636 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8638 ReloadGame(offset)
\r
8641 int gameNumber = lastLoadGameNumber + offset;
\r
8642 if (lastLoadGameFP == NULL) {
\r
8643 DisplayError(_("No game has been loaded yet"), 0);
\r
8646 if (gameNumber <= 0) {
\r
8647 DisplayError(_("Can't back up any further"), 0);
\r
8650 if (cmailMsgLoaded) {
\r
8651 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8652 lastLoadGameTitle, lastLoadGameUseList);
\r
8654 return LoadGame(lastLoadGameFP, gameNumber,
\r
8655 lastLoadGameTitle, lastLoadGameUseList);
\r
8661 /* Load the nth game from open file f */
\r
8663 LoadGame(f, gameNumber, title, useList)
\r
8670 char buf[MSG_SIZ];
\r
8671 int gn = gameNumber;
\r
8672 ListGame *lg = NULL;
\r
8673 int numPGNTags = 0;
\r
8675 GameMode oldGameMode;
\r
8676 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8678 if (appData.debugMode)
\r
8679 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8681 if (gameMode == Training )
\r
8682 SetTrainingModeOff();
\r
8684 oldGameMode = gameMode;
\r
8685 if (gameMode != BeginningOfGame) {
\r
8686 Reset(FALSE, TRUE);
\r
8690 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8691 fclose(lastLoadGameFP);
\r
8695 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8698 fseek(f, lg->offset, 0);
\r
8699 GameListHighlight(gameNumber);
\r
8703 DisplayError(_("Game number out of range"), 0);
\r
8707 GameListDestroy();
\r
8708 if (fseek(f, 0, 0) == -1) {
\r
8709 if (f == lastLoadGameFP ?
\r
8710 gameNumber == lastLoadGameNumber + 1 :
\r
8711 gameNumber == 1) {
\r
8714 DisplayError(_("Can't seek on game file"), 0);
\r
8719 lastLoadGameFP = f;
\r
8720 lastLoadGameNumber = gameNumber;
\r
8721 strcpy(lastLoadGameTitle, title);
\r
8722 lastLoadGameUseList = useList;
\r
8726 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8727 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8728 lg->gameInfo.black);
\r
8729 DisplayTitle(buf);
\r
8730 } else if (*title != NULLCHAR) {
\r
8731 if (gameNumber > 1) {
\r
8732 sprintf(buf, "%s %d", title, gameNumber);
\r
8733 DisplayTitle(buf);
\r
8735 DisplayTitle(title);
\r
8739 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8740 gameMode = PlayFromGameFile;
\r
8744 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8745 CopyBoard(boards[0], initialPosition);
\r
8749 * Skip the first gn-1 games in the file.
\r
8750 * Also skip over anything that precedes an identifiable
\r
8751 * start of game marker, to avoid being confused by
\r
8752 * garbage at the start of the file. Currently
\r
8753 * recognized start of game markers are the move number "1",
\r
8754 * the pattern "gnuchess .* game", the pattern
\r
8755 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8756 * A game that starts with one of the latter two patterns
\r
8757 * will also have a move number 1, possibly
\r
8758 * following a position diagram.
\r
8759 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8760 * start with an unnumbered move. Does that break anything?
\r
8762 cm = lastLoadGameStart = (ChessMove) 0;
\r
8764 yyboardindex = forwardMostMove;
\r
8765 cm = (ChessMove) yylex();
\r
8767 case (ChessMove) 0:
\r
8768 if (cmailMsgLoaded) {
\r
8769 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8771 Reset(TRUE, TRUE);
\r
8772 DisplayError(_("Game not found in file"), 0);
\r
8776 case GNUChessGame:
\r
8779 lastLoadGameStart = cm;
\r
8782 case MoveNumberOne:
\r
8783 switch (lastLoadGameStart) {
\r
8784 case GNUChessGame:
\r
8788 case MoveNumberOne:
\r
8789 case (ChessMove) 0:
\r
8790 gn--; /* count this game */
\r
8791 lastLoadGameStart = cm;
\r
8800 switch (lastLoadGameStart) {
\r
8801 case GNUChessGame:
\r
8803 case MoveNumberOne:
\r
8804 case (ChessMove) 0:
\r
8805 gn--; /* count this game */
\r
8806 lastLoadGameStart = cm;
\r
8809 lastLoadGameStart = cm; /* game counted already */
\r
8817 yyboardindex = forwardMostMove;
\r
8818 cm = (ChessMove) yylex();
\r
8819 } while (cm == PGNTag || cm == Comment);
\r
8826 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8827 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8828 != CMAIL_OLD_RESULT) {
\r
8829 nCmailResults ++ ;
\r
8830 cmailResult[ CMAIL_MAX_GAMES
\r
8831 - gn - 1] = CMAIL_OLD_RESULT;
\r
8837 /* Only a NormalMove can be at the start of a game
\r
8838 * without a position diagram. */
\r
8839 if (lastLoadGameStart == (ChessMove) 0) {
\r
8841 lastLoadGameStart = MoveNumberOne;
\r
8850 if (appData.debugMode)
\r
8851 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8853 if (cm == XBoardGame) {
\r
8854 /* Skip any header junk before position diagram and/or move 1 */
\r
8856 yyboardindex = forwardMostMove;
\r
8857 cm = (ChessMove) yylex();
\r
8859 if (cm == (ChessMove) 0 ||
\r
8860 cm == GNUChessGame || cm == XBoardGame) {
\r
8861 /* Empty game; pretend end-of-file and handle later */
\r
8862 cm = (ChessMove) 0;
\r
8866 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8867 cm == PGNTag || cm == Comment)
\r
8870 } else if (cm == GNUChessGame) {
\r
8871 if (gameInfo.event != NULL) {
\r
8872 free(gameInfo.event);
\r
8874 gameInfo.event = StrSave(yy_text);
\r
8877 startedFromSetupPosition = FALSE;
\r
8878 while (cm == PGNTag) {
\r
8879 if (appData.debugMode)
\r
8880 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8881 err = ParsePGNTag(yy_text, &gameInfo);
\r
8882 if (!err) numPGNTags++;
\r
8884 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8885 if(gameInfo.variant != oldVariant) {
\r
8886 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8887 InitPosition(TRUE);
\r
8888 oldVariant = gameInfo.variant;
\r
8889 if (appData.debugMode)
\r
8890 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8894 if (gameInfo.fen != NULL) {
\r
8895 Board initial_position;
\r
8896 startedFromSetupPosition = TRUE;
\r
8897 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8898 Reset(TRUE, TRUE);
\r
8899 DisplayError(_("Bad FEN position in file"), 0);
\r
8902 CopyBoard(boards[0], initial_position);
\r
8903 if (blackPlaysFirst) {
\r
8904 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8905 CopyBoard(boards[1], initial_position);
\r
8906 strcpy(moveList[0], "");
\r
8907 strcpy(parseList[0], "");
\r
8908 timeRemaining[0][1] = whiteTimeRemaining;
\r
8909 timeRemaining[1][1] = blackTimeRemaining;
\r
8910 if (commentList[0] != NULL) {
\r
8911 commentList[1] = commentList[0];
\r
8912 commentList[0] = NULL;
\r
8915 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8917 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8919 initialRulePlies = FENrulePlies;
\r
8920 epStatus[forwardMostMove] = FENepStatus;
\r
8921 for( i=0; i< nrCastlingRights; i++ )
\r
8922 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8924 yyboardindex = forwardMostMove;
\r
8925 free(gameInfo.fen);
\r
8926 gameInfo.fen = NULL;
\r
8929 yyboardindex = forwardMostMove;
\r
8930 cm = (ChessMove) yylex();
\r
8932 /* Handle comments interspersed among the tags */
\r
8933 while (cm == Comment) {
\r
8935 if (appData.debugMode)
\r
8936 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8938 if (*p == '{' || *p == '[' || *p == '(') {
\r
8939 p[strlen(p) - 1] = NULLCHAR;
\r
8942 while (*p == '\n') p++;
\r
8943 AppendComment(currentMove, p);
\r
8944 yyboardindex = forwardMostMove;
\r
8945 cm = (ChessMove) yylex();
\r
8949 /* don't rely on existence of Event tag since if game was
\r
8950 * pasted from clipboard the Event tag may not exist
\r
8952 if (numPGNTags > 0){
\r
8954 if (gameInfo.variant == VariantNormal) {
\r
8955 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8958 if( appData.autoDisplayTags ) {
\r
8959 tags = PGNTags(&gameInfo);
\r
8960 TagsPopUp(tags, CmailMsg());
\r
8965 /* Make something up, but don't display it now */
\r
8970 if (cm == PositionDiagram) {
\r
8973 Board initial_position;
\r
8975 if (appData.debugMode)
\r
8976 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8978 if (!startedFromSetupPosition) {
\r
8980 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8981 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8991 initial_position[i][j++] = CharToPiece(*p);
\r
8994 while (*p == ' ' || *p == '\t' ||
\r
8995 *p == '\n' || *p == '\r') p++;
\r
8997 if (strncmp(p, "black", strlen("black"))==0)
\r
8998 blackPlaysFirst = TRUE;
\r
9000 blackPlaysFirst = FALSE;
\r
9001 startedFromSetupPosition = TRUE;
\r
9003 CopyBoard(boards[0], initial_position);
\r
9004 if (blackPlaysFirst) {
\r
9005 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9006 CopyBoard(boards[1], initial_position);
\r
9007 strcpy(moveList[0], "");
\r
9008 strcpy(parseList[0], "");
\r
9009 timeRemaining[0][1] = whiteTimeRemaining;
\r
9010 timeRemaining[1][1] = blackTimeRemaining;
\r
9011 if (commentList[0] != NULL) {
\r
9012 commentList[1] = commentList[0];
\r
9013 commentList[0] = NULL;
\r
9016 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9019 yyboardindex = forwardMostMove;
\r
9020 cm = (ChessMove) yylex();
\r
9023 if (first.pr == NoProc) {
\r
9024 StartChessProgram(&first);
\r
9026 InitChessProgram(&first, FALSE);
\r
9027 SendToProgram("force\n", &first);
\r
9028 if (startedFromSetupPosition) {
\r
9029 SendBoard(&first, forwardMostMove);
\r
9030 if (appData.debugMode) {
\r
9031 fprintf(debugFP, "Load Game\n");
\r
9033 DisplayBothClocks();
\r
9036 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9037 loadFlag = appData.suppressLoadMoves;
\r
9039 while (cm == Comment) {
\r
9041 if (appData.debugMode)
\r
9042 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9044 if (*p == '{' || *p == '[' || *p == '(') {
\r
9045 p[strlen(p) - 1] = NULLCHAR;
\r
9048 while (*p == '\n') p++;
\r
9049 AppendComment(currentMove, p);
\r
9050 yyboardindex = forwardMostMove;
\r
9051 cm = (ChessMove) yylex();
\r
9054 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9055 cm == WhiteWins || cm == BlackWins ||
\r
9056 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9057 DisplayMessage("", _("No moves in game"));
\r
9058 if (cmailMsgLoaded) {
\r
9059 if (appData.debugMode)
\r
9060 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9061 ClearHighlights();
\r
9064 DrawPosition(FALSE, boards[currentMove]);
\r
9065 DisplayBothClocks();
\r
9066 gameMode = EditGame;
\r
9068 gameFileFP = NULL;
\r
9073 // [HGM] PV info: routine tests if comment empty
\r
9074 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9075 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9077 if (!matchMode && appData.timeDelay != 0)
\r
9078 DrawPosition(FALSE, boards[currentMove]);
\r
9080 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9081 programStats.ok_to_send = 1;
\r
9084 /* if the first token after the PGN tags is a move
\r
9085 * and not move number 1, retrieve it from the parser
\r
9087 if (cm != MoveNumberOne)
\r
9088 LoadGameOneMove(cm);
\r
9090 /* load the remaining moves from the file */
\r
9091 while (LoadGameOneMove((ChessMove)0)) {
\r
9092 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9093 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9096 /* rewind to the start of the game */
\r
9097 currentMove = backwardMostMove;
\r
9099 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9101 if (oldGameMode == AnalyzeFile ||
\r
9102 oldGameMode == AnalyzeMode) {
\r
9103 AnalyzeFileEvent();
\r
9106 if (matchMode || appData.timeDelay == 0) {
\r
9108 gameMode = EditGame;
\r
9110 } else if (appData.timeDelay > 0) {
\r
9111 AutoPlayGameLoop();
\r
9114 if (appData.debugMode)
\r
9115 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9117 loadFlag = 0; /* [HGM] true game starts */
\r
9121 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9123 ReloadPosition(offset)
\r
9126 int positionNumber = lastLoadPositionNumber + offset;
\r
9127 if (lastLoadPositionFP == NULL) {
\r
9128 DisplayError(_("No position has been loaded yet"), 0);
\r
9131 if (positionNumber <= 0) {
\r
9132 DisplayError(_("Can't back up any further"), 0);
\r
9135 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9136 lastLoadPositionTitle);
\r
9139 /* Load the nth position from the given file */
\r
9141 LoadPositionFromFile(filename, n, title)
\r
9147 char buf[MSG_SIZ];
\r
9149 if (strcmp(filename, "-") == 0) {
\r
9150 return LoadPosition(stdin, n, "stdin");
\r
9152 f = fopen(filename, "rb");
\r
9154 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9155 DisplayError(buf, errno);
\r
9158 return LoadPosition(f, n, title);
\r
9163 /* Load the nth position from the given open file, and close it */
\r
9165 LoadPosition(f, positionNumber, title)
\r
9167 int positionNumber;
\r
9170 char *p, line[MSG_SIZ];
\r
9171 Board initial_position;
\r
9172 int i, j, fenMode, pn;
\r
9174 if (gameMode == Training )
\r
9175 SetTrainingModeOff();
\r
9177 if (gameMode != BeginningOfGame) {
\r
9178 Reset(FALSE, TRUE);
\r
9180 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9181 fclose(lastLoadPositionFP);
\r
9183 if (positionNumber == 0) positionNumber = 1;
\r
9184 lastLoadPositionFP = f;
\r
9185 lastLoadPositionNumber = positionNumber;
\r
9186 strcpy(lastLoadPositionTitle, title);
\r
9187 if (first.pr == NoProc) {
\r
9188 StartChessProgram(&first);
\r
9189 InitChessProgram(&first, FALSE);
\r
9191 pn = positionNumber;
\r
9192 if (positionNumber < 0) {
\r
9193 /* Negative position number means to seek to that byte offset */
\r
9194 if (fseek(f, -positionNumber, 0) == -1) {
\r
9195 DisplayError(_("Can't seek on position file"), 0);
\r
9200 if (fseek(f, 0, 0) == -1) {
\r
9201 if (f == lastLoadPositionFP ?
\r
9202 positionNumber == lastLoadPositionNumber + 1 :
\r
9203 positionNumber == 1) {
\r
9206 DisplayError(_("Can't seek on position file"), 0);
\r
9211 /* See if this file is FEN or old-style xboard */
\r
9212 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9213 DisplayError(_("Position not found in file"), 0);
\r
9217 switch (line[0]) {
\r
9218 case '#': case 'x':
\r
9222 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9223 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9224 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9225 case '7': case '8': case '9':
\r
9226 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9227 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9228 case 'C': case 'W': case 'c': case 'w':
\r
9233 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9234 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9238 if (fenMode || line[0] == '#') pn--;
\r
9240 /* skip positions before number pn */
\r
9241 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9242 Reset(TRUE, TRUE);
\r
9243 DisplayError(_("Position not found in file"), 0);
\r
9246 if (fenMode || line[0] == '#') pn--;
\r
9251 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9252 DisplayError(_("Bad FEN position in file"), 0);
\r
9256 (void) fgets(line, MSG_SIZ, f);
\r
9257 (void) fgets(line, MSG_SIZ, f);
\r
9259 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9260 (void) fgets(line, MSG_SIZ, f);
\r
9261 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9264 initial_position[i][j++] = CharToPiece(*p);
\r
9268 blackPlaysFirst = FALSE;
\r
9270 (void) fgets(line, MSG_SIZ, f);
\r
9271 if (strncmp(line, "black", strlen("black"))==0)
\r
9272 blackPlaysFirst = TRUE;
\r
9275 startedFromSetupPosition = TRUE;
\r
9277 SendToProgram("force\n", &first);
\r
9278 CopyBoard(boards[0], initial_position);
\r
9279 if (blackPlaysFirst) {
\r
9280 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9281 strcpy(moveList[0], "");
\r
9282 strcpy(parseList[0], "");
\r
9283 CopyBoard(boards[1], initial_position);
\r
9284 DisplayMessage("", _("Black to play"));
\r
9286 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9287 DisplayMessage("", _("White to play"));
\r
9289 /* [HGM] copy FEN attributes as well */
\r
9291 initialRulePlies = FENrulePlies;
\r
9292 epStatus[forwardMostMove] = FENepStatus;
\r
9293 for( i=0; i< nrCastlingRights; i++ )
\r
9294 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9296 SendBoard(&first, forwardMostMove);
\r
9297 if (appData.debugMode) {
\r
9299 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9300 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9301 fprintf(debugFP, "Load Position\n");
\r
9304 if (positionNumber > 1) {
\r
9305 sprintf(line, "%s %d", title, positionNumber);
\r
9306 DisplayTitle(line);
\r
9308 DisplayTitle(title);
\r
9310 gameMode = EditGame;
\r
9313 timeRemaining[0][1] = whiteTimeRemaining;
\r
9314 timeRemaining[1][1] = blackTimeRemaining;
\r
9315 DrawPosition(FALSE, boards[currentMove]);
\r
9322 CopyPlayerNameIntoFileName(dest, src)
\r
9323 char **dest, *src;
\r
9325 while (*src != NULLCHAR && *src != ',') {
\r
9326 if (*src == ' ') {
\r
9330 *(*dest)++ = *src++;
\r
9335 char *DefaultFileName(ext)
\r
9338 static char def[MSG_SIZ];
\r
9341 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9343 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9345 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9349 def[0] = NULLCHAR;
\r
9354 /* Save the current game to the given file */
\r
9356 SaveGameToFile(filename, append)
\r
9361 char buf[MSG_SIZ];
\r
9363 if (strcmp(filename, "-") == 0) {
\r
9364 return SaveGame(stdout, 0, NULL);
\r
9366 f = fopen(filename, append ? "a" : "w");
\r
9368 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9369 DisplayError(buf, errno);
\r
9372 return SaveGame(f, 0, NULL);
\r
9381 static char buf[MSG_SIZ];
\r
9384 p = strchr(str, ' ');
\r
9385 if (p == NULL) return str;
\r
9386 strncpy(buf, str, p - str);
\r
9387 buf[p - str] = NULLCHAR;
\r
9391 #define PGN_MAX_LINE 75
\r
9393 #define PGN_SIDE_WHITE 0
\r
9394 #define PGN_SIDE_BLACK 1
\r
9397 static int FindFirstMoveOutOfBook( int side )
\r
9401 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9402 int index = backwardMostMove;
\r
9403 int has_book_hit = 0;
\r
9405 if( (index % 2) != side ) {
\r
9409 while( index < forwardMostMove ) {
\r
9410 /* Check to see if engine is in book */
\r
9411 int depth = pvInfoList[index].depth;
\r
9412 int score = pvInfoList[index].score;
\r
9415 if( depth <= 2 ) {
\r
9418 else if( score == 0 && depth == 63 ) {
\r
9419 in_book = 1; /* Zappa */
\r
9421 else if( score == 2 && depth == 99 ) {
\r
9422 in_book = 1; /* Abrok */
\r
9425 has_book_hit += in_book;
\r
9441 void GetOutOfBookInfo( char * buf )
\r
9445 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9447 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9448 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9452 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9453 for( i=0; i<2; i++ ) {
\r
9457 if( i > 0 && oob[0] >= 0 ) {
\r
9458 strcat( buf, " " );
\r
9461 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9462 sprintf( buf+strlen(buf), "%s%.2f",
\r
9463 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9464 pvInfoList[idx].score / 100.0 );
\r
9470 /* Save game in PGN style and close the file */
\r
9475 int i, offset, linelen, newblock;
\r
9477 // char *movetext;
\r
9479 int movelen, numlen, blank;
\r
9480 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9482 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9484 tm = time((time_t *) NULL);
\r
9486 PrintPGNTags(f, &gameInfo);
\r
9488 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9489 char *fen = PositionToFEN(backwardMostMove, NULL);
\r
9490 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9491 fprintf(f, "\n{--------------\n");
\r
9492 PrintPosition(f, backwardMostMove);
\r
9493 fprintf(f, "--------------}\n");
\r
9497 /* [AS] Out of book annotation */
\r
9498 if( appData.saveOutOfBookInfo ) {
\r
9501 GetOutOfBookInfo( buf );
\r
9503 if( buf[0] != '\0' ) {
\r
9504 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9511 i = backwardMostMove;
\r
9515 while (i < forwardMostMove) {
\r
9516 /* Print comments preceding this move */
\r
9517 if (commentList[i] != NULL) {
\r
9518 if (linelen > 0) fprintf(f, "\n");
\r
9519 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9524 /* Format move number */
\r
9525 if ((i % 2) == 0) {
\r
9526 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9529 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9531 numtext[0] = NULLCHAR;
\r
9534 numlen = strlen(numtext);
\r
9537 /* Print move number */
\r
9538 blank = linelen > 0 && numlen > 0;
\r
9539 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9548 fprintf(f, numtext);
\r
9549 linelen += numlen;
\r
9552 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9555 blank = linelen > 0 && movelen > 0;
\r
9556 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9565 fprintf(f, parseList[i]);
\r
9566 linelen += movelen;
\r
9568 /* [AS] Add PV info if present */
\r
9569 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9570 /* [HGM] add time */
\r
9571 char buf[MSG_SIZ]; int seconds = 0;
\r
9574 if(i >= backwardMostMove) {
\r
9575 if(WhiteOnMove(i))
\r
9576 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9577 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9579 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9580 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9582 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9584 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9587 if( seconds <= 0) buf[0] = 0; else
\r
9588 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9589 seconds = (seconds + 4)/10; // round to full seconds
\r
9590 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9591 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9594 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9595 pvInfoList[i].score >= 0 ? "+" : "",
\r
9596 pvInfoList[i].score / 100.0,
\r
9597 pvInfoList[i].depth,
\r
9600 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9602 /* Print score/depth */
\r
9603 blank = linelen > 0 && movelen > 0;
\r
9604 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9613 fprintf(f, move_buffer);
\r
9614 linelen += movelen;
\r
9620 /* Start a new line */
\r
9621 if (linelen > 0) fprintf(f, "\n");
\r
9623 /* Print comments after last move */
\r
9624 if (commentList[i] != NULL) {
\r
9625 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9628 /* Print result */
\r
9629 if (gameInfo.resultDetails != NULL &&
\r
9630 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9631 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9632 PGNResult(gameInfo.result));
\r
9634 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9641 /* Save game in old style and close the file */
\r
9643 SaveGameOldStyle(f)
\r
9649 tm = time((time_t *) NULL);
\r
9651 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9652 PrintOpponents(f);
\r
9654 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9655 fprintf(f, "\n[--------------\n");
\r
9656 PrintPosition(f, backwardMostMove);
\r
9657 fprintf(f, "--------------]\n");
\r
9662 i = backwardMostMove;
\r
9663 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9665 while (i < forwardMostMove) {
\r
9666 if (commentList[i] != NULL) {
\r
9667 fprintf(f, "[%s]\n", commentList[i]);
\r
9670 if ((i % 2) == 1) {
\r
9671 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9674 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9676 if (commentList[i] != NULL) {
\r
9680 if (i >= forwardMostMove) {
\r
9684 fprintf(f, "%s\n", parseList[i]);
\r
9689 if (commentList[i] != NULL) {
\r
9690 fprintf(f, "[%s]\n", commentList[i]);
\r
9693 /* This isn't really the old style, but it's close enough */
\r
9694 if (gameInfo.resultDetails != NULL &&
\r
9695 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9696 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9697 gameInfo.resultDetails);
\r
9699 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9706 /* Save the current game to open file f and close the file */
\r
9708 SaveGame(f, dummy, dummy2)
\r
9713 if (gameMode == EditPosition) EditPositionDone();
\r
9714 if (appData.oldSaveStyle)
\r
9715 return SaveGameOldStyle(f);
\r
9717 return SaveGamePGN(f);
\r
9720 /* Save the current position to the given file */
\r
9722 SavePositionToFile(filename)
\r
9726 char buf[MSG_SIZ];
\r
9728 if (strcmp(filename, "-") == 0) {
\r
9729 return SavePosition(stdout, 0, NULL);
\r
9731 f = fopen(filename, "a");
\r
9733 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9734 DisplayError(buf, errno);
\r
9737 SavePosition(f, 0, NULL);
\r
9743 /* Save the current position to the given open file and close the file */
\r
9745 SavePosition(f, dummy, dummy2)
\r
9753 if (appData.oldSaveStyle) {
\r
9754 tm = time((time_t *) NULL);
\r
9756 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9757 PrintOpponents(f);
\r
9758 fprintf(f, "[--------------\n");
\r
9759 PrintPosition(f, currentMove);
\r
9760 fprintf(f, "--------------]\n");
\r
9762 fen = PositionToFEN(currentMove, NULL);
\r
9763 fprintf(f, "%s\n", fen);
\r
9771 ReloadCmailMsgEvent(unregister)
\r
9775 static char *inFilename = NULL;
\r
9776 static char *outFilename;
\r
9778 struct stat inbuf, outbuf;
\r
9781 /* Any registered moves are unregistered if unregister is set, */
\r
9782 /* i.e. invoked by the signal handler */
\r
9784 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9785 cmailMoveRegistered[i] = FALSE;
\r
9786 if (cmailCommentList[i] != NULL) {
\r
9787 free(cmailCommentList[i]);
\r
9788 cmailCommentList[i] = NULL;
\r
9791 nCmailMovesRegistered = 0;
\r
9794 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9795 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9797 nCmailResults = 0;
\r
9799 if (inFilename == NULL) {
\r
9800 /* Because the filenames are static they only get malloced once */
\r
9801 /* and they never get freed */
\r
9802 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9803 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9805 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9806 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9809 status = stat(outFilename, &outbuf);
\r
9811 cmailMailedMove = FALSE;
\r
9813 status = stat(inFilename, &inbuf);
\r
9814 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9817 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9818 counts the games, notes how each one terminated, etc.
\r
9820 It would be nice to remove this kludge and instead gather all
\r
9821 the information while building the game list. (And to keep it
\r
9822 in the game list nodes instead of having a bunch of fixed-size
\r
9823 parallel arrays.) Note this will require getting each game's
\r
9824 termination from the PGN tags, as the game list builder does
\r
9825 not process the game moves. --mann
\r
9827 cmailMsgLoaded = TRUE;
\r
9828 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9830 /* Load first game in the file or popup game menu */
\r
9831 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9833 #endif /* !WIN32 */
\r
9841 char string[MSG_SIZ];
\r
9843 if ( cmailMailedMove
\r
9844 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9845 return TRUE; /* Allow free viewing */
\r
9848 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9849 /* with the move registered when the conditions for registering no */
\r
9851 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9852 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9853 nCmailMovesRegistered --;
\r
9855 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9857 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9858 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9862 if (cmailOldMove == -1) {
\r
9863 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9867 if (currentMove > cmailOldMove + 1) {
\r
9868 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9872 if (currentMove < cmailOldMove) {
\r
9873 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9877 if (forwardMostMove > currentMove) {
\r
9878 /* Silently truncate extra moves */
\r
9882 if ( (currentMove == cmailOldMove + 1)
\r
9883 || ( (currentMove == cmailOldMove)
\r
9884 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9885 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9886 if (gameInfo.result != GameUnfinished) {
\r
9887 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9890 if (commentList[currentMove] != NULL) {
\r
9891 cmailCommentList[lastLoadGameNumber - 1]
\r
9892 = StrSave(commentList[currentMove]);
\r
9894 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9896 if (appData.debugMode)
\r
9897 fprintf(debugFP, "Saving %s for game %d\n",
\r
9898 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9901 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9903 f = fopen(string, "w");
\r
9904 if (appData.oldSaveStyle) {
\r
9905 SaveGameOldStyle(f); /* also closes the file */
\r
9907 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9908 f = fopen(string, "w");
\r
9909 SavePosition(f, 0, NULL); /* also closes the file */
\r
9911 fprintf(f, "{--------------\n");
\r
9912 PrintPosition(f, currentMove);
\r
9913 fprintf(f, "--------------}\n\n");
\r
9915 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9918 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9919 nCmailMovesRegistered ++;
\r
9920 } else if (nCmailGames == 1) {
\r
9921 DisplayError(_("You have not made a move yet"), 0);
\r
9932 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9933 FILE *commandOutput;
\r
9934 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9935 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9941 if (! cmailMsgLoaded) {
\r
9942 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9946 if (nCmailGames == nCmailResults) {
\r
9947 DisplayError(_("No unfinished games"), 0);
\r
9951 #if CMAIL_PROHIBIT_REMAIL
\r
9952 if (cmailMailedMove) {
\r
9953 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
9954 DisplayError(msg, 0);
\r
9959 if (! (cmailMailedMove || RegisterMove())) return;
\r
9961 if ( cmailMailedMove
\r
9962 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9963 sprintf(string, partCommandString,
\r
9964 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9965 commandOutput = popen(string, "r");
\r
9967 if (commandOutput == NULL) {
\r
9968 DisplayError(_("Failed to invoke cmail"), 0);
\r
9970 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9971 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9973 if (nBuffers > 1) {
\r
9974 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9975 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9976 nBytes = MSG_SIZ - 1;
\r
9978 (void) memcpy(msg, buffer, nBytes);
\r
9980 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9982 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9983 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9986 for (i = 0; i < nCmailGames; i ++) {
\r
9987 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9992 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9994 sprintf(buffer, "%s/%s.%s.archive",
\r
9996 appData.cmailGameName,
\r
9998 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
9999 cmailMsgLoaded = FALSE;
\r
10003 DisplayInformation(msg);
\r
10004 pclose(commandOutput);
\r
10007 if ((*cmailMsg) != '\0') {
\r
10008 DisplayInformation(cmailMsg);
\r
10013 #endif /* !WIN32 */
\r
10022 int prependComma = 0;
\r
10024 char string[MSG_SIZ]; /* Space for game-list */
\r
10027 if (!cmailMsgLoaded) return "";
\r
10029 if (cmailMailedMove) {
\r
10030 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10032 /* Create a list of games left */
\r
10033 sprintf(string, "[");
\r
10034 for (i = 0; i < nCmailGames; i ++) {
\r
10035 if (! ( cmailMoveRegistered[i]
\r
10036 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10037 if (prependComma) {
\r
10038 sprintf(number, ",%d", i + 1);
\r
10040 sprintf(number, "%d", i + 1);
\r
10041 prependComma = 1;
\r
10044 strcat(string, number);
\r
10047 strcat(string, "]");
\r
10049 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10050 switch (nCmailGames) {
\r
10052 sprintf(cmailMsg,
\r
10053 _("Still need to make move for game\n"));
\r
10057 sprintf(cmailMsg,
\r
10058 _("Still need to make moves for both games\n"));
\r
10062 sprintf(cmailMsg,
\r
10063 _("Still need to make moves for all %d games\n"),
\r
10068 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10070 sprintf(cmailMsg,
\r
10071 _("Still need to make a move for game %s\n"),
\r
10076 if (nCmailResults == nCmailGames) {
\r
10077 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10079 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10084 sprintf(cmailMsg,
\r
10085 _("Still need to make moves for games %s\n"),
\r
10091 #endif /* WIN32 */
\r
10097 if (gameMode == Training)
\r
10098 SetTrainingModeOff();
\r
10100 Reset(TRUE, TRUE);
\r
10101 cmailMsgLoaded = FALSE;
\r
10102 if (appData.icsActive) {
\r
10103 SendToICS(ics_prefix);
\r
10104 SendToICS("refresh\n");
\r
10109 ExitEvent(status)
\r
10113 if (exiting > 2) {
\r
10114 /* Give up on clean exit */
\r
10117 if (exiting > 1) {
\r
10118 /* Keep trying for clean exit */
\r
10122 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10124 if (telnetISR != NULL) {
\r
10125 RemoveInputSource(telnetISR);
\r
10127 if (icsPR != NoProc) {
\r
10128 DestroyChildProcess(icsPR, TRUE);
\r
10131 /* Save game if resource set and not already saved by GameEnds() */
\r
10132 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10133 && forwardMostMove > 0) {
\r
10134 if (*appData.saveGameFile != NULLCHAR) {
\r
10135 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10136 } else if (appData.autoSaveGames) {
\r
10139 if (*appData.savePositionFile != NULLCHAR) {
\r
10140 SavePositionToFile(appData.savePositionFile);
\r
10143 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10145 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10146 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10148 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10149 /* make sure this other one finishes before killing it! */
\r
10150 if(endingGame) { int count = 0;
\r
10151 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10152 while(endingGame && count++ < 10) DoSleep(1);
\r
10153 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10156 /* Kill off chess programs */
\r
10157 if (first.pr != NoProc) {
\r
10158 ExitAnalyzeMode();
\r
10160 DoSleep( appData.delayBeforeQuit );
\r
10161 SendToProgram("quit\n", &first);
\r
10162 DoSleep( appData.delayAfterQuit );
\r
10163 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10165 if (second.pr != NoProc) {
\r
10166 DoSleep( appData.delayBeforeQuit );
\r
10167 SendToProgram("quit\n", &second);
\r
10168 DoSleep( appData.delayAfterQuit );
\r
10169 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10171 if (first.isr != NULL) {
\r
10172 RemoveInputSource(first.isr);
\r
10174 if (second.isr != NULL) {
\r
10175 RemoveInputSource(second.isr);
\r
10178 ShutDownFrontEnd();
\r
10185 if (appData.debugMode)
\r
10186 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10190 if (gameMode == MachinePlaysWhite ||
\r
10191 gameMode == MachinePlaysBlack) {
\r
10194 DisplayBothClocks();
\r
10196 if (gameMode == PlayFromGameFile) {
\r
10197 if (appData.timeDelay >= 0)
\r
10198 AutoPlayGameLoop();
\r
10199 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10200 Reset(FALSE, TRUE);
\r
10201 SendToICS(ics_prefix);
\r
10202 SendToICS("refresh\n");
\r
10203 } else if (currentMove < forwardMostMove) {
\r
10204 ForwardInner(forwardMostMove);
\r
10206 pauseExamInvalid = FALSE;
\r
10208 switch (gameMode) {
\r
10211 case IcsExamining:
\r
10212 pauseExamForwardMostMove = forwardMostMove;
\r
10213 pauseExamInvalid = FALSE;
\r
10214 /* fall through */
\r
10215 case IcsObserving:
\r
10216 case IcsPlayingWhite:
\r
10217 case IcsPlayingBlack:
\r
10221 case PlayFromGameFile:
\r
10222 (void) StopLoadGameTimer();
\r
10226 case BeginningOfGame:
\r
10227 if (appData.icsActive) return;
\r
10228 /* else fall through */
\r
10229 case MachinePlaysWhite:
\r
10230 case MachinePlaysBlack:
\r
10231 case TwoMachinesPlay:
\r
10232 if (forwardMostMove == 0)
\r
10233 return; /* don't pause if no one has moved */
\r
10234 if ((gameMode == MachinePlaysWhite &&
\r
10235 !WhiteOnMove(forwardMostMove)) ||
\r
10236 (gameMode == MachinePlaysBlack &&
\r
10237 WhiteOnMove(forwardMostMove))) {
\r
10248 EditCommentEvent()
\r
10250 char title[MSG_SIZ];
\r
10252 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10253 strcpy(title, _("Edit comment"));
\r
10255 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10256 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10257 parseList[currentMove - 1]);
\r
10260 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10267 char *tags = PGNTags(&gameInfo);
\r
10268 EditTagsPopUp(tags);
\r
10273 AnalyzeModeEvent()
\r
10275 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10278 if (gameMode != AnalyzeFile) {
\r
10279 if (!appData.icsEngineAnalyze) {
\r
10281 if (gameMode != EditGame) return;
\r
10283 ResurrectChessProgram();
\r
10284 SendToProgram("analyze\n", &first);
\r
10285 first.analyzing = TRUE;
\r
10286 /*first.maybeThinking = TRUE;*/
\r
10287 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10288 AnalysisPopUp(_("Analysis"),
\r
10289 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10291 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10296 StartAnalysisClock();
\r
10297 GetTimeMark(&lastNodeCountTime);
\r
10298 lastNodeCount = 0;
\r
10302 AnalyzeFileEvent()
\r
10304 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10307 if (gameMode != AnalyzeMode) {
\r
10309 if (gameMode != EditGame) return;
\r
10310 ResurrectChessProgram();
\r
10311 SendToProgram("analyze\n", &first);
\r
10312 first.analyzing = TRUE;
\r
10313 /*first.maybeThinking = TRUE;*/
\r
10314 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10315 AnalysisPopUp(_("Analysis"),
\r
10316 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10318 gameMode = AnalyzeFile;
\r
10323 StartAnalysisClock();
\r
10324 GetTimeMark(&lastNodeCountTime);
\r
10325 lastNodeCount = 0;
\r
10329 MachineWhiteEvent()
\r
10331 char buf[MSG_SIZ];
\r
10332 char *bookHit = NULL;
\r
10334 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10338 if (gameMode == PlayFromGameFile ||
\r
10339 gameMode == TwoMachinesPlay ||
\r
10340 gameMode == Training ||
\r
10341 gameMode == AnalyzeMode ||
\r
10342 gameMode == EndOfGame)
\r
10345 if (gameMode == EditPosition)
\r
10346 EditPositionDone();
\r
10348 if (!WhiteOnMove(currentMove)) {
\r
10349 DisplayError(_("It is not White's turn"), 0);
\r
10353 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10354 ExitAnalyzeMode();
\r
10356 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10357 gameMode == AnalyzeFile)
\r
10360 ResurrectChessProgram(); /* in case it isn't running */
\r
10361 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10362 gameMode = MachinePlaysWhite;
\r
10365 gameMode = MachinePlaysWhite;
\r
10369 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10370 DisplayTitle(buf);
\r
10371 if (first.sendName) {
\r
10372 sprintf(buf, "name %s\n", gameInfo.black);
\r
10373 SendToProgram(buf, &first);
\r
10375 if (first.sendTime) {
\r
10376 if (first.useColors) {
\r
10377 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10379 SendTimeRemaining(&first, TRUE);
\r
10381 if (first.useColors) {
\r
10382 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10384 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10385 SetMachineThinkingEnables();
\r
10386 first.maybeThinking = TRUE;
\r
10389 if (appData.autoFlipView && !flipView) {
\r
10390 flipView = !flipView;
\r
10391 DrawPosition(FALSE, NULL);
\r
10392 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10395 if(bookHit) { // [HGM] book: simulate book reply
\r
10396 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10398 programStats.nodes = programStats.depth = programStats.time =
\r
10399 programStats.score = programStats.got_only_move = 0;
\r
10400 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10402 strcpy(bookMove, "move ");
\r
10403 strcat(bookMove, bookHit);
\r
10404 HandleMachineMove(bookMove, &first);
\r
10409 MachineBlackEvent()
\r
10411 char buf[MSG_SIZ];
\r
10412 char *bookHit = NULL;
\r
10414 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10418 if (gameMode == PlayFromGameFile ||
\r
10419 gameMode == TwoMachinesPlay ||
\r
10420 gameMode == Training ||
\r
10421 gameMode == AnalyzeMode ||
\r
10422 gameMode == EndOfGame)
\r
10425 if (gameMode == EditPosition)
\r
10426 EditPositionDone();
\r
10428 if (WhiteOnMove(currentMove)) {
\r
10429 DisplayError(_("It is not Black's turn"), 0);
\r
10433 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10434 ExitAnalyzeMode();
\r
10436 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10437 gameMode == AnalyzeFile)
\r
10440 ResurrectChessProgram(); /* in case it isn't running */
\r
10441 gameMode = MachinePlaysBlack;
\r
10445 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10446 DisplayTitle(buf);
\r
10447 if (first.sendName) {
\r
10448 sprintf(buf, "name %s\n", gameInfo.white);
\r
10449 SendToProgram(buf, &first);
\r
10451 if (first.sendTime) {
\r
10452 if (first.useColors) {
\r
10453 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10455 SendTimeRemaining(&first, FALSE);
\r
10457 if (first.useColors) {
\r
10458 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10460 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10461 SetMachineThinkingEnables();
\r
10462 first.maybeThinking = TRUE;
\r
10465 if (appData.autoFlipView && flipView) {
\r
10466 flipView = !flipView;
\r
10467 DrawPosition(FALSE, NULL);
\r
10468 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10470 if(bookHit) { // [HGM] book: simulate book reply
\r
10471 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10473 programStats.nodes = programStats.depth = programStats.time =
\r
10474 programStats.score = programStats.got_only_move = 0;
\r
10475 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10477 strcpy(bookMove, "move ");
\r
10478 strcat(bookMove, bookHit);
\r
10479 HandleMachineMove(bookMove, &first);
\r
10485 DisplayTwoMachinesTitle()
\r
10487 char buf[MSG_SIZ];
\r
10488 if (appData.matchGames > 0) {
\r
10489 if (first.twoMachinesColor[0] == 'w') {
\r
10490 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10491 gameInfo.white, gameInfo.black,
\r
10492 first.matchWins, second.matchWins,
\r
10493 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10495 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10496 gameInfo.white, gameInfo.black,
\r
10497 second.matchWins, first.matchWins,
\r
10498 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10501 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10503 DisplayTitle(buf);
\r
10507 TwoMachinesEvent P((void))
\r
10510 char buf[MSG_SIZ];
\r
10511 ChessProgramState *onmove;
\r
10512 char *bookHit = NULL;
\r
10514 if (appData.noChessProgram) return;
\r
10516 switch (gameMode) {
\r
10517 case TwoMachinesPlay:
\r
10519 case MachinePlaysWhite:
\r
10520 case MachinePlaysBlack:
\r
10521 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10522 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10525 /* fall through */
\r
10526 case BeginningOfGame:
\r
10527 case PlayFromGameFile:
\r
10530 if (gameMode != EditGame) return;
\r
10532 case EditPosition:
\r
10533 EditPositionDone();
\r
10535 case AnalyzeMode:
\r
10536 case AnalyzeFile:
\r
10537 ExitAnalyzeMode();
\r
10544 forwardMostMove = currentMove;
\r
10545 ResurrectChessProgram(); /* in case first program isn't running */
\r
10547 if (second.pr == NULL) {
\r
10548 StartChessProgram(&second);
\r
10549 if (second.protocolVersion == 1) {
\r
10550 TwoMachinesEventIfReady();
\r
10552 /* kludge: allow timeout for initial "feature" command */
\r
10554 DisplayMessage("", _("Starting second chess program"));
\r
10555 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10559 DisplayMessage("", "");
\r
10560 InitChessProgram(&second, FALSE);
\r
10561 SendToProgram("force\n", &second);
\r
10562 if (startedFromSetupPosition) {
\r
10563 SendBoard(&second, backwardMostMove);
\r
10564 if (appData.debugMode) {
\r
10565 fprintf(debugFP, "Two Machines\n");
\r
10568 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10569 SendMoveToProgram(i, &second);
\r
10572 gameMode = TwoMachinesPlay;
\r
10576 DisplayTwoMachinesTitle();
\r
10577 firstMove = TRUE;
\r
10578 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10581 onmove = &second;
\r
10584 SendToProgram(first.computerString, &first);
\r
10585 if (first.sendName) {
\r
10586 sprintf(buf, "name %s\n", second.tidy);
\r
10587 SendToProgram(buf, &first);
\r
10589 SendToProgram(second.computerString, &second);
\r
10590 if (second.sendName) {
\r
10591 sprintf(buf, "name %s\n", first.tidy);
\r
10592 SendToProgram(buf, &second);
\r
10596 if (!first.sendTime || !second.sendTime) {
\r
10597 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10598 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10600 if (onmove->sendTime) {
\r
10601 if (onmove->useColors) {
\r
10602 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10604 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10606 if (onmove->useColors) {
\r
10607 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10609 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10610 // SendToProgram("go\n", onmove);
\r
10611 onmove->maybeThinking = TRUE;
\r
10612 SetMachineThinkingEnables();
\r
10616 if(bookHit) { // [HGM] book: simulate book reply
\r
10617 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10619 programStats.nodes = programStats.depth = programStats.time =
\r
10620 programStats.score = programStats.got_only_move = 0;
\r
10621 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10623 strcpy(bookMove, "move ");
\r
10624 strcat(bookMove, bookHit);
\r
10625 HandleMachineMove(bookMove, &first);
\r
10632 if (gameMode == Training) {
\r
10633 SetTrainingModeOff();
\r
10634 gameMode = PlayFromGameFile;
\r
10635 DisplayMessage("", _("Training mode off"));
\r
10637 gameMode = Training;
\r
10638 animateTraining = appData.animate;
\r
10640 /* make sure we are not already at the end of the game */
\r
10641 if (currentMove < forwardMostMove) {
\r
10642 SetTrainingModeOn();
\r
10643 DisplayMessage("", _("Training mode on"));
\r
10645 gameMode = PlayFromGameFile;
\r
10646 DisplayError(_("Already at end of game"), 0);
\r
10655 if (!appData.icsActive) return;
\r
10656 switch (gameMode) {
\r
10657 case IcsPlayingWhite:
\r
10658 case IcsPlayingBlack:
\r
10659 case IcsObserving:
\r
10661 case BeginningOfGame:
\r
10662 case IcsExamining:
\r
10668 case EditPosition:
\r
10669 EditPositionDone();
\r
10672 case AnalyzeMode:
\r
10673 case AnalyzeFile:
\r
10674 ExitAnalyzeMode();
\r
10682 gameMode = IcsIdle;
\r
10693 switch (gameMode) {
\r
10695 SetTrainingModeOff();
\r
10697 case MachinePlaysWhite:
\r
10698 case MachinePlaysBlack:
\r
10699 case BeginningOfGame:
\r
10700 SendToProgram("force\n", &first);
\r
10701 SetUserThinkingEnables();
\r
10703 case PlayFromGameFile:
\r
10704 (void) StopLoadGameTimer();
\r
10705 if (gameFileFP != NULL) {
\r
10706 gameFileFP = NULL;
\r
10709 case EditPosition:
\r
10710 EditPositionDone();
\r
10712 case AnalyzeMode:
\r
10713 case AnalyzeFile:
\r
10714 ExitAnalyzeMode();
\r
10715 SendToProgram("force\n", &first);
\r
10717 case TwoMachinesPlay:
\r
10718 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10719 ResurrectChessProgram();
\r
10720 SetUserThinkingEnables();
\r
10723 ResurrectChessProgram();
\r
10725 case IcsPlayingBlack:
\r
10726 case IcsPlayingWhite:
\r
10727 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10729 case IcsObserving:
\r
10730 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10732 case IcsExamining:
\r
10733 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10744 first.offeredDraw = second.offeredDraw = 0;
\r
10746 if (gameMode == PlayFromGameFile) {
\r
10747 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10748 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10749 DisplayTitle("");
\r
10752 if (gameMode == MachinePlaysWhite ||
\r
10753 gameMode == MachinePlaysBlack ||
\r
10754 gameMode == TwoMachinesPlay ||
\r
10755 gameMode == EndOfGame) {
\r
10756 i = forwardMostMove;
\r
10757 while (i > currentMove) {
\r
10758 SendToProgram("undo\n", &first);
\r
10761 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10762 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10763 DisplayBothClocks();
\r
10764 if (whiteFlag || blackFlag) {
\r
10765 whiteFlag = blackFlag = 0;
\r
10767 DisplayTitle("");
\r
10770 gameMode = EditGame;
\r
10777 EditPositionEvent()
\r
10779 if (gameMode == EditPosition) {
\r
10785 if (gameMode != EditGame) return;
\r
10787 gameMode = EditPosition;
\r
10790 if (currentMove > 0)
\r
10791 CopyBoard(boards[0], boards[currentMove]);
\r
10793 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10795 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10796 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10801 ExitAnalyzeMode()
\r
10803 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10804 if (appData.icsEngineAnalyze) {
\r
10805 appData.icsEngineAnalyze = FALSE;
\r
10807 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10809 if (first.analysisSupport && first.analyzing) {
\r
10810 SendToProgram("exit\n", &first);
\r
10811 first.analyzing = FALSE;
\r
10813 AnalysisPopDown();
\r
10814 thinkOutput[0] = NULLCHAR;
\r
10818 EditPositionDone()
\r
10820 startedFromSetupPosition = TRUE;
\r
10821 InitChessProgram(&first, FALSE);
\r
10822 SendToProgram("force\n", &first);
\r
10823 if (blackPlaysFirst) {
\r
10824 strcpy(moveList[0], "");
\r
10825 strcpy(parseList[0], "");
\r
10826 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10827 CopyBoard(boards[1], boards[0]);
\r
10828 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10830 epStatus[1] = epStatus[0];
\r
10831 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10834 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10836 SendBoard(&first, forwardMostMove);
\r
10837 if (appData.debugMode) {
\r
10838 fprintf(debugFP, "EditPosDone\n");
\r
10840 DisplayTitle("");
\r
10841 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10842 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10843 gameMode = EditGame;
\r
10845 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10846 ClearHighlights(); /* [AS] */
\r
10849 /* Pause for `ms' milliseconds */
\r
10850 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10857 GetTimeMark(&m1);
\r
10859 GetTimeMark(&m2);
\r
10860 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10863 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10865 SendMultiLineToICS(buf)
\r
10868 char temp[MSG_SIZ+1], *p;
\r
10871 len = strlen(buf);
\r
10872 if (len > MSG_SIZ)
\r
10875 strncpy(temp, buf, len);
\r
10880 if (*p == '\n' || *p == '\r')
\r
10885 strcat(temp, "\n");
\r
10887 SendToPlayer(temp, strlen(temp));
\r
10891 SetWhiteToPlayEvent()
\r
10893 if (gameMode == EditPosition) {
\r
10894 blackPlaysFirst = FALSE;
\r
10895 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10896 } else if (gameMode == IcsExamining) {
\r
10897 SendToICS(ics_prefix);
\r
10898 SendToICS("tomove white\n");
\r
10903 SetBlackToPlayEvent()
\r
10905 if (gameMode == EditPosition) {
\r
10906 blackPlaysFirst = TRUE;
\r
10907 currentMove = 1; /* kludge */
\r
10908 DisplayBothClocks();
\r
10910 } else if (gameMode == IcsExamining) {
\r
10911 SendToICS(ics_prefix);
\r
10912 SendToICS("tomove black\n");
\r
10917 EditPositionMenuEvent(selection, x, y)
\r
10918 ChessSquare selection;
\r
10921 char buf[MSG_SIZ];
\r
10922 ChessSquare piece = boards[0][y][x];
\r
10924 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10926 switch (selection) {
\r
10928 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10929 SendToICS(ics_prefix);
\r
10930 SendToICS("bsetup clear\n");
\r
10931 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10932 SendToICS(ics_prefix);
\r
10933 SendToICS("clearboard\n");
\r
10935 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10936 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10937 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10938 if (gameMode == IcsExamining) {
\r
10939 if (boards[currentMove][y][x] != EmptySquare) {
\r
10940 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10941 AAA + x, ONE + y);
\r
10945 boards[0][y][x] = p;
\r
10950 if (gameMode == EditPosition) {
\r
10951 DrawPosition(FALSE, boards[0]);
\r
10956 SetWhiteToPlayEvent();
\r
10960 SetBlackToPlayEvent();
\r
10963 case EmptySquare:
\r
10964 if (gameMode == IcsExamining) {
\r
10965 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10968 boards[0][y][x] = EmptySquare;
\r
10969 DrawPosition(FALSE, boards[0]);
\r
10973 case PromotePiece:
\r
10974 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10975 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10976 selection = (ChessSquare) (PROMOTED piece);
\r
10977 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10978 else selection = (ChessSquare)((int)piece - 1);
\r
10979 goto defaultlabel;
\r
10981 case DemotePiece:
\r
10982 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10983 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10984 selection = (ChessSquare) (DEMOTED piece);
\r
10985 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10986 else selection = (ChessSquare)((int)piece + 1);
\r
10987 goto defaultlabel;
\r
10991 if(gameInfo.variant == VariantShatranj ||
\r
10992 gameInfo.variant == VariantXiangqi ||
\r
10993 gameInfo.variant == VariantCourier )
\r
10994 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10995 goto defaultlabel;
\r
10999 if(gameInfo.variant == VariantXiangqi)
\r
11000 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11001 if(gameInfo.variant == VariantKnightmate)
\r
11002 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11005 if (gameMode == IcsExamining) {
\r
11006 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11007 PieceToChar(selection), AAA + x, ONE + y);
\r
11010 boards[0][y][x] = selection;
\r
11011 DrawPosition(FALSE, boards[0]);
\r
11019 DropMenuEvent(selection, x, y)
\r
11020 ChessSquare selection;
\r
11023 ChessMove moveType;
\r
11025 switch (gameMode) {
\r
11026 case IcsPlayingWhite:
\r
11027 case MachinePlaysBlack:
\r
11028 if (!WhiteOnMove(currentMove)) {
\r
11029 DisplayMoveError(_("It is Black's turn"));
\r
11032 moveType = WhiteDrop;
\r
11034 case IcsPlayingBlack:
\r
11035 case MachinePlaysWhite:
\r
11036 if (WhiteOnMove(currentMove)) {
\r
11037 DisplayMoveError(_("It is White's turn"));
\r
11040 moveType = BlackDrop;
\r
11043 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11049 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11050 selection = (ChessSquare) ((int) selection
\r
11051 + (int) BlackPawn - (int) WhitePawn);
\r
11053 if (boards[currentMove][y][x] != EmptySquare) {
\r
11054 DisplayMoveError(_("That square is occupied"));
\r
11058 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11064 /* Accept a pending offer of any kind from opponent */
\r
11066 if (appData.icsActive) {
\r
11067 SendToICS(ics_prefix);
\r
11068 SendToICS("accept\n");
\r
11069 } else if (cmailMsgLoaded) {
\r
11070 if (currentMove == cmailOldMove &&
\r
11071 commentList[cmailOldMove] != NULL &&
\r
11072 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11073 "Black offers a draw" : "White offers a draw")) {
\r
11075 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11076 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11078 DisplayError(_("There is no pending offer on this move"), 0);
\r
11079 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11082 /* Not used for offers from chess program */
\r
11089 /* Decline a pending offer of any kind from opponent */
\r
11091 if (appData.icsActive) {
\r
11092 SendToICS(ics_prefix);
\r
11093 SendToICS("decline\n");
\r
11094 } else if (cmailMsgLoaded) {
\r
11095 if (currentMove == cmailOldMove &&
\r
11096 commentList[cmailOldMove] != NULL &&
\r
11097 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11098 "Black offers a draw" : "White offers a draw")) {
\r
11100 AppendComment(cmailOldMove, "Draw declined");
\r
11101 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11102 #endif /*NOTDEF*/
\r
11104 DisplayError(_("There is no pending offer on this move"), 0);
\r
11107 /* Not used for offers from chess program */
\r
11114 /* Issue ICS rematch command */
\r
11115 if (appData.icsActive) {
\r
11116 SendToICS(ics_prefix);
\r
11117 SendToICS("rematch\n");
\r
11124 /* Call your opponent's flag (claim a win on time) */
\r
11125 if (appData.icsActive) {
\r
11126 SendToICS(ics_prefix);
\r
11127 SendToICS("flag\n");
\r
11129 switch (gameMode) {
\r
11132 case MachinePlaysWhite:
\r
11135 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11138 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11140 DisplayError(_("Your opponent is not out of time"), 0);
\r
11143 case MachinePlaysBlack:
\r
11146 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11149 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11151 DisplayError(_("Your opponent is not out of time"), 0);
\r
11161 /* Offer draw or accept pending draw offer from opponent */
\r
11163 if (appData.icsActive) {
\r
11164 /* Note: tournament rules require draw offers to be
\r
11165 made after you make your move but before you punch
\r
11166 your clock. Currently ICS doesn't let you do that;
\r
11167 instead, you immediately punch your clock after making
\r
11168 a move, but you can offer a draw at any time. */
\r
11170 SendToICS(ics_prefix);
\r
11171 SendToICS("draw\n");
\r
11172 } else if (cmailMsgLoaded) {
\r
11173 if (currentMove == cmailOldMove &&
\r
11174 commentList[cmailOldMove] != NULL &&
\r
11175 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11176 "Black offers a draw" : "White offers a draw")) {
\r
11177 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11178 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11179 } else if (currentMove == cmailOldMove + 1) {
\r
11180 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11181 "White offers a draw" : "Black offers a draw";
\r
11182 AppendComment(currentMove, offer);
\r
11183 DisplayComment(currentMove - 1, offer);
\r
11184 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11186 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11187 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11189 } else if (first.offeredDraw) {
\r
11190 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11192 if (first.sendDrawOffers) {
\r
11193 SendToProgram("draw\n", &first);
\r
11194 userOfferedDraw = TRUE;
\r
11202 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11204 if (appData.icsActive) {
\r
11205 SendToICS(ics_prefix);
\r
11206 SendToICS("adjourn\n");
\r
11208 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11216 /* Offer Abort or accept pending Abort offer from opponent */
\r
11218 if (appData.icsActive) {
\r
11219 SendToICS(ics_prefix);
\r
11220 SendToICS("abort\n");
\r
11222 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11229 /* Resign. You can do this even if it's not your turn. */
\r
11231 if (appData.icsActive) {
\r
11232 SendToICS(ics_prefix);
\r
11233 SendToICS("resign\n");
\r
11235 switch (gameMode) {
\r
11236 case MachinePlaysWhite:
\r
11237 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11239 case MachinePlaysBlack:
\r
11240 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11243 if (cmailMsgLoaded) {
\r
11245 if (WhiteOnMove(cmailOldMove)) {
\r
11246 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11248 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11250 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11261 StopObservingEvent()
\r
11263 /* Stop observing current games */
\r
11264 SendToICS(ics_prefix);
\r
11265 SendToICS("unobserve\n");
\r
11269 StopExaminingEvent()
\r
11271 /* Stop observing current game */
\r
11272 SendToICS(ics_prefix);
\r
11273 SendToICS("unexamine\n");
\r
11277 ForwardInner(target)
\r
11282 if (appData.debugMode)
\r
11283 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11284 target, currentMove, forwardMostMove);
\r
11286 if (gameMode == EditPosition)
\r
11289 if (gameMode == PlayFromGameFile && !pausing)
\r
11292 if (gameMode == IcsExamining && pausing)
\r
11293 limit = pauseExamForwardMostMove;
\r
11295 limit = forwardMostMove;
\r
11297 if (target > limit) target = limit;
\r
11299 if (target > 0 && moveList[target - 1][0]) {
\r
11300 int fromX, fromY, toX, toY;
\r
11301 toX = moveList[target - 1][2] - AAA;
\r
11302 toY = moveList[target - 1][3] - ONE;
\r
11303 if (moveList[target - 1][1] == '@') {
\r
11304 if (appData.highlightLastMove) {
\r
11305 SetHighlights(-1, -1, toX, toY);
\r
11308 fromX = moveList[target - 1][0] - AAA;
\r
11309 fromY = moveList[target - 1][1] - ONE;
\r
11310 if (target == currentMove + 1) {
\r
11311 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11313 if (appData.highlightLastMove) {
\r
11314 SetHighlights(fromX, fromY, toX, toY);
\r
11318 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11319 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11320 gameMode == AnalyzeFile) {
\r
11321 while (currentMove < target) {
\r
11322 SendMoveToProgram(currentMove++, &first);
\r
11325 currentMove = target;
\r
11328 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11329 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11330 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11332 DisplayBothClocks();
\r
11333 DisplayMove(currentMove - 1);
\r
11334 DrawPosition(FALSE, boards[currentMove]);
\r
11335 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11336 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11337 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11345 if (gameMode == IcsExamining && !pausing) {
\r
11346 SendToICS(ics_prefix);
\r
11347 SendToICS("forward\n");
\r
11349 ForwardInner(currentMove + 1);
\r
11356 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11357 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11358 * the remaining moves to the engine. Otherwise we get analysis output
\r
11359 * after each move.
\r
11361 if (first.analysisSupport) {
\r
11362 SendToProgram("exit\nforce\n", &first);
\r
11363 first.analyzing = FALSE;
\r
11367 if (gameMode == IcsExamining && !pausing) {
\r
11368 SendToICS(ics_prefix);
\r
11369 SendToICS("forward 999999\n");
\r
11371 ForwardInner(forwardMostMove);
\r
11374 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11375 /* we have fed all the moves, so reactivate analysis mode */
\r
11376 SendToProgram("analyze\n", &first);
\r
11377 first.analyzing = TRUE;
\r
11378 /*first.maybeThinking = TRUE;*/
\r
11379 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11384 BackwardInner(target)
\r
11387 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11389 if (appData.debugMode)
\r
11390 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11391 target, currentMove, forwardMostMove);
\r
11393 if (gameMode == EditPosition) return;
\r
11394 if (currentMove <= backwardMostMove) {
\r
11395 ClearHighlights();
\r
11396 DrawPosition(full_redraw, boards[currentMove]);
\r
11399 if (gameMode == PlayFromGameFile && !pausing)
\r
11402 if (moveList[target][0]) {
\r
11403 int fromX, fromY, toX, toY;
\r
11404 toX = moveList[target][2] - AAA;
\r
11405 toY = moveList[target][3] - ONE;
\r
11406 if (moveList[target][1] == '@') {
\r
11407 if (appData.highlightLastMove) {
\r
11408 SetHighlights(-1, -1, toX, toY);
\r
11411 fromX = moveList[target][0] - AAA;
\r
11412 fromY = moveList[target][1] - ONE;
\r
11413 if (target == currentMove - 1) {
\r
11414 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11416 if (appData.highlightLastMove) {
\r
11417 SetHighlights(fromX, fromY, toX, toY);
\r
11421 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11422 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11423 while (currentMove > target) {
\r
11424 SendToProgram("undo\n", &first);
\r
11428 currentMove = target;
\r
11431 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11432 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11433 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11435 DisplayBothClocks();
\r
11436 DisplayMove(currentMove - 1);
\r
11437 DrawPosition(full_redraw, boards[currentMove]);
\r
11438 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11439 // [HGM] PV info: routine tests if comment empty
\r
11440 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11446 if (gameMode == IcsExamining && !pausing) {
\r
11447 SendToICS(ics_prefix);
\r
11448 SendToICS("backward\n");
\r
11450 BackwardInner(currentMove - 1);
\r
11457 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11458 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11459 * all the moves. Otherwise we get analysis output after each undo.
\r
11461 if (first.analysisSupport) {
\r
11462 SendToProgram("exit\nforce\n", &first);
\r
11463 first.analyzing = FALSE;
\r
11467 if (gameMode == IcsExamining && !pausing) {
\r
11468 SendToICS(ics_prefix);
\r
11469 SendToICS("backward 999999\n");
\r
11471 BackwardInner(backwardMostMove);
\r
11474 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11475 /* we have fed all the moves, so reactivate analysis mode */
\r
11476 SendToProgram("analyze\n", &first);
\r
11477 first.analyzing = TRUE;
\r
11478 /*first.maybeThinking = TRUE;*/
\r
11479 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11484 ToNrEvent(int to)
\r
11486 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11487 if (to >= forwardMostMove) to = forwardMostMove;
\r
11488 if (to <= backwardMostMove) to = backwardMostMove;
\r
11489 if (to < currentMove) {
\r
11490 BackwardInner(to);
\r
11492 ForwardInner(to);
\r
11499 if (gameMode != IcsExamining) {
\r
11500 DisplayError(_("You are not examining a game"), 0);
\r
11504 DisplayError(_("You can't revert while pausing"), 0);
\r
11507 SendToICS(ics_prefix);
\r
11508 SendToICS("revert\n");
\r
11512 RetractMoveEvent()
\r
11514 switch (gameMode) {
\r
11515 case MachinePlaysWhite:
\r
11516 case MachinePlaysBlack:
\r
11517 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11518 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11521 if (forwardMostMove < 2) return;
\r
11522 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11523 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11524 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11525 DisplayBothClocks();
\r
11526 DisplayMove(currentMove - 1);
\r
11527 ClearHighlights();/*!! could figure this out*/
\r
11528 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11529 SendToProgram("remove\n", &first);
\r
11530 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11533 case BeginningOfGame:
\r
11537 case IcsPlayingWhite:
\r
11538 case IcsPlayingBlack:
\r
11539 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11540 SendToICS(ics_prefix);
\r
11541 SendToICS("takeback 2\n");
\r
11543 SendToICS(ics_prefix);
\r
11544 SendToICS("takeback 1\n");
\r
11553 ChessProgramState *cps;
\r
11555 switch (gameMode) {
\r
11556 case MachinePlaysWhite:
\r
11557 if (!WhiteOnMove(forwardMostMove)) {
\r
11558 DisplayError(_("It is your turn"), 0);
\r
11563 case MachinePlaysBlack:
\r
11564 if (WhiteOnMove(forwardMostMove)) {
\r
11565 DisplayError(_("It is your turn"), 0);
\r
11570 case TwoMachinesPlay:
\r
11571 if (WhiteOnMove(forwardMostMove) ==
\r
11572 (first.twoMachinesColor[0] == 'w')) {
\r
11578 case BeginningOfGame:
\r
11582 SendToProgram("?\n", cps);
\r
11586 TruncateGameEvent()
\r
11589 if (gameMode != EditGame) return;
\r
11596 if (forwardMostMove > currentMove) {
\r
11597 if (gameInfo.resultDetails != NULL) {
\r
11598 free(gameInfo.resultDetails);
\r
11599 gameInfo.resultDetails = NULL;
\r
11600 gameInfo.result = GameUnfinished;
\r
11602 forwardMostMove = currentMove;
\r
11603 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11611 if (appData.noChessProgram) return;
\r
11612 switch (gameMode) {
\r
11613 case MachinePlaysWhite:
\r
11614 if (WhiteOnMove(forwardMostMove)) {
\r
11615 DisplayError(_("Wait until your turn"), 0);
\r
11619 case BeginningOfGame:
\r
11620 case MachinePlaysBlack:
\r
11621 if (!WhiteOnMove(forwardMostMove)) {
\r
11622 DisplayError(_("Wait until your turn"), 0);
\r
11627 DisplayError(_("No hint available"), 0);
\r
11630 SendToProgram("hint\n", &first);
\r
11631 hintRequested = TRUE;
\r
11637 if (appData.noChessProgram) return;
\r
11638 switch (gameMode) {
\r
11639 case MachinePlaysWhite:
\r
11640 if (WhiteOnMove(forwardMostMove)) {
\r
11641 DisplayError(_("Wait until your turn"), 0);
\r
11645 case BeginningOfGame:
\r
11646 case MachinePlaysBlack:
\r
11647 if (!WhiteOnMove(forwardMostMove)) {
\r
11648 DisplayError(_("Wait until your turn"), 0);
\r
11652 case EditPosition:
\r
11653 EditPositionDone();
\r
11655 case TwoMachinesPlay:
\r
11660 SendToProgram("bk\n", &first);
\r
11661 bookOutput[0] = NULLCHAR;
\r
11662 bookRequested = TRUE;
\r
11668 char *tags = PGNTags(&gameInfo);
\r
11669 TagsPopUp(tags, CmailMsg());
\r
11673 /* end button procedures */
\r
11676 PrintPosition(fp, move)
\r
11682 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11683 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11684 char c = PieceToChar(boards[move][i][j]);
\r
11685 fputc(c == 'x' ? '.' : c, fp);
\r
11686 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11689 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11690 fprintf(fp, "white to play\n");
\r
11692 fprintf(fp, "black to play\n");
\r
11696 PrintOpponents(fp)
\r
11699 if (gameInfo.white != NULL) {
\r
11700 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11702 fprintf(fp, "\n");
\r
11706 /* Find last component of program's own name, using some heuristics */
\r
11708 TidyProgramName(prog, host, buf)
\r
11709 char *prog, *host, buf[MSG_SIZ];
\r
11712 int local = (strcmp(host, "localhost") == 0);
\r
11713 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11715 while (*p == ' ') p++;
\r
11718 if (*prog == '"' || *prog == '\'') {
\r
11719 q = strchr(prog + 1, *prog);
\r
11721 q = strchr(prog, ' ');
\r
11723 if (q == NULL) q = prog + strlen(prog);
\r
11725 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11727 if(p == prog && *p == '"') p++;
\r
11728 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11729 memcpy(buf, p, q - p);
\r
11730 buf[q - p] = NULLCHAR;
\r
11732 strcat(buf, "@");
\r
11733 strcat(buf, host);
\r
11738 TimeControlTagValue()
\r
11740 char buf[MSG_SIZ];
\r
11741 if (!appData.clockMode) {
\r
11742 strcpy(buf, "-");
\r
11743 } else if (movesPerSession > 0) {
\r
11744 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11745 } else if (timeIncrement == 0) {
\r
11746 sprintf(buf, "%ld", timeControl/1000);
\r
11748 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11750 return StrSave(buf);
\r
11756 /* This routine is used only for certain modes */
\r
11757 VariantClass v = gameInfo.variant;
\r
11758 ClearGameInfo(&gameInfo);
\r
11759 gameInfo.variant = v;
\r
11761 switch (gameMode) {
\r
11762 case MachinePlaysWhite:
\r
11763 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11764 gameInfo.site = StrSave(HostName());
\r
11765 gameInfo.date = PGNDate();
\r
11766 gameInfo.round = StrSave("-");
\r
11767 gameInfo.white = StrSave(first.tidy);
\r
11768 gameInfo.black = StrSave(UserName());
\r
11769 gameInfo.timeControl = TimeControlTagValue();
\r
11772 case MachinePlaysBlack:
\r
11773 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11774 gameInfo.site = StrSave(HostName());
\r
11775 gameInfo.date = PGNDate();
\r
11776 gameInfo.round = StrSave("-");
\r
11777 gameInfo.white = StrSave(UserName());
\r
11778 gameInfo.black = StrSave(first.tidy);
\r
11779 gameInfo.timeControl = TimeControlTagValue();
\r
11782 case TwoMachinesPlay:
\r
11783 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11784 gameInfo.site = StrSave(HostName());
\r
11785 gameInfo.date = PGNDate();
\r
11786 if (matchGame > 0) {
\r
11787 char buf[MSG_SIZ];
\r
11788 sprintf(buf, "%d", matchGame);
\r
11789 gameInfo.round = StrSave(buf);
\r
11791 gameInfo.round = StrSave("-");
\r
11793 if (first.twoMachinesColor[0] == 'w') {
\r
11794 gameInfo.white = StrSave(first.tidy);
\r
11795 gameInfo.black = StrSave(second.tidy);
\r
11797 gameInfo.white = StrSave(second.tidy);
\r
11798 gameInfo.black = StrSave(first.tidy);
\r
11800 gameInfo.timeControl = TimeControlTagValue();
\r
11804 gameInfo.event = StrSave("Edited game");
\r
11805 gameInfo.site = StrSave(HostName());
\r
11806 gameInfo.date = PGNDate();
\r
11807 gameInfo.round = StrSave("-");
\r
11808 gameInfo.white = StrSave("-");
\r
11809 gameInfo.black = StrSave("-");
\r
11812 case EditPosition:
\r
11813 gameInfo.event = StrSave("Edited position");
\r
11814 gameInfo.site = StrSave(HostName());
\r
11815 gameInfo.date = PGNDate();
\r
11816 gameInfo.round = StrSave("-");
\r
11817 gameInfo.white = StrSave("-");
\r
11818 gameInfo.black = StrSave("-");
\r
11821 case IcsPlayingWhite:
\r
11822 case IcsPlayingBlack:
\r
11823 case IcsObserving:
\r
11824 case IcsExamining:
\r
11827 case PlayFromGameFile:
\r
11828 gameInfo.event = StrSave("Game from non-PGN file");
\r
11829 gameInfo.site = StrSave(HostName());
\r
11830 gameInfo.date = PGNDate();
\r
11831 gameInfo.round = StrSave("-");
\r
11832 gameInfo.white = StrSave("?");
\r
11833 gameInfo.black = StrSave("?");
\r
11842 ReplaceComment(index, text)
\r
11848 while (*text == '\n') text++;
\r
11849 len = strlen(text);
\r
11850 while (len > 0 && text[len - 1] == '\n') len--;
\r
11852 if (commentList[index] != NULL)
\r
11853 free(commentList[index]);
\r
11856 commentList[index] = NULL;
\r
11859 commentList[index] = (char *) malloc(len + 2);
\r
11860 strncpy(commentList[index], text, len);
\r
11861 commentList[index][len] = '\n';
\r
11862 commentList[index][len + 1] = NULLCHAR;
\r
11875 if (ch == '\r') continue;
\r
11877 } while (ch != '\0');
\r
11881 AppendComment(index, text)
\r
11888 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11891 while (*text == '\n') text++;
\r
11892 len = strlen(text);
\r
11893 while (len > 0 && text[len - 1] == '\n') len--;
\r
11895 if (len == 0) return;
\r
11897 if (commentList[index] != NULL) {
\r
11898 old = commentList[index];
\r
11899 oldlen = strlen(old);
\r
11900 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11901 strcpy(commentList[index], old);
\r
11903 strncpy(&commentList[index][oldlen], text, len);
\r
11904 commentList[index][oldlen + len] = '\n';
\r
11905 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11907 commentList[index] = (char *) malloc(len + 2);
\r
11908 strncpy(commentList[index], text, len);
\r
11909 commentList[index][len] = '\n';
\r
11910 commentList[index][len + 1] = NULLCHAR;
\r
11914 static char * FindStr( char * text, char * sub_text )
\r
11916 char * result = strstr( text, sub_text );
\r
11918 if( result != NULL ) {
\r
11919 result += strlen( sub_text );
\r
11925 /* [AS] Try to extract PV info from PGN comment */
\r
11926 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11927 char *GetInfoFromComment( int index, char * text )
\r
11929 char * sep = text;
\r
11931 if( text != NULL && index > 0 ) {
\r
11934 int time = -1, sec = 0, deci;
\r
11935 char * s_eval = FindStr( text, "[%eval " );
\r
11936 char * s_emt = FindStr( text, "[%emt " );
\r
11938 if( s_eval != NULL || s_emt != NULL ) {
\r
11942 if( s_eval != NULL ) {
\r
11943 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11947 if( delim != ']' ) {
\r
11952 if( s_emt != NULL ) {
\r
11956 /* We expect something like: [+|-]nnn.nn/dd */
\r
11957 int score_lo = 0;
\r
11959 sep = strchr( text, '/' );
\r
11960 if( sep == NULL || sep < (text+4) ) {
\r
11964 time = -1; sec = -1; deci = -1;
\r
11965 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11966 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11967 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11968 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11972 if( score_lo < 0 || score_lo >= 100 ) {
\r
11976 if(sec >= 0) time = 600*time + 10*sec; else
\r
11977 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
11979 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11981 /* [HGM] PV time: now locate end of PV info */
\r
11982 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11984 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11986 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11988 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
11989 while(*sep == ' ') sep++;
\r
11992 if( depth <= 0 ) {
\r
12000 pvInfoList[index-1].depth = depth;
\r
12001 pvInfoList[index-1].score = score;
\r
12002 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12008 SendToProgram(message, cps)
\r
12010 ChessProgramState *cps;
\r
12012 int count, outCount, error;
\r
12013 char buf[MSG_SIZ];
\r
12015 if (cps->pr == NULL) return;
\r
12018 if (appData.debugMode) {
\r
12020 GetTimeMark(&now);
\r
12021 fprintf(debugFP, "%ld >%-6s: %s",
\r
12022 SubtractTimeMarks(&now, &programStartTime),
\r
12023 cps->which, message);
\r
12026 count = strlen(message);
\r
12027 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12028 if (outCount < count && !exiting
\r
12029 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12030 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12031 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12032 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12033 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12034 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12036 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12038 gameInfo.resultDetails = buf;
\r
12040 DisplayFatalError(buf, error, 1);
\r
12045 ReceiveFromProgram(isr, closure, message, count, error)
\r
12046 InputSourceRef isr;
\r
12047 VOIDSTAR closure;
\r
12053 char buf[MSG_SIZ];
\r
12054 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12056 if (isr != cps->isr) return; /* Killed intentionally */
\r
12057 if (count <= 0) {
\r
12058 if (count == 0) {
\r
12060 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12061 cps->which, cps->program);
\r
12062 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12063 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12064 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12065 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12067 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12069 gameInfo.resultDetails = buf;
\r
12071 RemoveInputSource(cps->isr);
\r
12072 DisplayFatalError(buf, 0, 1);
\r
12075 _("Error reading from %s chess program (%s)"),
\r
12076 cps->which, cps->program);
\r
12077 RemoveInputSource(cps->isr);
\r
12079 /* [AS] Program is misbehaving badly... kill it */
\r
12080 if( count == -2 ) {
\r
12081 DestroyChildProcess( cps->pr, 9 );
\r
12082 cps->pr = NoProc;
\r
12085 DisplayFatalError(buf, error, 1);
\r
12090 if ((end_str = strchr(message, '\r')) != NULL)
\r
12091 *end_str = NULLCHAR;
\r
12092 if ((end_str = strchr(message, '\n')) != NULL)
\r
12093 *end_str = NULLCHAR;
\r
12095 if (appData.debugMode) {
\r
12096 TimeMark now; int print = 1;
\r
12097 char *quote = ""; char c; int i;
\r
12099 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12100 char start = message[0];
\r
12101 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12102 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12103 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12104 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12105 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12106 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12107 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12108 { quote = "# "; print = (appData.engineComments == 2); }
\r
12109 message[0] = start; // restore original message
\r
12112 GetTimeMark(&now);
\r
12113 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12114 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12120 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12121 if (appData.icsEngineAnalyze) {
\r
12122 if (strstr(message, "whisper") != NULL ||
\r
12123 strstr(message, "kibitz") != NULL ||
\r
12124 strstr(message, "tellics") != NULL) return;
\r
12127 HandleMachineMove(message, cps);
\r
12132 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12133 ChessProgramState *cps;
\r
12134 int mps, inc, sd, st;
\r
12137 char buf[MSG_SIZ];
\r
12140 if( timeControl_2 > 0 ) {
\r
12141 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12142 tc = timeControl_2;
\r
12145 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12146 inc /= cps->timeOdds;
\r
12147 st /= cps->timeOdds;
\r
12149 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12152 /* Set exact time per move, normally using st command */
\r
12153 if (cps->stKludge) {
\r
12154 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12155 seconds = st % 60;
\r
12156 if (seconds == 0) {
\r
12157 sprintf(buf, "level 1 %d\n", st/60);
\r
12159 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12162 sprintf(buf, "st %d\n", st);
\r
12165 /* Set conventional or incremental time control, using level command */
\r
12166 if (seconds == 0) {
\r
12167 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12168 Fixed in later versions, but still avoid :seconds
\r
12169 when seconds is 0. */
\r
12170 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12172 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12173 seconds, inc/1000);
\r
12176 SendToProgram(buf, cps);
\r
12178 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12179 /* Orthogonally, limit search to given depth */
\r
12181 if (cps->sdKludge) {
\r
12182 sprintf(buf, "depth\n%d\n", sd);
\r
12184 sprintf(buf, "sd %d\n", sd);
\r
12186 SendToProgram(buf, cps);
\r
12189 if(cps->nps > 0) { /* [HGM] nps */
\r
12190 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12192 sprintf(buf, "nps %d\n", cps->nps);
\r
12193 SendToProgram(buf, cps);
\r
12198 ChessProgramState *WhitePlayer()
\r
12199 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12201 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12202 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12208 SendTimeRemaining(cps, machineWhite)
\r
12209 ChessProgramState *cps;
\r
12210 int /*boolean*/ machineWhite;
\r
12212 char message[MSG_SIZ];
\r
12213 long time, otime;
\r
12215 /* Note: this routine must be called when the clocks are stopped
\r
12216 or when they have *just* been set or switched; otherwise
\r
12217 it will be off by the time since the current tick started.
\r
12219 if (machineWhite) {
\r
12220 time = whiteTimeRemaining / 10;
\r
12221 otime = blackTimeRemaining / 10;
\r
12223 time = blackTimeRemaining / 10;
\r
12224 otime = whiteTimeRemaining / 10;
\r
12226 /* [HGM] translate opponent's time by time-odds factor */
\r
12227 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12228 if (appData.debugMode) {
\r
12229 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12232 if (time <= 0) time = 1;
\r
12233 if (otime <= 0) otime = 1;
\r
12235 sprintf(message, "time %ld\n", time);
\r
12236 SendToProgram(message, cps);
\r
12238 sprintf(message, "otim %ld\n", otime);
\r
12239 SendToProgram(message, cps);
\r
12243 BoolFeature(p, name, loc, cps)
\r
12247 ChessProgramState *cps;
\r
12249 char buf[MSG_SIZ];
\r
12250 int len = strlen(name);
\r
12252 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12254 sscanf(*p, "%d", &val);
\r
12255 *loc = (val != 0);
\r
12256 while (**p && **p != ' ') (*p)++;
\r
12257 sprintf(buf, "accepted %s\n", name);
\r
12258 SendToProgram(buf, cps);
\r
12265 IntFeature(p, name, loc, cps)
\r
12269 ChessProgramState *cps;
\r
12271 char buf[MSG_SIZ];
\r
12272 int len = strlen(name);
\r
12273 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12275 sscanf(*p, "%d", loc);
\r
12276 while (**p && **p != ' ') (*p)++;
\r
12277 sprintf(buf, "accepted %s\n", name);
\r
12278 SendToProgram(buf, cps);
\r
12285 StringFeature(p, name, loc, cps)
\r
12289 ChessProgramState *cps;
\r
12291 char buf[MSG_SIZ];
\r
12292 int len = strlen(name);
\r
12293 if (strncmp((*p), name, len) == 0
\r
12294 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12296 sscanf(*p, "%[^\"]", loc);
\r
12297 while (**p && **p != '\"') (*p)++;
\r
12298 if (**p == '\"') (*p)++;
\r
12299 sprintf(buf, "accepted %s\n", name);
\r
12300 SendToProgram(buf, cps);
\r
12307 ParseOption(Option *opt, ChessProgramState *cps)
\r
12308 // [HGM] options: process the string that defines an engine option, and determine
\r
12309 // name, type, default value, and allowed value range
\r
12311 char *p, *q, buf[MSG_SIZ];
\r
12312 int n, min = (-1)<<31, max = 1<<31, def;
\r
12314 if(p = strstr(opt->name, " -spin ")) {
\r
12315 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12316 if(max < min) max = min; // enforce consistency
\r
12317 if(def < min) def = min;
\r
12318 if(def > max) def = max;
\r
12319 opt->value = def;
\r
12322 opt->type = Spin;
\r
12323 } else if(p = strstr(opt->name, " -string ")) {
\r
12324 opt->textValue = p+9;
\r
12325 opt->type = TextBox;
\r
12326 } else if(p = strstr(opt->name, " -check ")) {
\r
12327 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12328 opt->value = (def != 0);
\r
12329 opt->type = CheckBox;
\r
12330 } else if(p = strstr(opt->name, " -combo ")) {
\r
12331 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12332 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12333 opt->value = n = 0;
\r
12334 while(q = StrStr(q, " /// ")) {
\r
12335 n++; *q = 0; // count choices, and null-terminate each of them
\r
12337 if(*q == '*') { // remember default, which is marked with * prefix
\r
12341 cps->comboList[cps->comboCnt++] = q;
\r
12343 cps->comboList[cps->comboCnt++] = NULL;
\r
12344 opt->max = n + 1;
\r
12345 opt->type = ComboBox;
\r
12346 } else if(p = strstr(opt->name, " -button")) {
\r
12347 opt->type = Button;
\r
12348 } else if(p = strstr(opt->name, " -save")) {
\r
12349 opt->type = SaveButton;
\r
12350 } else return FALSE;
\r
12351 *p = 0; // terminate option name
\r
12352 // now look if the command-line options define a setting for this engine option.
\r
12353 if(cps->optionSettings && cps->optionSettings[0])
\r
12354 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12355 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12356 sprintf(buf, "option %s", p);
\r
12357 if(p = strstr(buf, ",")) *p = 0;
\r
12358 strcat(buf, "\n");
\r
12359 SendToProgram(buf, cps);
\r
12365 FeatureDone(cps, val)
\r
12366 ChessProgramState* cps;
\r
12369 DelayedEventCallback cb = GetDelayedEvent();
\r
12370 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12371 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12372 CancelDelayedEvent();
\r
12373 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12375 cps->initDone = val;
\r
12378 /* Parse feature command from engine */
\r
12380 ParseFeatures(args, cps)
\r
12382 ChessProgramState *cps;
\r
12387 char buf[MSG_SIZ];
\r
12390 while (*p == ' ') p++;
\r
12391 if (*p == NULLCHAR) return;
\r
12393 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12394 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12395 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12396 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12397 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12398 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12399 /* Engine can disable reuse, but can't enable it if user said no */
\r
12400 if (!val) cps->reuse = FALSE;
\r
12403 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12404 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12405 if (gameMode == TwoMachinesPlay) {
\r
12406 DisplayTwoMachinesTitle();
\r
12408 DisplayTitle("");
\r
12412 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12413 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12414 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12415 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12416 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12417 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12418 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12419 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12420 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12421 if (IntFeature(&p, "done", &val, cps)) {
\r
12422 FeatureDone(cps, val);
\r
12425 /* Added by Tord: */
\r
12426 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12427 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12428 /* End of additions by Tord */
\r
12430 /* [HGM] added features: */
\r
12431 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12432 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12433 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12434 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12435 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12436 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12437 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12438 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12439 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12440 cps->nrOptions--;
\r
12441 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12442 DisplayError(buf, 0);
\r
12446 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12447 /* End of additions by HGM */
\r
12449 /* unknown feature: complain and skip */
\r
12451 while (*q && *q != '=') q++;
\r
12452 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12453 SendToProgram(buf, cps);
\r
12457 if (*p == '\"') {
\r
12459 while (*p && *p != '\"') p++;
\r
12460 if (*p == '\"') p++;
\r
12462 while (*p && *p != ' ') p++;
\r
12470 PeriodicUpdatesEvent(newState)
\r
12473 if (newState == appData.periodicUpdates)
\r
12476 appData.periodicUpdates=newState;
\r
12478 /* Display type changes, so update it now */
\r
12479 DisplayAnalysis();
\r
12481 /* Get the ball rolling again... */
\r
12483 AnalysisPeriodicEvent(1);
\r
12484 StartAnalysisClock();
\r
12489 PonderNextMoveEvent(newState)
\r
12492 if (newState == appData.ponderNextMove) return;
\r
12493 if (gameMode == EditPosition) EditPositionDone();
\r
12495 SendToProgram("hard\n", &first);
\r
12496 if (gameMode == TwoMachinesPlay) {
\r
12497 SendToProgram("hard\n", &second);
\r
12500 SendToProgram("easy\n", &first);
\r
12501 thinkOutput[0] = NULLCHAR;
\r
12502 if (gameMode == TwoMachinesPlay) {
\r
12503 SendToProgram("easy\n", &second);
\r
12506 appData.ponderNextMove = newState;
\r
12510 NewSettingEvent(option, command, value)
\r
12512 int option, value;
\r
12514 char buf[MSG_SIZ];
\r
12516 if (gameMode == EditPosition) EditPositionDone();
\r
12517 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12518 SendToProgram(buf, &first);
\r
12519 if (gameMode == TwoMachinesPlay) {
\r
12520 SendToProgram(buf, &second);
\r
12525 ShowThinkingEvent()
\r
12526 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12528 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12529 int newState = appData.showThinking
\r
12530 // [HGM] thinking: other features now need thinking output as well
\r
12531 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12533 if (oldState == newState) return;
\r
12534 oldState = newState;
\r
12535 if (gameMode == EditPosition) EditPositionDone();
\r
12537 SendToProgram("post\n", &first);
\r
12538 if (gameMode == TwoMachinesPlay) {
\r
12539 SendToProgram("post\n", &second);
\r
12542 SendToProgram("nopost\n", &first);
\r
12543 thinkOutput[0] = NULLCHAR;
\r
12544 if (gameMode == TwoMachinesPlay) {
\r
12545 SendToProgram("nopost\n", &second);
\r
12548 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12552 AskQuestionEvent(title, question, replyPrefix, which)
\r
12553 char *title; char *question; char *replyPrefix; char *which;
\r
12555 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12556 if (pr == NoProc) return;
\r
12557 AskQuestion(title, question, replyPrefix, pr);
\r
12561 DisplayMove(moveNumber)
\r
12564 char message[MSG_SIZ];
\r
12565 char res[MSG_SIZ];
\r
12566 char cpThinkOutput[MSG_SIZ];
\r
12568 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12570 if (moveNumber == forwardMostMove - 1 ||
\r
12571 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12573 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12575 if (strchr(cpThinkOutput, '\n')) {
\r
12576 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12579 *cpThinkOutput = NULLCHAR;
\r
12582 /* [AS] Hide thinking from human user */
\r
12583 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12584 *cpThinkOutput = NULLCHAR;
\r
12585 if( thinkOutput[0] != NULLCHAR ) {
\r
12588 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12589 cpThinkOutput[i] = '.';
\r
12591 cpThinkOutput[i] = NULLCHAR;
\r
12592 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12596 if (moveNumber == forwardMostMove - 1 &&
\r
12597 gameInfo.resultDetails != NULL) {
\r
12598 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12599 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12601 sprintf(res, " {%s} %s",
\r
12602 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12605 res[0] = NULLCHAR;
\r
12608 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12609 DisplayMessage(res, cpThinkOutput);
\r
12611 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12612 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12613 parseList[moveNumber], res);
\r
12614 DisplayMessage(message, cpThinkOutput);
\r
12619 DisplayAnalysisText(text)
\r
12622 char buf[MSG_SIZ];
\r
12624 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12625 || appData.icsEngineAnalyze) {
\r
12626 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12627 AnalysisPopUp(buf, text);
\r
12632 only_one_move(str)
\r
12635 while (*str && isspace(*str)) ++str;
\r
12636 while (*str && !isspace(*str)) ++str;
\r
12637 if (!*str) return 1;
\r
12638 while (*str && isspace(*str)) ++str;
\r
12639 if (!*str) return 1;
\r
12644 DisplayAnalysis()
\r
12646 char buf[MSG_SIZ];
\r
12647 char lst[MSG_SIZ / 2];
\r
12649 static char *xtra[] = { "", " (--)", " (++)" };
\r
12652 if (programStats.time == 0) {
\r
12653 programStats.time = 1;
\r
12656 if (programStats.got_only_move) {
\r
12657 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12659 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12661 nps = (u64ToDouble(programStats.nodes) /
\r
12662 ((double)programStats.time /100.0));
\r
12664 cs = programStats.time % 100;
\r
12665 s = programStats.time / 100;
\r
12666 h = (s / (60*60));
\r
12671 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12672 if (programStats.move_name[0] != NULLCHAR) {
\r
12673 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12674 programStats.depth,
\r
12675 programStats.nr_moves-programStats.moves_left,
\r
12676 programStats.nr_moves, programStats.move_name,
\r
12677 ((float)programStats.score)/100.0, lst,
\r
12678 only_one_move(lst)?
\r
12679 xtra[programStats.got_fail] : "",
\r
12680 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12682 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12683 programStats.depth,
\r
12684 programStats.nr_moves-programStats.moves_left,
\r
12685 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12687 only_one_move(lst)?
\r
12688 xtra[programStats.got_fail] : "",
\r
12689 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12692 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12693 programStats.depth,
\r
12694 ((float)programStats.score)/100.0,
\r
12696 only_one_move(lst)?
\r
12697 xtra[programStats.got_fail] : "",
\r
12698 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12701 DisplayAnalysisText(buf);
\r
12705 DisplayComment(moveNumber, text)
\r
12709 char title[MSG_SIZ];
\r
12710 char buf[8000]; // comment can be long!
\r
12711 int score, depth;
\r
12713 if( appData.autoDisplayComment ) {
\r
12714 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12715 strcpy(title, "Comment");
\r
12717 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12718 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12719 parseList[moveNumber]);
\r
12721 } else title[0] = 0;
\r
12723 // [HGM] PV info: display PV info together with (or as) comment
\r
12724 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12725 if(text == NULL) text = "";
\r
12726 score = pvInfoList[moveNumber].score;
\r
12727 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12728 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12729 CommentPopUp(title, buf);
\r
12731 if (text != NULL)
\r
12732 CommentPopUp(title, text);
\r
12735 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12736 * might be busy thinking or pondering. It can be omitted if your
\r
12737 * gnuchess is configured to stop thinking immediately on any user
\r
12738 * input. However, that gnuchess feature depends on the FIONREAD
\r
12739 * ioctl, which does not work properly on some flavors of Unix.
\r
12743 ChessProgramState *cps;
\r
12746 if (!cps->useSigint) return;
\r
12747 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12748 switch (gameMode) {
\r
12749 case MachinePlaysWhite:
\r
12750 case MachinePlaysBlack:
\r
12751 case TwoMachinesPlay:
\r
12752 case IcsPlayingWhite:
\r
12753 case IcsPlayingBlack:
\r
12754 case AnalyzeMode:
\r
12755 case AnalyzeFile:
\r
12756 /* Skip if we know it isn't thinking */
\r
12757 if (!cps->maybeThinking) return;
\r
12758 if (appData.debugMode)
\r
12759 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12760 InterruptChildProcess(cps->pr);
\r
12761 cps->maybeThinking = FALSE;
\r
12766 #endif /*ATTENTION*/
\r
12772 if (whiteTimeRemaining <= 0) {
\r
12773 if (!whiteFlag) {
\r
12774 whiteFlag = TRUE;
\r
12775 if (appData.icsActive) {
\r
12776 if (appData.autoCallFlag &&
\r
12777 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12778 SendToICS(ics_prefix);
\r
12779 SendToICS("flag\n");
\r
12783 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12785 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12786 if (appData.autoCallFlag) {
\r
12787 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12794 if (blackTimeRemaining <= 0) {
\r
12795 if (!blackFlag) {
\r
12796 blackFlag = TRUE;
\r
12797 if (appData.icsActive) {
\r
12798 if (appData.autoCallFlag &&
\r
12799 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12800 SendToICS(ics_prefix);
\r
12801 SendToICS("flag\n");
\r
12805 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12807 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12808 if (appData.autoCallFlag) {
\r
12809 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12820 CheckTimeControl()
\r
12822 if (!appData.clockMode || appData.icsActive ||
\r
12823 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12826 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12828 if ( !WhiteOnMove(forwardMostMove) )
\r
12829 /* White made time control */
\r
12830 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12831 /* [HGM] time odds: correct new time quota for time odds! */
\r
12832 / WhitePlayer()->timeOdds;
\r
12834 /* Black made time control */
\r
12835 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12836 / WhitePlayer()->other->timeOdds;
\r
12840 DisplayBothClocks()
\r
12842 int wom = gameMode == EditPosition ?
\r
12843 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12844 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12845 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12849 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12850 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12851 to use other calls if you don't. Clocks will be less accurate if
\r
12852 you have neither ftime nor gettimeofday.
\r
12855 /* VS 2008 requires the #include outside of the function */
\r
12856 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12857 #include <sys/timeb.h>
\r
12860 /* Get the current time as a TimeMark */
\r
12865 #if HAVE_GETTIMEOFDAY
\r
12867 struct timeval timeVal;
\r
12868 struct timezone timeZone;
\r
12870 gettimeofday(&timeVal, &timeZone);
\r
12871 tm->sec = (long) timeVal.tv_sec;
\r
12872 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12874 #else /*!HAVE_GETTIMEOFDAY*/
\r
12877 // include <sys/timeb.h> / moved to just above start of function
\r
12878 struct timeb timeB;
\r
12881 tm->sec = (long) timeB.time;
\r
12882 tm->ms = (int) timeB.millitm;
\r
12884 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12885 tm->sec = (long) time(NULL);
\r
12891 /* Return the difference in milliseconds between two
\r
12892 time marks. We assume the difference will fit in a long!
\r
12895 SubtractTimeMarks(tm2, tm1)
\r
12896 TimeMark *tm2, *tm1;
\r
12898 return 1000L*(tm2->sec - tm1->sec) +
\r
12899 (long) (tm2->ms - tm1->ms);
\r
12904 * Code to manage the game clocks.
\r
12906 * In tournament play, black starts the clock and then white makes a move.
\r
12907 * We give the human user a slight advantage if he is playing white---the
\r
12908 * clocks don't run until he makes his first move, so it takes zero time.
\r
12909 * Also, we don't account for network lag, so we could get out of sync
\r
12910 * with GNU Chess's clock -- but then, referees are always right.
\r
12913 static TimeMark tickStartTM;
\r
12914 static long intendedTickLength;
\r
12917 NextTickLength(timeRemaining)
\r
12918 long timeRemaining;
\r
12920 long nominalTickLength, nextTickLength;
\r
12922 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12923 nominalTickLength = 100L;
\r
12925 nominalTickLength = 1000L;
\r
12926 nextTickLength = timeRemaining % nominalTickLength;
\r
12927 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12929 return nextTickLength;
\r
12932 /* Adjust clock one minute up or down */
\r
12934 AdjustClock(Boolean which, int dir)
\r
12936 if(which) blackTimeRemaining += 60000*dir;
\r
12937 else whiteTimeRemaining += 60000*dir;
\r
12938 DisplayBothClocks();
\r
12941 /* Stop clocks and reset to a fresh time control */
\r
12945 (void) StopClockTimer();
\r
12946 if (appData.icsActive) {
\r
12947 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12948 } else { /* [HGM] correct new time quote for time odds */
\r
12949 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12950 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12952 if (whiteFlag || blackFlag) {
\r
12953 DisplayTitle("");
\r
12954 whiteFlag = blackFlag = FALSE;
\r
12956 DisplayBothClocks();
\r
12959 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12961 /* Decrement running clock by amount of time that has passed */
\r
12963 DecrementClocks()
\r
12965 long timeRemaining;
\r
12966 long lastTickLength, fudge;
\r
12969 if (!appData.clockMode) return;
\r
12970 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12972 GetTimeMark(&now);
\r
12974 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12976 /* Fudge if we woke up a little too soon */
\r
12977 fudge = intendedTickLength - lastTickLength;
\r
12978 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12980 if (WhiteOnMove(forwardMostMove)) {
\r
12981 if(whiteNPS >= 0) lastTickLength = 0;
\r
12982 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12983 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12984 WhiteOnMove(currentMove));
\r
12986 if(blackNPS >= 0) lastTickLength = 0;
\r
12987 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
12988 DisplayBlackClock(blackTimeRemaining - fudge,
\r
12989 !WhiteOnMove(currentMove));
\r
12992 if (CheckFlags()) return;
\r
12994 tickStartTM = now;
\r
12995 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
12996 StartClockTimer(intendedTickLength);
\r
12998 /* if the time remaining has fallen below the alarm threshold, sound the
\r
12999 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13000 * with increment) the time remaining has increased to a level above the
\r
13001 * threshold, reset the alarm so it can sound again.
\r
13004 if (appData.icsActive && appData.icsAlarm) {
\r
13006 /* make sure we are dealing with the user's clock */
\r
13007 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13008 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13011 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13012 alarmSounded = FALSE;
\r
13013 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13014 PlayAlarmSound();
\r
13015 alarmSounded = TRUE;
\r
13021 /* A player has just moved, so stop the previously running
\r
13022 clock and (if in clock mode) start the other one.
\r
13023 We redisplay both clocks in case we're in ICS mode, because
\r
13024 ICS gives us an update to both clocks after every move.
\r
13025 Note that this routine is called *after* forwardMostMove
\r
13026 is updated, so the last fractional tick must be subtracted
\r
13027 from the color that is *not* on move now.
\r
13032 long lastTickLength;
\r
13034 int flagged = FALSE;
\r
13036 GetTimeMark(&now);
\r
13038 if (StopClockTimer() && appData.clockMode) {
\r
13039 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13040 if (WhiteOnMove(forwardMostMove)) {
\r
13041 if(blackNPS >= 0) lastTickLength = 0;
\r
13042 blackTimeRemaining -= lastTickLength;
\r
13043 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13044 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13045 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13046 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13048 if(whiteNPS >= 0) lastTickLength = 0;
\r
13049 whiteTimeRemaining -= lastTickLength;
\r
13050 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13051 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13052 pvInfoList[forwardMostMove-1].time =
\r
13053 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13055 flagged = CheckFlags();
\r
13057 CheckTimeControl();
\r
13059 if (flagged || !appData.clockMode) return;
\r
13061 switch (gameMode) {
\r
13062 case MachinePlaysBlack:
\r
13063 case MachinePlaysWhite:
\r
13064 case BeginningOfGame:
\r
13065 if (pausing) return;
\r
13069 case PlayFromGameFile:
\r
13070 case IcsExamining:
\r
13077 tickStartTM = now;
\r
13078 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13079 whiteTimeRemaining : blackTimeRemaining);
\r
13080 StartClockTimer(intendedTickLength);
\r
13084 /* Stop both clocks */
\r
13088 long lastTickLength;
\r
13091 if (!StopClockTimer()) return;
\r
13092 if (!appData.clockMode) return;
\r
13094 GetTimeMark(&now);
\r
13096 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13097 if (WhiteOnMove(forwardMostMove)) {
\r
13098 if(whiteNPS >= 0) lastTickLength = 0;
\r
13099 whiteTimeRemaining -= lastTickLength;
\r
13100 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13102 if(blackNPS >= 0) lastTickLength = 0;
\r
13103 blackTimeRemaining -= lastTickLength;
\r
13104 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13109 /* Start clock of player on move. Time may have been reset, so
\r
13110 if clock is already running, stop and restart it. */
\r
13114 (void) StopClockTimer(); /* in case it was running already */
\r
13115 DisplayBothClocks();
\r
13116 if (CheckFlags()) return;
\r
13118 if (!appData.clockMode) return;
\r
13119 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13121 GetTimeMark(&tickStartTM);
\r
13122 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13123 whiteTimeRemaining : blackTimeRemaining);
\r
13125 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13126 whiteNPS = blackNPS = -1;
\r
13127 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13128 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13129 whiteNPS = first.nps;
\r
13130 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13131 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13132 blackNPS = first.nps;
\r
13133 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13134 whiteNPS = second.nps;
\r
13135 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13136 blackNPS = second.nps;
\r
13137 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13139 StartClockTimer(intendedTickLength);
\r
13146 long second, minute, hour, day;
\r
13148 static char buf[32];
\r
13150 if (ms > 0 && ms <= 9900) {
\r
13151 /* convert milliseconds to tenths, rounding up */
\r
13152 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13154 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13158 /* convert milliseconds to seconds, rounding up */
\r
13159 /* use floating point to avoid strangeness of integer division
\r
13160 with negative dividends on many machines */
\r
13161 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13163 if (second < 0) {
\r
13165 second = -second;
\r
13168 day = second / (60 * 60 * 24);
\r
13169 second = second % (60 * 60 * 24);
\r
13170 hour = second / (60 * 60);
\r
13171 second = second % (60 * 60);
\r
13172 minute = second / 60;
\r
13173 second = second % 60;
\r
13176 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13177 sign, day, hour, minute, second);
\r
13178 else if (hour > 0)
\r
13179 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13181 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13188 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13191 StrStr(string, match)
\r
13192 char *string, *match;
\r
13196 length = strlen(match);
\r
13198 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13199 if (!strncmp(match, string, length))
\r
13206 StrCaseStr(string, match)
\r
13207 char *string, *match;
\r
13209 int i, j, length;
\r
13211 length = strlen(match);
\r
13213 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13214 for (j = 0; j < length; j++) {
\r
13215 if (ToLower(match[j]) != ToLower(string[j]))
\r
13218 if (j == length) return string;
\r
13224 #ifndef _amigados
\r
13226 StrCaseCmp(s1, s2)
\r
13232 c1 = ToLower(*s1++);
\r
13233 c2 = ToLower(*s2++);
\r
13234 if (c1 > c2) return 1;
\r
13235 if (c1 < c2) return -1;
\r
13236 if (c1 == NULLCHAR) return 0;
\r
13245 return isupper(c) ? tolower(c) : c;
\r
13253 return islower(c) ? toupper(c) : c;
\r
13255 #endif /* !_amigados */
\r
13263 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13270 StrSavePtr(s, savePtr)
\r
13271 char *s, **savePtr;
\r
13276 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13277 strcpy(*savePtr, s);
\r
13279 return(*savePtr);
\r
13287 char buf[MSG_SIZ];
\r
13289 clock = time((time_t *)NULL);
\r
13290 tm = localtime(&clock);
\r
13291 sprintf(buf, "%04d.%02d.%02d",
\r
13292 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13293 return StrSave(buf);
\r
13298 PositionToFEN(move, overrideCastling)
\r
13300 char *overrideCastling;
\r
13302 int i, j, fromX, fromY, toX, toY;
\r
13307 ChessSquare piece;
\r
13309 whiteToPlay = (gameMode == EditPosition) ?
\r
13310 !blackPlaysFirst : (move % 2 == 0);
\r
13313 /* Piece placement data */
\r
13314 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13316 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13317 if (boards[move][i][j] == EmptySquare) {
\r
13319 } else { ChessSquare piece = boards[move][i][j];
\r
13320 if (emptycount > 0) {
\r
13321 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13322 *p++ = '0' + emptycount;
\r
13323 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13326 if(PieceToChar(piece) == '+') {
\r
13327 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13329 piece = (ChessSquare)(DEMOTED piece);
\r
13331 *p++ = PieceToChar(piece);
\r
13332 if(p[-1] == '~') {
\r
13333 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13334 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13339 if (emptycount > 0) {
\r
13340 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13341 *p++ = '0' + emptycount;
\r
13342 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13349 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13350 if( gameInfo.holdingsWidth ) {
\r
13351 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13353 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13354 piece = boards[move][i][BOARD_WIDTH-1];
\r
13355 if( piece != EmptySquare )
\r
13356 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13357 *p++ = PieceToChar(piece);
\r
13359 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13360 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13361 if( piece != EmptySquare )
\r
13362 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13363 *p++ = PieceToChar(piece);
\r
13366 if( q == p ) *p++ = '-';
\r
13371 /* Active color */
\r
13372 *p++ = whiteToPlay ? 'w' : 'b';
\r
13375 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
\r
13376 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
\r
13378 if(nrCastlingRights) {
\r
13380 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13381 /* [HGM] write directly from rights */
\r
13382 if(castlingRights[move][2] >= 0 &&
\r
13383 castlingRights[move][0] >= 0 )
\r
13384 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13385 if(castlingRights[move][2] >= 0 &&
\r
13386 castlingRights[move][1] >= 0 )
\r
13387 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13388 if(castlingRights[move][5] >= 0 &&
\r
13389 castlingRights[move][3] >= 0 )
\r
13390 *p++ = castlingRights[move][3] + AAA;
\r
13391 if(castlingRights[move][5] >= 0 &&
\r
13392 castlingRights[move][4] >= 0 )
\r
13393 *p++ = castlingRights[move][4] + AAA;
\r
13396 /* [HGM] write true castling rights */
\r
13397 if( nrCastlingRights == 6 ) {
\r
13398 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13399 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13400 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13401 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13402 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13403 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13404 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13405 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13408 if (q == p) *p++ = '-'; /* No castling rights */
\r
13412 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13413 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13414 /* En passant target square */
\r
13415 if (move > backwardMostMove) {
\r
13416 fromX = moveList[move - 1][0] - AAA;
\r
13417 fromY = moveList[move - 1][1] - ONE;
\r
13418 toX = moveList[move - 1][2] - AAA;
\r
13419 toY = moveList[move - 1][3] - ONE;
\r
13420 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13421 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13422 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13424 /* 2-square pawn move just happened */
\r
13425 *p++ = toX + AAA;
\r
13426 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13437 /* [HGM] find reversible plies */
\r
13438 { int i = 0, j=move;
\r
13440 if (appData.debugMode) { int k;
\r
13441 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13442 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13443 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13447 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13448 if( j == backwardMostMove ) i += initialRulePlies;
\r
13449 sprintf(p, "%d ", i);
\r
13450 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13452 /* Fullmove number */
\r
13453 sprintf(p, "%d", (move / 2) + 1);
\r
13455 return StrSave(buf);
\r
13459 ParseFEN(board, blackPlaysFirst, fen)
\r
13461 int *blackPlaysFirst;
\r
13467 ChessSquare piece;
\r
13471 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13472 if(gameInfo.holdingsWidth) {
\r
13473 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13474 board[i][0] = EmptySquare; /* black holdings */
\r
13475 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13476 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13477 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13481 /* Piece placement data */
\r
13482 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13485 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13486 if (*p == '/') p++;
\r
13487 emptycount = gameInfo.boardWidth - j;
\r
13488 while (emptycount--)
\r
13489 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13491 #if(BOARD_SIZE >= 10)
\r
13492 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13493 p++; emptycount=10;
\r
13494 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13495 while (emptycount--)
\r
13496 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13498 } else if (isdigit(*p)) {
\r
13499 emptycount = *p++ - '0';
\r
13500 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13501 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13502 while (emptycount--)
\r
13503 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13504 } else if (*p == '+' || isalpha(*p)) {
\r
13505 if (j >= gameInfo.boardWidth) return FALSE;
\r
13507 piece = CharToPiece(*++p);
\r
13508 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13509 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13510 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13511 } else piece = CharToPiece(*p++);
\r
13513 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13514 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13515 piece = (ChessSquare) (PROMOTED piece);
\r
13516 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13519 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13525 while (*p == '/' || *p == ' ') p++;
\r
13527 /* [HGM] look for Crazyhouse holdings here */
\r
13528 while(*p==' ') p++;
\r
13529 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13530 if(*p == '[') p++;
\r
13531 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13532 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13533 /* if we would allow FEN reading to set board size, we would */
\r
13534 /* have to add holdings and shift the board read so far here */
\r
13535 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13537 if((int) piece >= (int) BlackPawn ) {
\r
13538 i = (int)piece - (int)BlackPawn;
\r
13539 i = PieceToNumber((ChessSquare)i);
\r
13540 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13541 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13542 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13544 i = (int)piece - (int)WhitePawn;
\r
13545 i = PieceToNumber((ChessSquare)i);
\r
13546 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13547 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13548 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13552 if(*p == ']') *p++;
\r
13555 while(*p == ' ') p++;
\r
13557 /* Active color */
\r
13560 *blackPlaysFirst = FALSE;
\r
13563 *blackPlaysFirst = TRUE;
\r
13569 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13570 /* return the extra info in global variiables */
\r
13572 /* set defaults in case FEN is incomplete */
\r
13573 FENepStatus = EP_UNKNOWN;
\r
13574 for(i=0; i<nrCastlingRights; i++ ) {
\r
13575 FENcastlingRights[i] =
\r
13576 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13577 } /* assume possible unless obviously impossible */
\r
13578 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13579 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13580 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13581 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13582 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13583 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13584 FENrulePlies = 0;
\r
13586 while(*p==' ') p++;
\r
13587 if(nrCastlingRights) {
\r
13588 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13589 /* castling indicator present, so default becomes no castlings */
\r
13590 for(i=0; i<nrCastlingRights; i++ ) {
\r
13591 FENcastlingRights[i] = -1;
\r
13594 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13595 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13596 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13597 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13598 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13600 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13601 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13602 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13606 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13607 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13608 FENcastlingRights[2] = whiteKingFile;
\r
13611 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13612 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13613 FENcastlingRights[2] = whiteKingFile;
\r
13616 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13617 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13618 FENcastlingRights[5] = blackKingFile;
\r
13621 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13622 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13623 FENcastlingRights[5] = blackKingFile;
\r
13626 default: /* FRC castlings */
\r
13627 if(c >= 'a') { /* black rights */
\r
13628 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13629 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13630 if(i == BOARD_RGHT) break;
\r
13631 FENcastlingRights[5] = i;
\r
13633 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13634 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13636 FENcastlingRights[3] = c;
\r
13638 FENcastlingRights[4] = c;
\r
13639 } else { /* white rights */
\r
13640 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13641 if(board[0][i] == WhiteKing) break;
\r
13642 if(i == BOARD_RGHT) break;
\r
13643 FENcastlingRights[2] = i;
\r
13644 c -= AAA - 'a' + 'A';
\r
13645 if(board[0][c] >= WhiteKing) break;
\r
13647 FENcastlingRights[0] = c;
\r
13649 FENcastlingRights[1] = c;
\r
13653 if (appData.debugMode) {
\r
13654 fprintf(debugFP, "FEN castling rights:");
\r
13655 for(i=0; i<nrCastlingRights; i++)
\r
13656 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13657 fprintf(debugFP, "\n");
\r
13660 while(*p==' ') p++;
\r
13663 /* read e.p. field in games that know e.p. capture */
\r
13664 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13665 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13667 p++; FENepStatus = EP_NONE;
\r
13669 char c = *p++ - AAA;
\r
13671 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13672 if(*p >= '0' && *p <='9') *p++;
\r
13678 if(sscanf(p, "%d", &i) == 1) {
\r
13679 FENrulePlies = i; /* 50-move ply counter */
\r
13680 /* (The move number is still ignored) */
\r
13687 EditPositionPasteFEN(char *fen)
\r
13689 if (fen != NULL) {
\r
13690 Board initial_position;
\r
13692 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13693 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13696 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13697 EditPositionEvent();
\r
13698 blackPlaysFirst = savedBlackPlaysFirst;
\r
13699 CopyBoard(boards[0], initial_position);
\r
13700 /* [HGM] copy FEN attributes as well */
\r
13702 initialRulePlies = FENrulePlies;
\r
13703 epStatus[0] = FENepStatus;
\r
13704 for( i=0; i<nrCastlingRights; i++ )
\r
13705 castlingRights[0][i] = FENcastlingRights[i];
\r
13707 EditPositionDone();
\r
13708 DisplayBothClocks();
\r
13709 DrawPosition(FALSE, boards[currentMove]);
\r