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
355 case VariantAtomic:
\r
356 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
358 case VariantKriegspiel:
\r
359 flags |= F_KRIEGSPIEL_CAPTURE;
\r
361 case VariantCapaRandom:
\r
362 case VariantFischeRandom:
\r
363 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
364 case VariantNoCastle:
\r
365 case VariantShatranj:
\r
366 case VariantCourier:
\r
367 flags &= ~F_ALL_CASTLE_OK;
\r
375 FILE *gameFileFP, *debugFP;
\r
378 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
379 into a fixed-size buffer. Because of this, we must be prepared to
\r
380 receive strings as long as the size of the input buffer, which is currently
\r
381 set to 4K for Windows and 8K for the rest.
\r
382 So, we must either allocate sufficiently large buffers here, or
\r
383 reduce the size of the input buffer in the input reading part.
\r
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
388 char thinkOutput1[MSG_SIZ*10];
\r
390 ChessProgramState first, second;
\r
392 /* premove variables */
\r
393 int premoveToX = 0;
\r
394 int premoveToY = 0;
\r
395 int premoveFromX = 0;
\r
396 int premoveFromY = 0;
\r
397 int premovePromoChar = 0;
\r
398 int gotPremove = 0;
\r
399 Boolean alarmSounded;
\r
400 /* end premove variables */
\r
402 char *ics_prefix = "$";
\r
403 int ics_type = ICS_GENERIC;
\r
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
406 int pauseExamForwardMostMove = 0;
\r
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
413 int whiteFlag = FALSE, blackFlag = FALSE;
\r
414 int userOfferedDraw = FALSE;
\r
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
417 int cmailMoveType[CMAIL_MAX_GAMES];
\r
418 long ics_clock_paused = 0;
\r
419 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
421 GameMode gameMode = BeginningOfGame;
\r
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
425 int hiddenThinkOutputState = 0; /* [AS] */
\r
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
427 int adjudicateLossPlies = 6;
\r
428 char white_holding[64], black_holding[64];
\r
429 TimeMark lastNodeCountTime;
\r
430 long lastNodeCount=0;
\r
431 int have_sent_ICS_logon = 0;
\r
432 int movesPerSession;
\r
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
434 long timeControl_2; /* [AS] Allow separate time controls */
\r
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
436 long timeRemaining[2][MAX_MOVES];
\r
438 TimeMark programStartTime;
\r
439 char ics_handle[MSG_SIZ];
\r
440 int have_set_title = 0;
\r
442 /* animateTraining preserves the state of appData.animate
\r
443 * when Training mode is activated. This allows the
\r
444 * response to be animated when appData.animate == TRUE and
\r
445 * appData.animateDragging == TRUE.
\r
447 Boolean animateTraining;
\r
453 Board boards[MAX_MOVES];
\r
454 /* [HGM] Following 7 needed for accurate legality tests: */
\r
455 char epStatus[MAX_MOVES];
\r
456 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
457 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
458 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
459 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
460 int initialRulePlies, FENrulePlies;
\r
462 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
464 int shuffleOpenings;
\r
466 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
467 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
468 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
469 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
470 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
473 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
475 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
477 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
480 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
481 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
482 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
483 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
484 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
487 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
488 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
489 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
490 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
491 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
494 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
495 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
496 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
497 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
498 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
502 #if (BOARD_SIZE>=10)
\r
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
504 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
505 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
506 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
507 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
511 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
512 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
513 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
514 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
518 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
519 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
520 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
521 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
525 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
526 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
527 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
528 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
532 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
533 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
534 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
535 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
541 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
542 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
543 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
546 #define GothicArray CapablancaArray
\r
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
552 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
553 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
554 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
557 #define FalconArray CapablancaArray
\r
560 #else // !(BOARD_SIZE>=10)
\r
561 #define XiangqiPosition FIDEArray
\r
562 #define CapablancaArray FIDEArray
\r
563 #define GothicArray FIDEArray
\r
564 #define GreatArray FIDEArray
\r
565 #endif // !(BOARD_SIZE>=10)
\r
567 #if (BOARD_SIZE>=12)
\r
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
570 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
571 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
572 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
574 #else // !(BOARD_SIZE>=12)
\r
575 #define CourierArray CapablancaArray
\r
576 #endif // !(BOARD_SIZE>=12)
\r
579 Board initialPosition;
\r
582 /* Convert str to a rating. Checks for special cases of "----",
\r
584 "++++", etc. Also strips ()'s */
\r
586 string_to_rating(str)
\r
589 while(*str && !isdigit(*str)) ++str;
\r
591 return 0; /* One of the special "no rating" cases */
\r
597 ClearProgramStats()
\r
599 /* Init programStats */
\r
600 programStats.movelist[0] = 0;
\r
601 programStats.depth = 0;
\r
602 programStats.nr_moves = 0;
\r
603 programStats.moves_left = 0;
\r
604 programStats.nodes = 0;
\r
605 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
606 programStats.score = 0;
\r
607 programStats.got_only_move = 0;
\r
608 programStats.got_fail = 0;
\r
609 programStats.line_is_book = 0;
\r
615 int matched, min, sec;
\r
617 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
619 GetTimeMark(&programStartTime);
\r
621 ClearProgramStats();
\r
622 programStats.ok_to_send = 1;
\r
623 programStats.seen_stat = 0;
\r
626 * Initialize game list
\r
628 ListNew(&gameList);
\r
632 * Internet chess server status
\r
634 if (appData.icsActive) {
\r
635 appData.matchMode = FALSE;
\r
636 appData.matchGames = 0;
\r
638 appData.noChessProgram = !appData.zippyPlay;
\r
640 appData.zippyPlay = FALSE;
\r
641 appData.zippyTalk = FALSE;
\r
642 appData.noChessProgram = TRUE;
\r
644 if (*appData.icsHelper != NULLCHAR) {
\r
645 appData.useTelnet = TRUE;
\r
646 appData.telnetProgram = appData.icsHelper;
\r
649 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
652 /* [AS] Initialize pv info list [HGM] and game state */
\r
656 for( i=0; i<MAX_MOVES; i++ ) {
\r
657 pvInfoList[i].depth = -1;
\r
658 epStatus[i]=EP_NONE;
\r
659 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
664 * Parse timeControl resource
\r
666 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
667 appData.movesPerSession)) {
\r
669 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
670 DisplayFatalError(buf, 0, 2);
\r
674 * Parse searchTime resource
\r
676 if (*appData.searchTime != NULLCHAR) {
\r
677 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
678 if (matched == 1) {
\r
679 searchTime = min * 60;
\r
680 } else if (matched == 2) {
\r
681 searchTime = min * 60 + sec;
\r
684 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
685 DisplayFatalError(buf, 0, 2);
\r
689 /* [AS] Adjudication threshold */
\r
690 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
692 first.which = "first";
\r
693 second.which = "second";
\r
694 first.maybeThinking = second.maybeThinking = FALSE;
\r
695 first.pr = second.pr = NoProc;
\r
696 first.isr = second.isr = NULL;
\r
697 first.sendTime = second.sendTime = 2;
\r
698 first.sendDrawOffers = 1;
\r
699 if (appData.firstPlaysBlack) {
\r
700 first.twoMachinesColor = "black\n";
\r
701 second.twoMachinesColor = "white\n";
\r
703 first.twoMachinesColor = "white\n";
\r
704 second.twoMachinesColor = "black\n";
\r
706 first.program = appData.firstChessProgram;
\r
707 second.program = appData.secondChessProgram;
\r
708 first.host = appData.firstHost;
\r
709 second.host = appData.secondHost;
\r
710 first.dir = appData.firstDirectory;
\r
711 second.dir = appData.secondDirectory;
\r
712 first.other = &second;
\r
713 second.other = &first;
\r
714 first.initString = appData.initString;
\r
715 second.initString = appData.secondInitString;
\r
716 first.computerString = appData.firstComputerString;
\r
717 second.computerString = appData.secondComputerString;
\r
718 first.useSigint = second.useSigint = TRUE;
\r
719 first.useSigterm = second.useSigterm = TRUE;
\r
720 first.reuse = appData.reuseFirst;
\r
721 second.reuse = appData.reuseSecond;
\r
722 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
723 second.nps = appData.secondNPS;
\r
724 first.useSetboard = second.useSetboard = FALSE;
\r
725 first.useSAN = second.useSAN = FALSE;
\r
726 first.usePing = second.usePing = FALSE;
\r
727 first.lastPing = second.lastPing = 0;
\r
728 first.lastPong = second.lastPong = 0;
\r
729 first.usePlayother = second.usePlayother = FALSE;
\r
730 first.useColors = second.useColors = TRUE;
\r
731 first.useUsermove = second.useUsermove = FALSE;
\r
732 first.sendICS = second.sendICS = FALSE;
\r
733 first.sendName = second.sendName = appData.icsActive;
\r
734 first.sdKludge = second.sdKludge = FALSE;
\r
735 first.stKludge = second.stKludge = FALSE;
\r
736 TidyProgramName(first.program, first.host, first.tidy);
\r
737 TidyProgramName(second.program, second.host, second.tidy);
\r
738 first.matchWins = second.matchWins = 0;
\r
739 strcpy(first.variants, appData.variant);
\r
740 strcpy(second.variants, appData.variant);
\r
741 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
742 first.analyzing = second.analyzing = FALSE;
\r
743 first.initDone = second.initDone = FALSE;
\r
745 /* New features added by Tord: */
\r
746 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
747 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
748 /* End of new features added by Tord. */
\r
750 /* [HGM] time odds: set factor for each machine */
\r
751 first.timeOdds = appData.firstTimeOdds;
\r
752 second.timeOdds = appData.secondTimeOdds;
\r
754 if(appData.timeOddsMode) {
\r
755 norm = first.timeOdds;
\r
756 if(norm > second.timeOdds) norm = second.timeOdds;
\r
758 first.timeOdds /= norm;
\r
759 second.timeOdds /= norm;
\r
762 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
763 first.accumulateTC = appData.firstAccumulateTC;
\r
764 second.accumulateTC = appData.secondAccumulateTC;
\r
765 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
768 first.debug = second.debug = FALSE;
\r
769 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
771 /* [HGM] options */
\r
772 first.optionSettings = appData.firstOptions;
\r
773 second.optionSettings = appData.secondOptions;
\r
775 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
776 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
777 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
778 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
779 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
780 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
782 if (appData.firstProtocolVersion > PROTOVER ||
\r
783 appData.firstProtocolVersion < 1) {
\r
785 sprintf(buf, _("protocol version %d not supported"),
\r
786 appData.firstProtocolVersion);
\r
787 DisplayFatalError(buf, 0, 2);
\r
789 first.protocolVersion = appData.firstProtocolVersion;
\r
792 if (appData.secondProtocolVersion > PROTOVER ||
\r
793 appData.secondProtocolVersion < 1) {
\r
795 sprintf(buf, _("protocol version %d not supported"),
\r
796 appData.secondProtocolVersion);
\r
797 DisplayFatalError(buf, 0, 2);
\r
799 second.protocolVersion = appData.secondProtocolVersion;
\r
802 if (appData.icsActive) {
\r
803 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
804 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
805 appData.clockMode = FALSE;
\r
806 first.sendTime = second.sendTime = 0;
\r
810 /* Override some settings from environment variables, for backward
\r
811 compatibility. Unfortunately it's not feasible to have the env
\r
812 vars just set defaults, at least in xboard. Ugh.
\r
814 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
819 if (appData.noChessProgram) {
\r
820 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
821 + strlen(PATCHLEVEL));
\r
822 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
827 while (*q != ' ' && *q != NULLCHAR) q++;
\r
829 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
830 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
831 + strlen(PATCHLEVEL) + (q - p));
\r
832 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
833 strncat(programVersion, p, q - p);
\r
835 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
836 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
837 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
838 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
842 if (!appData.icsActive) {
\r
844 /* Check for variants that are supported only in ICS mode,
\r
845 or not at all. Some that are accepted here nevertheless
\r
846 have bugs; see comments below.
\r
848 VariantClass variant = StringToVariant(appData.variant);
\r
850 case VariantBughouse: /* need four players and two boards */
\r
851 case VariantKriegspiel: /* need to hide pieces and move details */
\r
852 /* case VariantFischeRandom: (Fabien: moved below) */
\r
853 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
854 DisplayFatalError(buf, 0, 2);
\r
857 case VariantUnknown:
\r
858 case VariantLoadable:
\r
868 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
869 DisplayFatalError(buf, 0, 2);
\r
872 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
873 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
874 case VariantGothic: /* [HGM] should work */
\r
875 case VariantCapablanca: /* [HGM] should work */
\r
876 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
877 case VariantShogi: /* [HGM] drops not tested for legality */
\r
878 case VariantKnightmate: /* [HGM] should work */
\r
879 case VariantCylinder: /* [HGM] untested */
\r
880 case VariantFalcon: /* [HGM] untested */
\r
881 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
882 offboard interposition not understood */
\r
883 case VariantNormal: /* definitely works! */
\r
884 case VariantWildCastle: /* pieces not automatically shuffled */
\r
885 case VariantNoCastle: /* pieces not automatically shuffled */
\r
886 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
887 case VariantLosers: /* should work except for win condition,
\r
888 and doesn't know captures are mandatory */
\r
889 case VariantSuicide: /* should work except for win condition,
\r
890 and doesn't know captures are mandatory */
\r
891 case VariantGiveaway: /* should work except for win condition,
\r
892 and doesn't know captures are mandatory */
\r
893 case VariantTwoKings: /* should work */
\r
894 case VariantAtomic: /* should work except for win condition */
\r
895 case Variant3Check: /* should work except for win condition */
\r
896 case VariantShatranj: /* should work except for all win conditions */
\r
897 case VariantBerolina: /* might work if TestLegality is off */
\r
898 case VariantCapaRandom: /* should work */
\r
899 case VariantJanus: /* should work */
\r
900 case VariantSuper: /* experimental */
\r
901 case VariantGreat: /* experimental, requires legality testing to be off */
\r
906 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
907 InitEngineUCI( installDir, &second );
\r
910 int NextIntegerFromString( char ** str, long * value )
\r
915 while( *s == ' ' || *s == '\t' ) {
\r
921 if( *s >= '0' && *s <= '9' ) {
\r
922 while( *s >= '0' && *s <= '9' ) {
\r
923 *value = *value * 10 + (*s - '0');
\r
935 int NextTimeControlFromString( char ** str, long * value )
\r
938 int result = NextIntegerFromString( str, &temp );
\r
940 if( result == 0 ) {
\r
941 *value = temp * 60; /* Minutes */
\r
942 if( **str == ':' ) {
\r
944 result = NextIntegerFromString( str, &temp );
\r
945 *value += temp; /* Seconds */
\r
952 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
953 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
954 int result = -1; long temp, temp2;
\r
956 if(**str != '+') return -1; // old params remain in force!
\r
958 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
961 /* time only: incremental or sudden-death time control */
\r
962 if(**str == '+') { /* increment follows; read it */
\r
964 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
965 *inc = temp2 * 1000;
\r
967 *moves = 0; *tc = temp * 1000;
\r
969 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
971 (*str)++; /* classical time control */
\r
972 result = NextTimeControlFromString( str, &temp2);
\r
975 *tc = temp2 * 1000;
\r
981 int GetTimeQuota(int movenr)
\r
982 { /* [HGM] get time to add from the multi-session time-control string */
\r
983 int moves=1; /* kludge to force reading of first session */
\r
984 long time, increment;
\r
985 char *s = fullTimeControlString;
\r
987 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
989 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
990 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
991 if(movenr == -1) return time; /* last move before new session */
\r
992 if(!moves) return increment; /* current session is incremental */
\r
993 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
994 } while(movenr >= -1); /* try again for next session */
\r
996 return 0; // no new time quota on this move
\r
1000 ParseTimeControl(tc, ti, mps)
\r
1006 int matched, min, sec;
\r
1008 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1009 if (matched == 1) {
\r
1010 timeControl = min * 60 * 1000;
\r
1011 } else if (matched == 2) {
\r
1012 timeControl = (min * 60 + sec) * 1000;
\r
1019 char buf[MSG_SIZ];
\r
1021 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1024 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1025 else sprintf(buf, "+%s+%d", tc, ti);
\r
1028 sprintf(buf, "+%d/%s", mps, tc);
\r
1029 else sprintf(buf, "+%s", tc);
\r
1031 fullTimeControlString = StrSave(buf);
\r
1033 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1037 if( *tc == '/' ) {
\r
1038 /* Parse second time control */
\r
1041 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1049 timeControl_2 = tc2 * 1000;
\r
1052 timeControl_2 = 0;
\r
1059 timeControl = tc1 * 1000;
\r
1063 timeIncrement = ti * 1000; /* convert to ms */
\r
1064 movesPerSession = 0;
\r
1066 timeIncrement = 0;
\r
1067 movesPerSession = mps;
\r
1075 if (appData.debugMode) {
\r
1076 fprintf(debugFP, "%s\n", programVersion);
\r
1079 if (appData.matchGames > 0) {
\r
1080 appData.matchMode = TRUE;
\r
1081 } else if (appData.matchMode) {
\r
1082 appData.matchGames = 1;
\r
1084 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1085 appData.matchGames = appData.sameColorGames;
\r
1086 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1087 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1088 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1090 Reset(TRUE, FALSE);
\r
1091 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1094 /* kludge: allow timeout for initial "feature" commands */
\r
1096 DisplayMessage("", _("Starting chess program"));
\r
1097 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1102 InitBackEnd3 P((void))
\r
1104 GameMode initialMode;
\r
1105 char buf[MSG_SIZ];
\r
1108 InitChessProgram(&first, startedFromSetupPosition);
\r
1111 if (appData.icsActive) {
\r
1113 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1116 err = establish();
\r
1118 if (*appData.icsCommPort != NULLCHAR) {
\r
1119 sprintf(buf, _("Could not open comm port %s"),
\r
1120 appData.icsCommPort);
\r
1122 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1123 appData.icsHost, appData.icsPort);
\r
1125 DisplayFatalError(buf, err, 1);
\r
1130 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1132 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1133 } else if (appData.noChessProgram) {
\r
1139 if (*appData.cmailGameName != NULLCHAR) {
\r
1141 OpenLoopback(&cmailPR);
\r
1143 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1147 DisplayMessage("", "");
\r
1148 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1149 initialMode = BeginningOfGame;
\r
1150 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1151 initialMode = TwoMachinesPlay;
\r
1152 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1153 initialMode = AnalyzeFile;
\r
1154 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1155 initialMode = AnalyzeMode;
\r
1156 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1157 initialMode = MachinePlaysWhite;
\r
1158 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1159 initialMode = MachinePlaysBlack;
\r
1160 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1161 initialMode = EditGame;
\r
1162 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1163 initialMode = EditPosition;
\r
1164 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1165 initialMode = Training;
\r
1167 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1168 DisplayFatalError(buf, 0, 2);
\r
1172 if (appData.matchMode) {
\r
1173 /* Set up machine vs. machine match */
\r
1174 if (appData.noChessProgram) {
\r
1175 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1181 if (*appData.loadGameFile != NULLCHAR) {
\r
1182 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1183 if(index<0) lastIndex = index = 1;
\r
1184 if (!LoadGameFromFile(appData.loadGameFile,
\r
1186 appData.loadGameFile, FALSE)) {
\r
1187 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1190 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1191 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1192 if(index<0) lastIndex = index = 1;
\r
1193 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1195 appData.loadPositionFile)) {
\r
1196 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1200 TwoMachinesEvent();
\r
1201 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1202 /* Set up cmail mode */
\r
1203 ReloadCmailMsgEvent(TRUE);
\r
1205 /* Set up other modes */
\r
1206 if (initialMode == AnalyzeFile) {
\r
1207 if (*appData.loadGameFile == NULLCHAR) {
\r
1208 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1212 if (*appData.loadGameFile != NULLCHAR) {
\r
1213 (void) LoadGameFromFile(appData.loadGameFile,
\r
1214 appData.loadGameIndex,
\r
1215 appData.loadGameFile, TRUE);
\r
1216 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1217 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1218 appData.loadPositionIndex,
\r
1219 appData.loadPositionFile);
\r
1220 /* [HGM] try to make self-starting even after FEN load */
\r
1221 /* to allow automatic setup of fairy variants with wtm */
\r
1222 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1223 gameMode = BeginningOfGame;
\r
1224 setboardSpoiledMachineBlack = 1;
\r
1226 /* [HGM] loadPos: make that every new game uses the setup */
\r
1227 /* from file as long as we do not switch variant */
\r
1228 if(!blackPlaysFirst) { int i;
\r
1229 startedFromPositionFile = TRUE;
\r
1230 CopyBoard(filePosition, boards[0]);
\r
1231 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1234 if (initialMode == AnalyzeMode) {
\r
1235 if (appData.noChessProgram) {
\r
1236 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1239 if (appData.icsActive) {
\r
1240 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1243 AnalyzeModeEvent();
\r
1244 } else if (initialMode == AnalyzeFile) {
\r
1245 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1246 ShowThinkingEvent();
\r
1247 AnalyzeFileEvent();
\r
1248 AnalysisPeriodicEvent(1);
\r
1249 } else if (initialMode == MachinePlaysWhite) {
\r
1250 if (appData.noChessProgram) {
\r
1251 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1255 if (appData.icsActive) {
\r
1256 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1260 MachineWhiteEvent();
\r
1261 } else if (initialMode == MachinePlaysBlack) {
\r
1262 if (appData.noChessProgram) {
\r
1263 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1267 if (appData.icsActive) {
\r
1268 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1272 MachineBlackEvent();
\r
1273 } else if (initialMode == TwoMachinesPlay) {
\r
1274 if (appData.noChessProgram) {
\r
1275 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1279 if (appData.icsActive) {
\r
1280 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1284 TwoMachinesEvent();
\r
1285 } else if (initialMode == EditGame) {
\r
1287 } else if (initialMode == EditPosition) {
\r
1288 EditPositionEvent();
\r
1289 } else if (initialMode == Training) {
\r
1290 if (*appData.loadGameFile == NULLCHAR) {
\r
1291 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1300 * Establish will establish a contact to a remote host.port.
\r
1301 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1302 * used to talk to the host.
\r
1303 * Returns 0 if okay, error code if not.
\r
1308 char buf[MSG_SIZ];
\r
1310 if (*appData.icsCommPort != NULLCHAR) {
\r
1311 /* Talk to the host through a serial comm port */
\r
1312 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1314 } else if (*appData.gateway != NULLCHAR) {
\r
1315 if (*appData.remoteShell == NULLCHAR) {
\r
1316 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1317 sprintf(buf, "%s %s %s",
\r
1318 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1319 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1322 /* Use the rsh program to run telnet program on a gateway host */
\r
1323 if (*appData.remoteUser == NULLCHAR) {
\r
1324 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1325 appData.gateway, appData.telnetProgram,
\r
1326 appData.icsHost, appData.icsPort);
\r
1328 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1329 appData.remoteShell, appData.gateway,
\r
1330 appData.remoteUser, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 return StartChildProcess(buf, "", &icsPR);
\r
1336 } else if (appData.useTelnet) {
\r
1337 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1340 /* TCP socket interface differs somewhat between
\r
1341 Unix and NT; handle details in the front end.
\r
1343 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1348 show_bytes(fp, buf, count)
\r
1354 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1355 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1364 /* Returns an errno value */
\r
1366 OutputMaybeTelnet(pr, message, count, outError)
\r
1372 char buf[8192], *p, *q, *buflim;
\r
1373 int left, newcount, outcount;
\r
1375 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1376 *appData.gateway != NULLCHAR) {
\r
1377 if (appData.debugMode) {
\r
1378 fprintf(debugFP, ">ICS: ");
\r
1379 show_bytes(debugFP, message, count);
\r
1380 fprintf(debugFP, "\n");
\r
1382 return OutputToProcess(pr, message, count, outError);
\r
1385 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1391 if (q >= buflim) {
\r
1392 if (appData.debugMode) {
\r
1393 fprintf(debugFP, ">ICS: ");
\r
1394 show_bytes(debugFP, buf, newcount);
\r
1395 fprintf(debugFP, "\n");
\r
1397 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1398 if (outcount < newcount) return -1; /* to be sure */
\r
1405 } else if (((unsigned char) *p) == TN_IAC) {
\r
1406 *q++ = (char) TN_IAC;
\r
1413 if (appData.debugMode) {
\r
1414 fprintf(debugFP, ">ICS: ");
\r
1415 show_bytes(debugFP, buf, newcount);
\r
1416 fprintf(debugFP, "\n");
\r
1418 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1419 if (outcount < newcount) return -1; /* to be sure */
\r
1424 read_from_player(isr, closure, message, count, error)
\r
1425 InputSourceRef isr;
\r
1431 int outError, outCount;
\r
1432 static int gotEof = 0;
\r
1434 /* Pass data read from player on to ICS */
\r
1437 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1438 if (outCount < count) {
\r
1439 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1441 } else if (count < 0) {
\r
1442 RemoveInputSource(isr);
\r
1443 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1444 } else if (gotEof++ > 0) {
\r
1445 RemoveInputSource(isr);
\r
1446 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1454 int count, outCount, outError;
\r
1456 if (icsPR == NULL) return;
\r
1458 count = strlen(s);
\r
1459 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1460 if (outCount < count) {
\r
1461 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1465 /* This is used for sending logon scripts to the ICS. Sending
\r
1466 without a delay causes problems when using timestamp on ICC
\r
1467 (at least on my machine). */
\r
1469 SendToICSDelayed(s,msdelay)
\r
1473 int count, outCount, outError;
\r
1475 if (icsPR == NULL) return;
\r
1477 count = strlen(s);
\r
1478 if (appData.debugMode) {
\r
1479 fprintf(debugFP, ">ICS: ");
\r
1480 show_bytes(debugFP, s, count);
\r
1481 fprintf(debugFP, "\n");
\r
1483 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1485 if (outCount < count) {
\r
1486 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1491 /* Remove all highlighting escape sequences in s
\r
1492 Also deletes any suffix starting with '('
\r
1495 StripHighlightAndTitle(s)
\r
1498 static char retbuf[MSG_SIZ];
\r
1501 while (*s != NULLCHAR) {
\r
1502 while (*s == '\033') {
\r
1503 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1504 if (*s != NULLCHAR) s++;
\r
1506 while (*s != NULLCHAR && *s != '\033') {
\r
1507 if (*s == '(' || *s == '[') {
\r
1518 /* Remove all highlighting escape sequences in s */
\r
1523 static char retbuf[MSG_SIZ];
\r
1526 while (*s != NULLCHAR) {
\r
1527 while (*s == '\033') {
\r
1528 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1529 if (*s != NULLCHAR) s++;
\r
1531 while (*s != NULLCHAR && *s != '\033') {
\r
1539 char *variantNames[] = VARIANT_NAMES;
\r
1544 return variantNames[v];
\r
1548 /* Identify a variant from the strings the chess servers use or the
\r
1549 PGN Variant tag names we use. */
\r
1551 StringToVariant(e)
\r
1556 VariantClass v = VariantNormal;
\r
1557 int i, found = FALSE;
\r
1558 char buf[MSG_SIZ];
\r
1562 /* [HGM] skip over optional board-size prefixes */
\r
1563 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1564 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1565 while( *e++ != '_');
\r
1568 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1569 if (StrCaseStr(e, variantNames[i])) {
\r
1570 v = (VariantClass) i;
\r
1577 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1578 || StrCaseStr(e, "wild/fr")
\r
1579 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1580 v = VariantFischeRandom;
\r
1581 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1582 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1584 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1585 if (isdigit(*p)) {
\r
1591 case 0: /* FICS only, actually */
\r
1593 /* Castling legal even if K starts on d-file */
\r
1594 v = VariantWildCastle;
\r
1599 /* Castling illegal even if K & R happen to start in
\r
1600 normal positions. */
\r
1601 v = VariantNoCastle;
\r
1614 /* Castling legal iff K & R start in normal positions */
\r
1615 v = VariantNormal;
\r
1620 /* Special wilds for position setup; unclear what to do here */
\r
1621 v = VariantLoadable;
\r
1624 /* Bizarre ICC game */
\r
1625 v = VariantTwoKings;
\r
1628 v = VariantKriegspiel;
\r
1631 v = VariantLosers;
\r
1634 v = VariantFischeRandom;
\r
1637 v = VariantCrazyhouse;
\r
1640 v = VariantBughouse;
\r
1643 v = Variant3Check;
\r
1646 /* Not quite the same as FICS suicide! */
\r
1647 v = VariantGiveaway;
\r
1650 v = VariantAtomic;
\r
1653 v = VariantShatranj;
\r
1656 /* Temporary names for future ICC types. The name *will* change in
\r
1657 the next xboard/WinBoard release after ICC defines it. */
\r
1686 v = VariantXiangqi;
\r
1689 v = VariantCourier;
\r
1692 v = VariantGothic;
\r
1695 v = VariantCapablanca;
\r
1698 v = VariantKnightmate;
\r
1704 v = VariantCylinder;
\r
1707 v = VariantFalcon;
\r
1710 v = VariantCapaRandom;
\r
1713 v = VariantBerolina;
\r
1725 /* Found "wild" or "w" in the string but no number;
\r
1726 must assume it's normal chess. */
\r
1727 v = VariantNormal;
\r
1730 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1731 DisplayError(buf, 0);
\r
1732 v = VariantUnknown;
\r
1737 if (appData.debugMode) {
\r
1738 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1739 e, wnum, VariantName(v));
\r
1744 static int leftover_start = 0, leftover_len = 0;
\r
1745 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1747 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1748 advance *index beyond it, and set leftover_start to the new value of
\r
1749 *index; else return FALSE. If pattern contains the character '*', it
\r
1750 matches any sequence of characters not containing '\r', '\n', or the
\r
1751 character following the '*' (if any), and the matched sequence(s) are
\r
1752 copied into star_match.
\r
1755 looking_at(buf, index, pattern)
\r
1760 char *bufp = &buf[*index], *patternp = pattern;
\r
1761 int star_count = 0;
\r
1762 char *matchp = star_match[0];
\r
1765 if (*patternp == NULLCHAR) {
\r
1766 *index = leftover_start = bufp - buf;
\r
1767 *matchp = NULLCHAR;
\r
1770 if (*bufp == NULLCHAR) return FALSE;
\r
1771 if (*patternp == '*') {
\r
1772 if (*bufp == *(patternp + 1)) {
\r
1773 *matchp = NULLCHAR;
\r
1774 matchp = star_match[++star_count];
\r
1778 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1780 if (*patternp == NULLCHAR)
\r
1785 *matchp++ = *bufp++;
\r
1789 if (*patternp != *bufp) return FALSE;
\r
1796 SendToPlayer(data, length)
\r
1800 int error, outCount;
\r
1801 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1802 if (outCount < length) {
\r
1803 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1808 PackHolding(packed, holding)
\r
1812 char *p = holding;
\r
1814 int runlength = 0;
\r
1820 switch (runlength) {
\r
1831 sprintf(q, "%d", runlength);
\r
1843 /* Telnet protocol requests from the front end */
\r
1845 TelnetRequest(ddww, option)
\r
1846 unsigned char ddww, option;
\r
1848 unsigned char msg[3];
\r
1849 int outCount, outError;
\r
1851 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1853 if (appData.debugMode) {
\r
1854 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1870 sprintf(buf1, "%d", ddww);
\r
1875 optionStr = "ECHO";
\r
1879 sprintf(buf2, "%d", option);
\r
1882 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1887 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1888 if (outCount < 3) {
\r
1889 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1896 if (!appData.icsActive) return;
\r
1897 TelnetRequest(TN_DO, TN_ECHO);
\r
1903 if (!appData.icsActive) return;
\r
1904 TelnetRequest(TN_DONT, TN_ECHO);
\r
1908 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1910 /* put the holdings sent to us by the server on the board holdings area */
\r
1911 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1913 ChessSquare piece;
\r
1915 if(gameInfo.holdingsWidth < 2) return;
\r
1917 if( (int)lowestPiece >= BlackPawn ) {
\r
1918 holdingsColumn = 0;
\r
1920 holdingsStartRow = BOARD_HEIGHT-1;
\r
1923 holdingsColumn = BOARD_WIDTH-1;
\r
1924 countsColumn = BOARD_WIDTH-2;
\r
1925 holdingsStartRow = 0;
\r
1929 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1930 board[i][holdingsColumn] = EmptySquare;
\r
1931 board[i][countsColumn] = (ChessSquare) 0;
\r
1933 while( (p=*holdings++) != NULLCHAR ) {
\r
1934 piece = CharToPiece( ToUpper(p) );
\r
1935 if(piece == EmptySquare) continue;
\r
1936 /*j = (int) piece - (int) WhitePawn;*/
\r
1937 j = PieceToNumber(piece);
\r
1938 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1939 if(j < 0) continue; /* should not happen */
\r
1940 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1941 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1942 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1949 VariantSwitch(Board board, VariantClass newVariant)
\r
1951 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1952 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1953 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1955 startedFromPositionFile = FALSE;
\r
1956 if(gameInfo.variant == newVariant) return;
\r
1958 /* [HGM] This routine is called each time an assignment is made to
\r
1959 * gameInfo.variant during a game, to make sure the board sizes
\r
1960 * are set to match the new variant. If that means adding or deleting
\r
1961 * holdings, we shift the playing board accordingly
\r
1962 * This kludge is needed because in ICS observe mode, we get boards
\r
1963 * of an ongoing game without knowing the variant, and learn about the
\r
1964 * latter only later. This can be because of the move list we requested,
\r
1965 * in which case the game history is refilled from the beginning anyway,
\r
1966 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1967 * case we want to add those holdings to the already received position.
\r
1971 if (appData.debugMode) {
\r
1972 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1973 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1974 setbuf(debugFP, NULL);
\r
1976 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1977 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1978 switch(newVariant) {
\r
1979 case VariantShogi:
\r
1980 newWidth = 9; newHeight = 9;
\r
1981 gameInfo.holdingsSize = 7;
\r
1982 case VariantBughouse:
\r
1983 case VariantCrazyhouse:
\r
1984 newHoldingsWidth = 2; break;
\r
1986 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1989 if(newWidth != gameInfo.boardWidth ||
\r
1990 newHeight != gameInfo.boardHeight ||
\r
1991 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1993 /* shift position to new playing area, if needed */
\r
1994 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1995 for(i=0; i<BOARD_HEIGHT; i++)
\r
1996 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1999 for(i=0; i<newHeight; i++) {
\r
2000 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2001 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2003 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2004 for(i=0; i<BOARD_HEIGHT; i++)
\r
2005 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2006 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2010 gameInfo.boardWidth = newWidth;
\r
2011 gameInfo.boardHeight = newHeight;
\r
2012 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2013 gameInfo.variant = newVariant;
\r
2014 InitDrawingSizes(-2, 0);
\r
2016 /* [HGM] The following should definitely be solved in a better way */
\r
2018 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2019 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2020 saveEP = epStatus[0];
\r
2022 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2024 epStatus[0] = saveEP;
\r
2025 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2026 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2028 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2030 forwardMostMove = oldForwardMostMove;
\r
2031 backwardMostMove = oldBackwardMostMove;
\r
2032 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2035 static int loggedOn = FALSE;
\r
2037 /*-- Game start info cache: --*/
\r
2039 char gs_kind[MSG_SIZ];
\r
2040 static char player1Name[128] = "";
\r
2041 static char player2Name[128] = "";
\r
2042 static int player1Rating = -1;
\r
2043 static int player2Rating = -1;
\r
2044 /*----------------------------*/
\r
2046 ColorClass curColor = ColorNormal;
\r
2047 int suppressKibitz = 0;
\r
2050 read_from_ics(isr, closure, data, count, error)
\r
2051 InputSourceRef isr;
\r
2057 #define BUF_SIZE 8192
\r
2058 #define STARTED_NONE 0
\r
2059 #define STARTED_MOVES 1
\r
2060 #define STARTED_BOARD 2
\r
2061 #define STARTED_OBSERVE 3
\r
2062 #define STARTED_HOLDINGS 4
\r
2063 #define STARTED_CHATTER 5
\r
2064 #define STARTED_COMMENT 6
\r
2065 #define STARTED_MOVES_NOHIDE 7
\r
2067 static int started = STARTED_NONE;
\r
2068 static char parse[20000];
\r
2069 static int parse_pos = 0;
\r
2070 static char buf[BUF_SIZE + 1];
\r
2071 static int firstTime = TRUE, intfSet = FALSE;
\r
2072 static ColorClass prevColor = ColorNormal;
\r
2073 static int savingComment = FALSE;
\r
2079 int backup; /* [DM] For zippy color lines */
\r
2082 if (appData.debugMode) {
\r
2084 fprintf(debugFP, "<ICS: ");
\r
2085 show_bytes(debugFP, data, count);
\r
2086 fprintf(debugFP, "\n");
\r
2090 if (appData.debugMode) { int f = forwardMostMove;
\r
2091 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2092 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2095 /* If last read ended with a partial line that we couldn't parse,
\r
2096 prepend it to the new read and try again. */
\r
2097 if (leftover_len > 0) {
\r
2098 for (i=0; i<leftover_len; i++)
\r
2099 buf[i] = buf[leftover_start + i];
\r
2102 /* Copy in new characters, removing nulls and \r's */
\r
2103 buf_len = leftover_len;
\r
2104 for (i = 0; i < count; i++) {
\r
2105 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2106 buf[buf_len++] = data[i];
\r
2107 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2108 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2109 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2112 buf[buf_len] = NULLCHAR;
\r
2113 next_out = leftover_len;
\r
2114 leftover_start = 0;
\r
2117 while (i < buf_len) {
\r
2118 /* Deal with part of the TELNET option negotiation
\r
2119 protocol. We refuse to do anything beyond the
\r
2120 defaults, except that we allow the WILL ECHO option,
\r
2121 which ICS uses to turn off password echoing when we are
\r
2122 directly connected to it. We reject this option
\r
2123 if localLineEditing mode is on (always on in xboard)
\r
2124 and we are talking to port 23, which might be a real
\r
2125 telnet server that will try to keep WILL ECHO on permanently.
\r
2127 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2128 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2129 unsigned char option;
\r
2131 switch ((unsigned char) buf[++i]) {
\r
2133 if (appData.debugMode)
\r
2134 fprintf(debugFP, "\n<WILL ");
\r
2135 switch (option = (unsigned char) buf[++i]) {
\r
2137 if (appData.debugMode)
\r
2138 fprintf(debugFP, "ECHO ");
\r
2139 /* Reply only if this is a change, according
\r
2140 to the protocol rules. */
\r
2141 if (remoteEchoOption) break;
\r
2142 if (appData.localLineEditing &&
\r
2143 atoi(appData.icsPort) == TN_PORT) {
\r
2144 TelnetRequest(TN_DONT, TN_ECHO);
\r
2147 TelnetRequest(TN_DO, TN_ECHO);
\r
2148 remoteEchoOption = TRUE;
\r
2152 if (appData.debugMode)
\r
2153 fprintf(debugFP, "%d ", option);
\r
2154 /* Whatever this is, we don't want it. */
\r
2155 TelnetRequest(TN_DONT, option);
\r
2160 if (appData.debugMode)
\r
2161 fprintf(debugFP, "\n<WONT ");
\r
2162 switch (option = (unsigned char) buf[++i]) {
\r
2164 if (appData.debugMode)
\r
2165 fprintf(debugFP, "ECHO ");
\r
2166 /* Reply only if this is a change, according
\r
2167 to the protocol rules. */
\r
2168 if (!remoteEchoOption) break;
\r
2170 TelnetRequest(TN_DONT, TN_ECHO);
\r
2171 remoteEchoOption = FALSE;
\r
2174 if (appData.debugMode)
\r
2175 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2176 /* Whatever this is, it must already be turned
\r
2177 off, because we never agree to turn on
\r
2178 anything non-default, so according to the
\r
2179 protocol rules, we don't reply. */
\r
2184 if (appData.debugMode)
\r
2185 fprintf(debugFP, "\n<DO ");
\r
2186 switch (option = (unsigned char) buf[++i]) {
\r
2188 /* Whatever this is, we refuse to do it. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "%d ", option);
\r
2191 TelnetRequest(TN_WONT, option);
\r
2196 if (appData.debugMode)
\r
2197 fprintf(debugFP, "\n<DONT ");
\r
2198 switch (option = (unsigned char) buf[++i]) {
\r
2200 if (appData.debugMode)
\r
2201 fprintf(debugFP, "%d ", option);
\r
2202 /* Whatever this is, we are already not doing
\r
2203 it, because we never agree to do anything
\r
2204 non-default, so according to the protocol
\r
2205 rules, we don't reply. */
\r
2210 if (appData.debugMode)
\r
2211 fprintf(debugFP, "\n<IAC ");
\r
2212 /* Doubled IAC; pass it through */
\r
2216 if (appData.debugMode)
\r
2217 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2218 /* Drop all other telnet commands on the floor */
\r
2221 if (oldi > next_out)
\r
2222 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2223 if (++i > next_out)
\r
2228 /* OK, this at least will *usually* work */
\r
2229 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2233 if (loggedOn && !intfSet) {
\r
2234 if (ics_type == ICS_ICC) {
\r
2236 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2239 } else if (ics_type == ICS_CHESSNET) {
\r
2240 sprintf(str, "/style 12\n");
\r
2242 strcpy(str, "alias $ @\n$set interface ");
\r
2243 strcat(str, programVersion);
\r
2244 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2246 strcat(str, "$iset nohighlight 1\n");
\r
2248 strcat(str, "$iset lock 1\n$style 12\n");
\r
2254 if (started == STARTED_COMMENT) {
\r
2255 /* Accumulate characters in comment */
\r
2256 parse[parse_pos++] = buf[i];
\r
2257 if (buf[i] == '\n') {
\r
2258 parse[parse_pos] = NULLCHAR;
\r
2259 if(!suppressKibitz) // [HGM] kibitz
\r
2260 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2261 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2262 int nrDigit = 0, nrAlph = 0, i;
\r
2263 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2264 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2265 parse[parse_pos] = NULLCHAR;
\r
2266 // try to be smart: if it does not look like search info, it should go to
\r
2267 // ICS interaction window after all, not to engine-output window.
\r
2268 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2269 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2270 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2271 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2273 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2274 OutputKibitz(suppressKibitz, parse);
\r
2276 char tmp[MSG_SIZ];
\r
2277 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2278 SendToPlayer(tmp, strlen(tmp));
\r
2281 started = STARTED_NONE;
\r
2283 /* Don't match patterns against characters in chatter */
\r
2288 if (started == STARTED_CHATTER) {
\r
2289 if (buf[i] != '\n') {
\r
2290 /* Don't match patterns against characters in chatter */
\r
2294 started = STARTED_NONE;
\r
2297 /* Kludge to deal with rcmd protocol */
\r
2298 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2299 DisplayFatalError(&buf[1], 0, 1);
\r
2302 firstTime = FALSE;
\r
2305 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2306 ics_type = ICS_ICC;
\r
2308 if (appData.debugMode)
\r
2309 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2312 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2313 ics_type = ICS_FICS;
\r
2315 if (appData.debugMode)
\r
2316 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2319 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2320 ics_type = ICS_CHESSNET;
\r
2322 if (appData.debugMode)
\r
2323 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2328 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2329 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2330 looking_at(buf, &i, "will be \"*\""))) {
\r
2331 strcpy(ics_handle, star_match[0]);
\r
2335 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2336 char buf[MSG_SIZ];
\r
2337 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2338 DisplayIcsInteractionTitle(buf);
\r
2339 have_set_title = TRUE;
\r
2342 /* skip finger notes */
\r
2343 if (started == STARTED_NONE &&
\r
2344 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2345 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2346 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2347 started = STARTED_CHATTER;
\r
2352 /* skip formula vars */
\r
2353 if (started == STARTED_NONE &&
\r
2354 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2355 started = STARTED_CHATTER;
\r
2361 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2362 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2363 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2364 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2365 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2366 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2367 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2368 suppressKibitz = TRUE;
\r
2369 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2370 && (gameMode == IcsPlayingWhite)) ||
\r
2371 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2372 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2373 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2375 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2376 parse_pos = 0; parse[0] = NULLCHAR;
\r
2377 savingComment = TRUE;
\r
2378 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2379 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2383 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2384 started = STARTED_CHATTER;
\r
2385 suppressKibitz = TRUE;
\r
2387 } // [HGM] kibitz: end of patch
\r
2389 if (appData.zippyTalk || appData.zippyPlay) {
\r
2390 /* [DM] Backup address for color zippy lines */
\r
2394 if (loggedOn == TRUE)
\r
2395 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2396 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2398 if (ZippyControl(buf, &i) ||
\r
2399 ZippyConverse(buf, &i) ||
\r
2400 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2402 if (!appData.colorize) continue;
\r
2406 } // [DM] 'else { ' deleted
\r
2407 if (/* Don't color "message" or "messages" output */
\r
2408 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2409 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2410 looking_at(buf, &i, "--* (*:*): ") ||
\r
2411 /* Regular tells and says */
\r
2412 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2413 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2414 looking_at(buf, &i, "* says: ") ||
\r
2415 /* Message notifications (same color as tells) */
\r
2416 looking_at(buf, &i, "* has left a message ") ||
\r
2417 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2418 /* Whispers and kibitzes */
\r
2419 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2420 looking_at(buf, &i, "* kibitzes: ") ||
\r
2421 /* Channel tells */
\r
2422 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2424 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2425 /* Avoid "tells you:" spoofs in channels */
\r
2428 if (star_match[0][0] == NULLCHAR ||
\r
2429 strchr(star_match[0], ' ') ||
\r
2430 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2431 /* Reject bogus matches */
\r
2434 if (appData.colorize) {
\r
2435 if (oldi > next_out) {
\r
2436 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2441 Colorize(ColorTell, FALSE);
\r
2442 curColor = ColorTell;
\r
2445 Colorize(ColorKibitz, FALSE);
\r
2446 curColor = ColorKibitz;
\r
2449 p = strrchr(star_match[1], '(');
\r
2451 p = star_match[1];
\r
2455 if (atoi(p) == 1) {
\r
2456 Colorize(ColorChannel1, FALSE);
\r
2457 curColor = ColorChannel1;
\r
2459 Colorize(ColorChannel, FALSE);
\r
2460 curColor = ColorChannel;
\r
2464 curColor = ColorNormal;
\r
2468 if (started == STARTED_NONE && appData.autoComment &&
\r
2469 (gameMode == IcsObserving ||
\r
2470 gameMode == IcsPlayingWhite ||
\r
2471 gameMode == IcsPlayingBlack)) {
\r
2472 parse_pos = i - oldi;
\r
2473 memcpy(parse, &buf[oldi], parse_pos);
\r
2474 parse[parse_pos] = NULLCHAR;
\r
2475 started = STARTED_COMMENT;
\r
2476 savingComment = TRUE;
\r
2478 started = STARTED_CHATTER;
\r
2479 savingComment = FALSE;
\r
2486 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2487 looking_at(buf, &i, "* c-shouts: ")) {
\r
2488 if (appData.colorize) {
\r
2489 if (oldi > next_out) {
\r
2490 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2493 Colorize(ColorSShout, FALSE);
\r
2494 curColor = ColorSShout;
\r
2497 started = STARTED_CHATTER;
\r
2501 if (looking_at(buf, &i, "--->")) {
\r
2506 if (looking_at(buf, &i, "* shouts: ") ||
\r
2507 looking_at(buf, &i, "--> ")) {
\r
2508 if (appData.colorize) {
\r
2509 if (oldi > next_out) {
\r
2510 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2513 Colorize(ColorShout, FALSE);
\r
2514 curColor = ColorShout;
\r
2517 started = STARTED_CHATTER;
\r
2521 if (looking_at( buf, &i, "Challenge:")) {
\r
2522 if (appData.colorize) {
\r
2523 if (oldi > next_out) {
\r
2524 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2527 Colorize(ColorChallenge, FALSE);
\r
2528 curColor = ColorChallenge;
\r
2534 if (looking_at(buf, &i, "* offers you") ||
\r
2535 looking_at(buf, &i, "* offers to be") ||
\r
2536 looking_at(buf, &i, "* would like to") ||
\r
2537 looking_at(buf, &i, "* requests to") ||
\r
2538 looking_at(buf, &i, "Your opponent offers") ||
\r
2539 looking_at(buf, &i, "Your opponent requests")) {
\r
2541 if (appData.colorize) {
\r
2542 if (oldi > next_out) {
\r
2543 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2546 Colorize(ColorRequest, FALSE);
\r
2547 curColor = ColorRequest;
\r
2552 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2553 if (appData.colorize) {
\r
2554 if (oldi > next_out) {
\r
2555 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2558 Colorize(ColorSeek, FALSE);
\r
2559 curColor = ColorSeek;
\r
2564 if (looking_at(buf, &i, "\\ ")) {
\r
2565 if (prevColor != ColorNormal) {
\r
2566 if (oldi > next_out) {
\r
2567 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2570 Colorize(prevColor, TRUE);
\r
2571 curColor = prevColor;
\r
2573 if (savingComment) {
\r
2574 parse_pos = i - oldi;
\r
2575 memcpy(parse, &buf[oldi], parse_pos);
\r
2576 parse[parse_pos] = NULLCHAR;
\r
2577 started = STARTED_COMMENT;
\r
2579 started = STARTED_CHATTER;
\r
2584 if (looking_at(buf, &i, "Black Strength :") ||
\r
2585 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2586 looking_at(buf, &i, "<10>") ||
\r
2587 looking_at(buf, &i, "#@#")) {
\r
2588 /* Wrong board style */
\r
2590 SendToICS(ics_prefix);
\r
2591 SendToICS("set style 12\n");
\r
2592 SendToICS(ics_prefix);
\r
2593 SendToICS("refresh\n");
\r
2597 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2599 have_sent_ICS_logon = 1;
\r
2603 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2604 (looking_at(buf, &i, "\n<12> ") ||
\r
2605 looking_at(buf, &i, "<12> "))) {
\r
2607 if (oldi > next_out) {
\r
2608 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2611 started = STARTED_BOARD;
\r
2616 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2617 looking_at(buf, &i, "<b1> ")) {
\r
2618 if (oldi > next_out) {
\r
2619 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2622 started = STARTED_HOLDINGS;
\r
2627 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2629 /* Header for a move list -- first line */
\r
2631 switch (ics_getting_history) {
\r
2633 switch (gameMode) {
\r
2635 case BeginningOfGame:
\r
2636 /* User typed "moves" or "oldmoves" while we
\r
2637 were idle. Pretend we asked for these
\r
2638 moves and soak them up so user can step
\r
2639 through them and/or save them.
\r
2641 Reset(FALSE, TRUE);
\r
2642 gameMode = IcsObserving;
\r
2645 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2647 case EditGame: /*?*/
\r
2648 case EditPosition: /*?*/
\r
2649 /* Should above feature work in these modes too? */
\r
2650 /* For now it doesn't */
\r
2651 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2654 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2659 /* Is this the right one? */
\r
2660 if (gameInfo.white && gameInfo.black &&
\r
2661 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2662 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2664 ics_getting_history = H_GOT_REQ_HEADER;
\r
2667 case H_GOT_REQ_HEADER:
\r
2668 case H_GOT_UNREQ_HEADER:
\r
2669 case H_GOT_UNWANTED_HEADER:
\r
2670 case H_GETTING_MOVES:
\r
2671 /* Should not happen */
\r
2672 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2673 ics_getting_history = H_FALSE;
\r
2677 /* Save player ratings into gameInfo if needed */
\r
2678 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2679 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2680 (gameInfo.whiteRating == -1 ||
\r
2681 gameInfo.blackRating == -1)) {
\r
2683 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2684 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2685 if (appData.debugMode)
\r
2686 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2687 gameInfo.whiteRating, gameInfo.blackRating);
\r
2692 if (looking_at(buf, &i,
\r
2693 "* * match, initial time: * minute*, increment: * second")) {
\r
2694 /* Header for a move list -- second line */
\r
2695 /* Initial board will follow if this is a wild game */
\r
2696 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2697 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2698 gameInfo.event = StrSave(str);
\r
2699 /* [HGM] we switched variant. Translate boards if needed. */
\r
2700 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2704 if (looking_at(buf, &i, "Move ")) {
\r
2705 /* Beginning of a move list */
\r
2706 switch (ics_getting_history) {
\r
2708 /* Normally should not happen */
\r
2709 /* Maybe user hit reset while we were parsing */
\r
2712 /* Happens if we are ignoring a move list that is not
\r
2713 * the one we just requested. Common if the user
\r
2714 * tries to observe two games without turning off
\r
2717 case H_GETTING_MOVES:
\r
2718 /* Should not happen */
\r
2719 DisplayError(_("Error gathering move list: nested"), 0);
\r
2720 ics_getting_history = H_FALSE;
\r
2722 case H_GOT_REQ_HEADER:
\r
2723 ics_getting_history = H_GETTING_MOVES;
\r
2724 started = STARTED_MOVES;
\r
2726 if (oldi > next_out) {
\r
2727 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2730 case H_GOT_UNREQ_HEADER:
\r
2731 ics_getting_history = H_GETTING_MOVES;
\r
2732 started = STARTED_MOVES_NOHIDE;
\r
2735 case H_GOT_UNWANTED_HEADER:
\r
2736 ics_getting_history = H_FALSE;
\r
2742 if (looking_at(buf, &i, "% ") ||
\r
2743 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2744 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2745 savingComment = FALSE;
\r
2746 switch (started) {
\r
2747 case STARTED_MOVES:
\r
2748 case STARTED_MOVES_NOHIDE:
\r
2749 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2750 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2751 ParseGameHistory(parse);
\r
2753 if (appData.zippyPlay && first.initDone) {
\r
2754 FeedMovesToProgram(&first, forwardMostMove);
\r
2755 if (gameMode == IcsPlayingWhite) {
\r
2756 if (WhiteOnMove(forwardMostMove)) {
\r
2757 if (first.sendTime) {
\r
2758 if (first.useColors) {
\r
2759 SendToProgram("black\n", &first);
\r
2761 SendTimeRemaining(&first, TRUE);
\r
2764 if (first.useColors) {
\r
2765 SendToProgram("white\ngo\n", &first);
\r
2767 SendToProgram("go\n", &first);
\r
2770 if (first.useColors) {
\r
2771 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2773 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2775 first.maybeThinking = TRUE;
\r
2777 if (first.usePlayother) {
\r
2778 if (first.sendTime) {
\r
2779 SendTimeRemaining(&first, TRUE);
\r
2781 SendToProgram("playother\n", &first);
\r
2782 firstMove = FALSE;
\r
2787 } else if (gameMode == IcsPlayingBlack) {
\r
2788 if (!WhiteOnMove(forwardMostMove)) {
\r
2789 if (first.sendTime) {
\r
2790 if (first.useColors) {
\r
2791 SendToProgram("white\n", &first);
\r
2793 SendTimeRemaining(&first, FALSE);
\r
2796 if (first.useColors) {
\r
2797 SendToProgram("black\ngo\n", &first);
\r
2799 SendToProgram("go\n", &first);
\r
2802 if (first.useColors) {
\r
2803 SendToProgram("black\n", &first);
\r
2805 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2807 first.maybeThinking = TRUE;
\r
2809 if (first.usePlayother) {
\r
2810 if (first.sendTime) {
\r
2811 SendTimeRemaining(&first, FALSE);
\r
2813 SendToProgram("playother\n", &first);
\r
2814 firstMove = FALSE;
\r
2822 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2823 /* Moves came from oldmoves or moves command
\r
2824 while we weren't doing anything else.
\r
2826 currentMove = forwardMostMove;
\r
2827 ClearHighlights();/*!!could figure this out*/
\r
2828 flipView = appData.flipView;
\r
2829 DrawPosition(FALSE, boards[currentMove]);
\r
2830 DisplayBothClocks();
\r
2831 sprintf(str, "%s vs. %s",
\r
2832 gameInfo.white, gameInfo.black);
\r
2833 DisplayTitle(str);
\r
2834 gameMode = IcsIdle;
\r
2836 /* Moves were history of an active game */
\r
2837 if (gameInfo.resultDetails != NULL) {
\r
2838 free(gameInfo.resultDetails);
\r
2839 gameInfo.resultDetails = NULL;
\r
2842 HistorySet(parseList, backwardMostMove,
\r
2843 forwardMostMove, currentMove-1);
\r
2844 DisplayMove(currentMove - 1);
\r
2845 if (started == STARTED_MOVES) next_out = i;
\r
2846 started = STARTED_NONE;
\r
2847 ics_getting_history = H_FALSE;
\r
2850 case STARTED_OBSERVE:
\r
2851 started = STARTED_NONE;
\r
2852 SendToICS(ics_prefix);
\r
2853 SendToICS("refresh\n");
\r
2859 if(bookHit) { // [HGM] book: simulate book reply
\r
2860 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2862 programStats.nodes = programStats.depth = programStats.time =
\r
2863 programStats.score = programStats.got_only_move = 0;
\r
2864 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2866 strcpy(bookMove, "move ");
\r
2867 strcat(bookMove, bookHit);
\r
2868 HandleMachineMove(bookMove, &first);
\r
2873 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2874 started == STARTED_HOLDINGS ||
\r
2875 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2876 /* Accumulate characters in move list or board */
\r
2877 parse[parse_pos++] = buf[i];
\r
2880 /* Start of game messages. Mostly we detect start of game
\r
2881 when the first board image arrives. On some versions
\r
2882 of the ICS, though, we need to do a "refresh" after starting
\r
2883 to observe in order to get the current board right away. */
\r
2884 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2885 started = STARTED_OBSERVE;
\r
2889 /* Handle auto-observe */
\r
2890 if (appData.autoObserve &&
\r
2891 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2892 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2894 /* Choose the player that was highlighted, if any. */
\r
2895 if (star_match[0][0] == '\033' ||
\r
2896 star_match[1][0] != '\033') {
\r
2897 player = star_match[0];
\r
2899 player = star_match[2];
\r
2901 sprintf(str, "%sobserve %s\n",
\r
2902 ics_prefix, StripHighlightAndTitle(player));
\r
2905 /* Save ratings from notify string */
\r
2906 strcpy(player1Name, star_match[0]);
\r
2907 player1Rating = string_to_rating(star_match[1]);
\r
2908 strcpy(player2Name, star_match[2]);
\r
2909 player2Rating = string_to_rating(star_match[3]);
\r
2911 if (appData.debugMode)
\r
2913 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2914 player1Name, player1Rating,
\r
2915 player2Name, player2Rating);
\r
2920 /* Deal with automatic examine mode after a game,
\r
2921 and with IcsObserving -> IcsExamining transition */
\r
2922 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2923 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2925 int gamenum = atoi(star_match[0]);
\r
2926 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2927 gamenum == ics_gamenum) {
\r
2928 /* We were already playing or observing this game;
\r
2929 no need to refetch history */
\r
2930 gameMode = IcsExamining;
\r
2932 pauseExamForwardMostMove = forwardMostMove;
\r
2933 } else if (currentMove < forwardMostMove) {
\r
2934 ForwardInner(forwardMostMove);
\r
2937 /* I don't think this case really can happen */
\r
2938 SendToICS(ics_prefix);
\r
2939 SendToICS("refresh\n");
\r
2944 /* Error messages */
\r
2945 if (ics_user_moved) {
\r
2946 if (looking_at(buf, &i, "Illegal move") ||
\r
2947 looking_at(buf, &i, "Not a legal move") ||
\r
2948 looking_at(buf, &i, "Your king is in check") ||
\r
2949 looking_at(buf, &i, "It isn't your turn") ||
\r
2950 looking_at(buf, &i, "It is not your move")) {
\r
2951 /* Illegal move */
\r
2952 ics_user_moved = 0;
\r
2953 if (forwardMostMove > backwardMostMove) {
\r
2954 currentMove = --forwardMostMove;
\r
2955 DisplayMove(currentMove - 1); /* before DMError */
\r
2956 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2957 DrawPosition(FALSE, boards[currentMove]);
\r
2959 DisplayBothClocks();
\r
2965 if (looking_at(buf, &i, "still have time") ||
\r
2966 looking_at(buf, &i, "not out of time") ||
\r
2967 looking_at(buf, &i, "either player is out of time") ||
\r
2968 looking_at(buf, &i, "has timeseal; checking")) {
\r
2969 /* We must have called his flag a little too soon */
\r
2970 whiteFlag = blackFlag = FALSE;
\r
2974 if (looking_at(buf, &i, "added * seconds to") ||
\r
2975 looking_at(buf, &i, "seconds were added to")) {
\r
2976 /* Update the clocks */
\r
2977 SendToICS(ics_prefix);
\r
2978 SendToICS("refresh\n");
\r
2982 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2983 ics_clock_paused = TRUE;
\r
2988 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2989 ics_clock_paused = FALSE;
\r
2994 /* Grab player ratings from the Creating: message.
\r
2995 Note we have to check for the special case when
\r
2996 the ICS inserts things like [white] or [black]. */
\r
2997 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2998 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3000 0 player 1 name (not necessarily white)
\r
3002 2 empty, white, or black (IGNORED)
\r
3003 3 player 2 name (not necessarily black)
\r
3006 The names/ratings are sorted out when the game
\r
3007 actually starts (below).
\r
3009 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3010 player1Rating = string_to_rating(star_match[1]);
\r
3011 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3012 player2Rating = string_to_rating(star_match[4]);
\r
3014 if (appData.debugMode)
\r
3016 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3017 player1Name, player1Rating,
\r
3018 player2Name, player2Rating);
\r
3023 /* Improved generic start/end-of-game messages */
\r
3024 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3025 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3026 /* If tkind == 0: */
\r
3027 /* star_match[0] is the game number */
\r
3028 /* [1] is the white player's name */
\r
3029 /* [2] is the black player's name */
\r
3030 /* For end-of-game: */
\r
3031 /* [3] is the reason for the game end */
\r
3032 /* [4] is a PGN end game-token, preceded by " " */
\r
3033 /* For start-of-game: */
\r
3034 /* [3] begins with "Creating" or "Continuing" */
\r
3035 /* [4] is " *" or empty (don't care). */
\r
3036 int gamenum = atoi(star_match[0]);
\r
3037 char *whitename, *blackname, *why, *endtoken;
\r
3038 ChessMove endtype = (ChessMove) 0;
\r
3041 whitename = star_match[1];
\r
3042 blackname = star_match[2];
\r
3043 why = star_match[3];
\r
3044 endtoken = star_match[4];
\r
3046 whitename = star_match[1];
\r
3047 blackname = star_match[3];
\r
3048 why = star_match[5];
\r
3049 endtoken = star_match[6];
\r
3052 /* Game start messages */
\r
3053 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3054 strncmp(why, "Continuing ", 11) == 0) {
\r
3055 gs_gamenum = gamenum;
\r
3056 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3058 if (appData.zippyPlay) {
\r
3059 ZippyGameStart(whitename, blackname);
\r
3065 /* Game end messages */
\r
3066 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3067 ics_gamenum != gamenum) {
\r
3070 while (endtoken[0] == ' ') endtoken++;
\r
3071 switch (endtoken[0]) {
\r
3074 endtype = GameUnfinished;
\r
3077 endtype = BlackWins;
\r
3080 if (endtoken[1] == '/')
\r
3081 endtype = GameIsDrawn;
\r
3083 endtype = WhiteWins;
\r
3086 GameEnds(endtype, why, GE_ICS);
\r
3088 if (appData.zippyPlay && first.initDone) {
\r
3089 ZippyGameEnd(endtype, why);
\r
3090 if (first.pr == NULL) {
\r
3091 /* Start the next process early so that we'll
\r
3092 be ready for the next challenge */
\r
3093 StartChessProgram(&first);
\r
3095 /* Send "new" early, in case this command takes
\r
3096 a long time to finish, so that we'll be ready
\r
3097 for the next challenge. */
\r
3098 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3099 Reset(TRUE, TRUE);
\r
3105 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3106 looking_at(buf, &i, "no longer observing game *") ||
\r
3107 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3108 if (gameMode == IcsObserving &&
\r
3109 atoi(star_match[0]) == ics_gamenum)
\r
3111 /* icsEngineAnalyze */
\r
3112 if (appData.icsEngineAnalyze) {
\r
3113 ExitAnalyzeMode();
\r
3117 gameMode = IcsIdle;
\r
3119 ics_user_moved = FALSE;
\r
3124 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3125 if (gameMode == IcsExamining &&
\r
3126 atoi(star_match[0]) == ics_gamenum)
\r
3128 gameMode = IcsIdle;
\r
3130 ics_user_moved = FALSE;
\r
3135 /* Advance leftover_start past any newlines we find,
\r
3136 so only partial lines can get reparsed */
\r
3137 if (looking_at(buf, &i, "\n")) {
\r
3138 prevColor = curColor;
\r
3139 if (curColor != ColorNormal) {
\r
3140 if (oldi > next_out) {
\r
3141 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3144 Colorize(ColorNormal, FALSE);
\r
3145 curColor = ColorNormal;
\r
3147 if (started == STARTED_BOARD) {
\r
3148 started = STARTED_NONE;
\r
3149 parse[parse_pos] = NULLCHAR;
\r
3150 ParseBoard12(parse);
\r
3151 ics_user_moved = 0;
\r
3153 /* Send premove here */
\r
3154 if (appData.premove) {
\r
3155 char str[MSG_SIZ];
\r
3156 if (currentMove == 0 &&
\r
3157 gameMode == IcsPlayingWhite &&
\r
3158 appData.premoveWhite) {
\r
3159 sprintf(str, "%s%s\n", ics_prefix,
\r
3160 appData.premoveWhiteText);
\r
3161 if (appData.debugMode)
\r
3162 fprintf(debugFP, "Sending premove:\n");
\r
3164 } else if (currentMove == 1 &&
\r
3165 gameMode == IcsPlayingBlack &&
\r
3166 appData.premoveBlack) {
\r
3167 sprintf(str, "%s%s\n", ics_prefix,
\r
3168 appData.premoveBlackText);
\r
3169 if (appData.debugMode)
\r
3170 fprintf(debugFP, "Sending premove:\n");
\r
3172 } else if (gotPremove) {
\r
3174 ClearPremoveHighlights();
\r
3175 if (appData.debugMode)
\r
3176 fprintf(debugFP, "Sending premove:\n");
\r
3177 UserMoveEvent(premoveFromX, premoveFromY,
\r
3178 premoveToX, premoveToY,
\r
3179 premovePromoChar);
\r
3183 /* Usually suppress following prompt */
\r
3184 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3185 if (looking_at(buf, &i, "*% ")) {
\r
3186 savingComment = FALSE;
\r
3190 } else if (started == STARTED_HOLDINGS) {
\r
3192 char new_piece[MSG_SIZ];
\r
3193 started = STARTED_NONE;
\r
3194 parse[parse_pos] = NULLCHAR;
\r
3195 if (appData.debugMode)
\r
3196 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3197 parse, currentMove);
\r
3198 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3199 gamenum == ics_gamenum) {
\r
3200 if (gameInfo.variant == VariantNormal) {
\r
3201 /* [HGM] We seem to switch variant during a game!
\r
3202 * Presumably no holdings were displayed, so we have
\r
3203 * to move the position two files to the right to
\r
3204 * create room for them!
\r
3206 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3207 /* Get a move list just to see the header, which
\r
3208 will tell us whether this is really bug or zh */
\r
3209 if (ics_getting_history == H_FALSE) {
\r
3210 ics_getting_history = H_REQUESTED;
\r
3211 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3215 new_piece[0] = NULLCHAR;
\r
3216 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3217 &gamenum, white_holding, black_holding,
\r
3219 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3220 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3221 /* [HGM] copy holdings to board holdings area */
\r
3222 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3223 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3225 if (appData.zippyPlay && first.initDone) {
\r
3226 ZippyHoldings(white_holding, black_holding,
\r
3230 if (tinyLayout || smallLayout) {
\r
3231 char wh[16], bh[16];
\r
3232 PackHolding(wh, white_holding);
\r
3233 PackHolding(bh, black_holding);
\r
3234 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3235 gameInfo.white, gameInfo.black);
\r
3237 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3238 gameInfo.white, white_holding,
\r
3239 gameInfo.black, black_holding);
\r
3242 DrawPosition(FALSE, boards[currentMove]);
\r
3243 DisplayTitle(str);
\r
3245 /* Suppress following prompt */
\r
3246 if (looking_at(buf, &i, "*% ")) {
\r
3247 savingComment = FALSE;
\r
3254 i++; /* skip unparsed character and loop back */
\r
3257 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3258 started != STARTED_HOLDINGS && i > next_out) {
\r
3259 SendToPlayer(&buf[next_out], i - next_out);
\r
3262 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3264 leftover_len = buf_len - leftover_start;
\r
3265 /* if buffer ends with something we couldn't parse,
\r
3266 reparse it after appending the next read */
\r
3268 } else if (count == 0) {
\r
3269 RemoveInputSource(isr);
\r
3270 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3272 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3277 /* Board style 12 looks like this:
\r
3279 <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
3281 * The "<12> " is stripped before it gets to this routine. The two
\r
3282 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3283 * some chess servers may not have them, or may have only the first.
\r
3284 * Additional trailing fields may be added in the future.
\r
3287 #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
3289 #define RELATION_OBSERVING_PLAYED 0
\r
3290 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3291 #define RELATION_PLAYING_MYMOVE 1
\r
3292 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3293 #define RELATION_EXAMINING 2
\r
3294 #define RELATION_ISOLATED_BOARD -3
\r
3295 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3298 ParseBoard12(string)
\r
3301 GameMode newGameMode;
\r
3302 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3303 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3304 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3305 char to_play, board_chars[200];
\r
3306 char move_str[500], str[500], elapsed_time[500];
\r
3307 char black[32], white[32];
\r
3309 int prevMove = currentMove;
\r
3311 ChessMove moveType;
\r
3312 int fromX, fromY, toX, toY;
\r
3314 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3315 char *bookHit = NULL; // [HGM] book
\r
3317 fromX = fromY = toX = toY = -1;
\r
3321 if (appData.debugMode)
\r
3322 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3324 move_str[0] = NULLCHAR;
\r
3325 elapsed_time[0] = NULLCHAR;
\r
3326 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3328 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3329 if(string[i] == ' ') { ranks++; files = 0; }
\r
3333 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3334 board_chars[i] = '\0';
\r
3337 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3338 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3339 &gamenum, white, black, &relation, &basetime, &increment,
\r
3340 &white_stren, &black_stren, &white_time, &black_time,
\r
3341 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3345 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3346 DisplayError(str, 0);
\r
3350 /* Convert the move number to internal form */
\r
3351 moveNum = (moveNum - 1) * 2;
\r
3352 if (to_play == 'B') moveNum++;
\r
3353 if (moveNum >= MAX_MOVES) {
\r
3354 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3359 switch (relation) {
\r
3360 case RELATION_OBSERVING_PLAYED:
\r
3361 case RELATION_OBSERVING_STATIC:
\r
3362 if (gamenum == -1) {
\r
3363 /* Old ICC buglet */
\r
3364 relation = RELATION_OBSERVING_STATIC;
\r
3366 newGameMode = IcsObserving;
\r
3368 case RELATION_PLAYING_MYMOVE:
\r
3369 case RELATION_PLAYING_NOTMYMOVE:
\r
3371 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3372 IcsPlayingWhite : IcsPlayingBlack;
\r
3374 case RELATION_EXAMINING:
\r
3375 newGameMode = IcsExamining;
\r
3377 case RELATION_ISOLATED_BOARD:
\r
3379 /* Just display this board. If user was doing something else,
\r
3380 we will forget about it until the next board comes. */
\r
3381 newGameMode = IcsIdle;
\r
3383 case RELATION_STARTING_POSITION:
\r
3384 newGameMode = gameMode;
\r
3388 /* Modify behavior for initial board display on move listing
\r
3391 switch (ics_getting_history) {
\r
3395 case H_GOT_REQ_HEADER:
\r
3396 case H_GOT_UNREQ_HEADER:
\r
3397 /* This is the initial position of the current game */
\r
3398 gamenum = ics_gamenum;
\r
3399 moveNum = 0; /* old ICS bug workaround */
\r
3400 if (to_play == 'B') {
\r
3401 startedFromSetupPosition = TRUE;
\r
3402 blackPlaysFirst = TRUE;
\r
3404 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3405 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3406 if (currentMove == 0) currentMove = 1;
\r
3408 newGameMode = gameMode;
\r
3409 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3411 case H_GOT_UNWANTED_HEADER:
\r
3412 /* This is an initial board that we don't want */
\r
3414 case H_GETTING_MOVES:
\r
3415 /* Should not happen */
\r
3416 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3417 ics_getting_history = H_FALSE;
\r
3421 /* Take action if this is the first board of a new game, or of a
\r
3422 different game than is currently being displayed. */
\r
3423 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3424 relation == RELATION_ISOLATED_BOARD) {
\r
3426 /* Forget the old game and get the history (if any) of the new one */
\r
3427 if (gameMode != BeginningOfGame) {
\r
3428 Reset(FALSE, TRUE);
\r
3431 if (appData.autoRaiseBoard) BoardToTop();
\r
3433 if (gamenum == -1) {
\r
3434 newGameMode = IcsIdle;
\r
3435 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3436 appData.getMoveList) {
\r
3437 /* Need to get game history */
\r
3438 ics_getting_history = H_REQUESTED;
\r
3439 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3443 /* Initially flip the board to have black on the bottom if playing
\r
3444 black or if the ICS flip flag is set, but let the user change
\r
3445 it with the Flip View button. */
\r
3446 flipView = appData.autoFlipView ?
\r
3447 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3450 /* Done with values from previous mode; copy in new ones */
\r
3451 gameMode = newGameMode;
\r
3453 ics_gamenum = gamenum;
\r
3454 if (gamenum == gs_gamenum) {
\r
3455 int klen = strlen(gs_kind);
\r
3456 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3457 sprintf(str, "ICS %s", gs_kind);
\r
3458 gameInfo.event = StrSave(str);
\r
3460 gameInfo.event = StrSave("ICS game");
\r
3462 gameInfo.site = StrSave(appData.icsHost);
\r
3463 gameInfo.date = PGNDate();
\r
3464 gameInfo.round = StrSave("-");
\r
3465 gameInfo.white = StrSave(white);
\r
3466 gameInfo.black = StrSave(black);
\r
3467 timeControl = basetime * 60 * 1000;
\r
3468 timeControl_2 = 0;
\r
3469 timeIncrement = increment * 1000;
\r
3470 movesPerSession = 0;
\r
3471 gameInfo.timeControl = TimeControlTagValue();
\r
3472 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3473 if (appData.debugMode) {
\r
3474 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3475 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3476 setbuf(debugFP, NULL);
\r
3479 gameInfo.outOfBook = NULL;
\r
3481 /* Do we have the ratings? */
\r
3482 if (strcmp(player1Name, white) == 0 &&
\r
3483 strcmp(player2Name, black) == 0) {
\r
3484 if (appData.debugMode)
\r
3485 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3486 player1Rating, player2Rating);
\r
3487 gameInfo.whiteRating = player1Rating;
\r
3488 gameInfo.blackRating = player2Rating;
\r
3489 } else if (strcmp(player2Name, white) == 0 &&
\r
3490 strcmp(player1Name, black) == 0) {
\r
3491 if (appData.debugMode)
\r
3492 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3493 player2Rating, player1Rating);
\r
3494 gameInfo.whiteRating = player2Rating;
\r
3495 gameInfo.blackRating = player1Rating;
\r
3497 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3499 /* Silence shouts if requested */
\r
3500 if (appData.quietPlay &&
\r
3501 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3502 SendToICS(ics_prefix);
\r
3503 SendToICS("set shout 0\n");
\r
3507 /* Deal with midgame name changes */
\r
3509 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3510 if (gameInfo.white) free(gameInfo.white);
\r
3511 gameInfo.white = StrSave(white);
\r
3513 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3514 if (gameInfo.black) free(gameInfo.black);
\r
3515 gameInfo.black = StrSave(black);
\r
3519 /* Throw away game result if anything actually changes in examine mode */
\r
3520 if (gameMode == IcsExamining && !newGame) {
\r
3521 gameInfo.result = GameUnfinished;
\r
3522 if (gameInfo.resultDetails != NULL) {
\r
3523 free(gameInfo.resultDetails);
\r
3524 gameInfo.resultDetails = NULL;
\r
3528 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3529 in if they are in a different variation than we are. */
\r
3530 if (pauseExamInvalid) return;
\r
3531 if (pausing && gameMode == IcsExamining) {
\r
3532 if (moveNum <= pauseExamForwardMostMove) {
\r
3533 pauseExamInvalid = TRUE;
\r
3534 forwardMostMove = pauseExamForwardMostMove;
\r
3539 if (appData.debugMode) {
\r
3540 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3542 /* Parse the board */
\r
3543 for (k = 0; k < ranks; k++) {
\r
3544 for (j = 0; j < files; j++)
\r
3545 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3546 if(gameInfo.holdingsWidth > 1) {
\r
3547 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3548 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3551 CopyBoard(boards[moveNum], board);
\r
3552 if (moveNum == 0) {
\r
3553 startedFromSetupPosition =
\r
3554 !CompareBoards(board, initialPosition);
\r
3555 if(startedFromSetupPosition)
\r
3556 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3559 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3560 to make it also work for FRC opening positions. Note that board12
\r
3561 is really defective for later FRC positions, as it has no way to
\r
3562 indicate which Rook can castle if they are on the same side of King.
\r
3563 For the initial position we grant rights to the outermost Rooks,
\r
3564 and remember thos rights, and we then copy them on positions
\r
3565 later in an FRC game. This means WB might not recognize castlings with
\r
3566 Rooks that have moved back to their original position as illegal,
\r
3567 but in ICS mode that is not its job anyway.
\r
3569 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3570 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3572 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3573 if(board[0][i] == WhiteRook) j = i;
\r
3574 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3575 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3576 if(board[0][i] == WhiteRook) j = i;
\r
3577 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3578 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3579 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3580 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3581 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3582 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3583 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3585 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3586 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3587 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3588 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3589 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3590 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3592 r = castlingRights[moveNum][0] = initialRights[0];
\r
3593 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3594 r = castlingRights[moveNum][1] = initialRights[1];
\r
3595 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3596 r = castlingRights[moveNum][3] = initialRights[3];
\r
3597 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3598 r = castlingRights[moveNum][4] = initialRights[4];
\r
3599 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3600 /* wildcastle kludge: always assume King has rights */
\r
3601 r = castlingRights[moveNum][2] = initialRights[2];
\r
3602 r = castlingRights[moveNum][5] = initialRights[5];
\r
3604 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3605 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3608 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3609 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3610 /* This was an initial position from a move list, not
\r
3611 the current position */
\r
3615 /* Update currentMove and known move number limits */
\r
3616 newMove = newGame || moveNum > forwardMostMove;
\r
3618 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3619 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3620 takeback = forwardMostMove - moveNum;
\r
3621 for (i = 0; i < takeback; i++) {
\r
3622 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3623 SendToProgram("undo\n", &first);
\r
3628 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3629 if (gameMode == IcsExamining && moveNum == 0) {
\r
3630 /* Workaround for ICS limitation: we are not told the wild
\r
3631 type when starting to examine a game. But if we ask for
\r
3632 the move list, the move list header will tell us */
\r
3633 ics_getting_history = H_REQUESTED;
\r
3634 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3637 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3638 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3639 forwardMostMove = moveNum;
\r
3640 if (!pausing || currentMove > forwardMostMove)
\r
3641 currentMove = forwardMostMove;
\r
3643 /* New part of history that is not contiguous with old part */
\r
3644 if (pausing && gameMode == IcsExamining) {
\r
3645 pauseExamInvalid = TRUE;
\r
3646 forwardMostMove = pauseExamForwardMostMove;
\r
3649 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3650 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3651 ics_getting_history = H_REQUESTED;
\r
3652 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3657 /* Update the clocks */
\r
3658 if (strchr(elapsed_time, '.')) {
\r
3659 /* Time is in ms */
\r
3660 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3661 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3663 /* Time is in seconds */
\r
3664 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3665 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3670 if (appData.zippyPlay && newGame &&
\r
3671 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3672 gameMode != IcsExamining)
\r
3673 ZippyFirstBoard(moveNum, basetime, increment);
\r
3676 /* Put the move on the move list, first converting
\r
3677 to canonical algebraic form. */
\r
3678 if (moveNum > 0) {
\r
3679 if (appData.debugMode) {
\r
3680 if (appData.debugMode) { int f = forwardMostMove;
\r
3681 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3682 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3684 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3685 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3686 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3687 setbuf(debugFP, NULL);
\r
3689 if (moveNum <= backwardMostMove) {
\r
3690 /* We don't know what the board looked like before
\r
3691 this move. Punt. */
\r
3692 strcpy(parseList[moveNum - 1], move_str);
\r
3693 strcat(parseList[moveNum - 1], " ");
\r
3694 strcat(parseList[moveNum - 1], elapsed_time);
\r
3695 moveList[moveNum - 1][0] = NULLCHAR;
\r
3696 } else if (strcmp(move_str, "none") == 0) {
\r
3697 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3698 /* Again, we don't know what the board looked like;
\r
3699 this is really the start of the game. */
\r
3700 parseList[moveNum - 1][0] = NULLCHAR;
\r
3701 moveList[moveNum - 1][0] = NULLCHAR;
\r
3702 backwardMostMove = moveNum;
\r
3703 startedFromSetupPosition = TRUE;
\r
3704 fromX = fromY = toX = toY = -1;
\r
3706 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3707 // So we parse the long-algebraic move string in stead of the SAN move
\r
3708 int valid; char buf[MSG_SIZ], *prom;
\r
3710 // str looks something like "Q/a1-a2"; kill the slash
\r
3711 if(str[1] == '/')
\r
3712 sprintf(buf, "%c%s", str[0], str+2);
\r
3713 else strcpy(buf, str); // might be castling
\r
3714 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3715 strcat(buf, prom); // long move lacks promo specification!
\r
3716 if(!appData.testLegality) {
\r
3717 if(appData.debugMode)
\r
3718 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3719 strcpy(move_str, buf);
\r
3721 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3722 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3723 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3724 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3725 // end of long SAN patch
\r
3727 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3728 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3729 fromY, fromX, toY, toX, promoChar,
\r
3730 parseList[moveNum-1]);
\r
3731 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3732 castlingRights[moveNum]) ) {
\r
3734 case MT_STALEMATE:
\r
3738 if(gameInfo.variant != VariantShogi)
\r
3739 strcat(parseList[moveNum - 1], "+");
\r
3741 case MT_CHECKMATE:
\r
3742 strcat(parseList[moveNum - 1], "#");
\r
3745 strcat(parseList[moveNum - 1], " ");
\r
3746 strcat(parseList[moveNum - 1], elapsed_time);
\r
3747 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3748 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3749 strcat(moveList[moveNum - 1], "\n");
\r
3751 /* Move from ICS was illegal!? Punt. */
\r
3752 if (appData.debugMode) {
\r
3753 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3754 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3757 if (appData.testLegality && appData.debugMode) {
\r
3758 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3759 DisplayError(str, 0);
\r
3762 strcpy(parseList[moveNum - 1], move_str);
\r
3763 strcat(parseList[moveNum - 1], " ");
\r
3764 strcat(parseList[moveNum - 1], elapsed_time);
\r
3765 moveList[moveNum - 1][0] = NULLCHAR;
\r
3766 fromX = fromY = toX = toY = -1;
\r
3769 if (appData.debugMode) {
\r
3770 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3771 setbuf(debugFP, NULL);
\r
3775 /* Send move to chess program (BEFORE animating it). */
\r
3776 if (appData.zippyPlay && !newGame && newMove &&
\r
3777 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3779 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3780 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3781 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3782 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3784 DisplayError(str, 0);
\r
3786 if (first.sendTime) {
\r
3787 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3789 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3790 if (firstMove && !bookHit) {
\r
3791 firstMove = FALSE;
\r
3792 if (first.useColors) {
\r
3793 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3795 "black\ngo\n", &first);
\r
3797 SendToProgram("go\n", &first);
\r
3799 first.maybeThinking = TRUE;
\r
3802 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3803 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3804 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3805 DisplayError(str, 0);
\r
3807 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3808 SendMoveToProgram(moveNum - 1, &first);
\r
3815 if (moveNum > 0 && !gotPremove) {
\r
3816 /* If move comes from a remote source, animate it. If it
\r
3817 isn't remote, it will have already been animated. */
\r
3818 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3819 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3821 if (!pausing && appData.highlightLastMove) {
\r
3822 SetHighlights(fromX, fromY, toX, toY);
\r
3826 /* Start the clocks */
\r
3827 whiteFlag = blackFlag = FALSE;
\r
3828 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3829 if (ticking == 0) {
\r
3830 ics_clock_paused = TRUE;
\r
3832 } else if (ticking == 1) {
\r
3833 ics_clock_paused = FALSE;
\r
3835 if (gameMode == IcsIdle ||
\r
3836 relation == RELATION_OBSERVING_STATIC ||
\r
3837 relation == RELATION_EXAMINING ||
\r
3839 DisplayBothClocks();
\r
3843 /* Display opponents and material strengths */
\r
3844 if (gameInfo.variant != VariantBughouse &&
\r
3845 gameInfo.variant != VariantCrazyhouse) {
\r
3846 if (tinyLayout || smallLayout) {
\r
3847 if(gameInfo.variant == VariantNormal)
\r
3848 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3849 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3850 basetime, increment);
\r
3852 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3853 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3854 basetime, increment, (int) gameInfo.variant);
\r
3856 if(gameInfo.variant == VariantNormal)
\r
3857 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3858 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3859 basetime, increment);
\r
3861 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3862 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3863 basetime, increment, VariantName(gameInfo.variant));
\r
3865 DisplayTitle(str);
\r
3866 if (appData.debugMode) {
\r
3867 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3872 /* Display the board */
\r
3875 if (appData.premove)
\r
3876 if (!gotPremove ||
\r
3877 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3878 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3879 ClearPremoveHighlights();
\r
3881 DrawPosition(FALSE, boards[currentMove]);
\r
3882 DisplayMove(moveNum - 1);
\r
3883 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3887 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3889 if(bookHit) { // [HGM] book: simulate book reply
\r
3890 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3892 programStats.nodes = programStats.depth = programStats.time =
\r
3893 programStats.score = programStats.got_only_move = 0;
\r
3894 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3896 strcpy(bookMove, "move ");
\r
3897 strcat(bookMove, bookHit);
\r
3898 HandleMachineMove(bookMove, &first);
\r
3904 GetMoveListEvent()
\r
3906 char buf[MSG_SIZ];
\r
3907 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3908 ics_getting_history = H_REQUESTED;
\r
3909 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3915 AnalysisPeriodicEvent(force)
\r
3918 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3919 && !force) || !appData.periodicUpdates)
\r
3922 /* Send . command to Crafty to collect stats */
\r
3923 SendToProgram(".\n", &first);
\r
3925 /* Don't send another until we get a response (this makes
\r
3926 us stop sending to old Crafty's which don't understand
\r
3927 the "." command (sending illegal cmds resets node count & time,
\r
3928 which looks bad)) */
\r
3929 programStats.ok_to_send = 0;
\r
3933 SendMoveToProgram(moveNum, cps)
\r
3935 ChessProgramState *cps;
\r
3937 char buf[MSG_SIZ];
\r
3939 if (cps->useUsermove) {
\r
3940 SendToProgram("usermove ", cps);
\r
3942 if (cps->useSAN) {
\r
3944 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3945 int len = space - parseList[moveNum];
\r
3946 memcpy(buf, parseList[moveNum], len);
\r
3947 buf[len++] = '\n';
\r
3948 buf[len] = NULLCHAR;
\r
3950 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3952 SendToProgram(buf, cps);
\r
3954 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3955 AlphaRank(moveList[moveNum], 4);
\r
3956 SendToProgram(moveList[moveNum], cps);
\r
3957 AlphaRank(moveList[moveNum], 4); // and back
\r
3959 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3960 * the engine. It would be nice to have a better way to identify castle
\r
3962 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3963 && cps->useOOCastle) {
\r
3964 int fromX = moveList[moveNum][0] - AAA;
\r
3965 int fromY = moveList[moveNum][1] - ONE;
\r
3966 int toX = moveList[moveNum][2] - AAA;
\r
3967 int toY = moveList[moveNum][3] - ONE;
\r
3968 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3969 && boards[moveNum][toY][toX] == WhiteRook)
\r
3970 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3971 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3972 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3973 else SendToProgram("O-O-O\n", cps);
\r
3975 else SendToProgram(moveList[moveNum], cps);
\r
3977 else SendToProgram(moveList[moveNum], cps);
\r
3978 /* End of additions by Tord */
\r
3981 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3982 /* Send 'go' if we are in a mode where machine should play. */
\r
3983 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3984 (gameMode == TwoMachinesPlay ||
\r
3986 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3988 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3989 SendToProgram("go\n", cps);
\r
3990 if (appData.debugMode) {
\r
3991 fprintf(debugFP, "(extra)\n");
\r
3994 setboardSpoiledMachineBlack = 0;
\r
3998 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3999 ChessMove moveType;
\r
4000 int fromX, fromY, toX, toY;
\r
4002 char user_move[MSG_SIZ];
\r
4004 switch (moveType) {
\r
4006 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4007 (int)moveType, fromX, fromY, toX, toY);
\r
4008 DisplayError(user_move + strlen("say "), 0);
\r
4010 case WhiteKingSideCastle:
\r
4011 case BlackKingSideCastle:
\r
4012 case WhiteQueenSideCastleWild:
\r
4013 case BlackQueenSideCastleWild:
\r
4015 case WhiteHSideCastleFR:
\r
4016 case BlackHSideCastleFR:
\r
4018 sprintf(user_move, "o-o\n");
\r
4020 case WhiteQueenSideCastle:
\r
4021 case BlackQueenSideCastle:
\r
4022 case WhiteKingSideCastleWild:
\r
4023 case BlackKingSideCastleWild:
\r
4025 case WhiteASideCastleFR:
\r
4026 case BlackASideCastleFR:
\r
4028 sprintf(user_move, "o-o-o\n");
\r
4030 case WhitePromotionQueen:
\r
4031 case BlackPromotionQueen:
\r
4032 case WhitePromotionRook:
\r
4033 case BlackPromotionRook:
\r
4034 case WhitePromotionBishop:
\r
4035 case BlackPromotionBishop:
\r
4036 case WhitePromotionKnight:
\r
4037 case BlackPromotionKnight:
\r
4038 case WhitePromotionKing:
\r
4039 case BlackPromotionKing:
\r
4040 case WhitePromotionChancellor:
\r
4041 case BlackPromotionChancellor:
\r
4042 case WhitePromotionArchbishop:
\r
4043 case BlackPromotionArchbishop:
\r
4044 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4045 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4046 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4047 PieceToChar(WhiteFerz));
\r
4048 else if(gameInfo.variant == VariantGreat)
\r
4049 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4050 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4051 PieceToChar(WhiteMan));
\r
4053 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4054 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4055 PieceToChar(PromoPiece(moveType)));
\r
4059 sprintf(user_move, "%c@%c%c\n",
\r
4060 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4061 AAA + toX, ONE + toY);
\r
4064 case WhiteCapturesEnPassant:
\r
4065 case BlackCapturesEnPassant:
\r
4066 case IllegalMove: /* could be a variant we don't quite understand */
\r
4067 sprintf(user_move, "%c%c%c%c\n",
\r
4068 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4071 SendToICS(user_move);
\r
4075 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4076 int rf, ff, rt, ft;
\r
4080 if (rf == DROP_RANK) {
\r
4081 sprintf(move, "%c@%c%c\n",
\r
4082 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4084 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4085 sprintf(move, "%c%c%c%c\n",
\r
4086 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4088 sprintf(move, "%c%c%c%c%c\n",
\r
4089 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4095 ProcessICSInitScript(f)
\r
4098 char buf[MSG_SIZ];
\r
4100 while (fgets(buf, MSG_SIZ, f)) {
\r
4101 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4108 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4110 AlphaRank(char *move, int n)
\r
4112 // char *p = move, c; int x, y;
\r
4114 if (appData.debugMode) {
\r
4115 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4118 if(move[1]=='*' &&
\r
4119 move[2]>='0' && move[2]<='9' &&
\r
4120 move[3]>='a' && move[3]<='x' ) {
\r
4122 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4123 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4125 if(move[0]>='0' && move[0]<='9' &&
\r
4126 move[1]>='a' && move[1]<='x' &&
\r
4127 move[2]>='0' && move[2]<='9' &&
\r
4128 move[3]>='a' && move[3]<='x' ) {
\r
4129 /* input move, Shogi -> normal */
\r
4130 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4131 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4132 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4133 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4135 if(move[1]=='@' &&
\r
4136 move[3]>='0' && move[3]<='9' &&
\r
4137 move[2]>='a' && move[2]<='x' ) {
\r
4139 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4140 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4143 move[0]>='a' && move[0]<='x' &&
\r
4144 move[3]>='0' && move[3]<='9' &&
\r
4145 move[2]>='a' && move[2]<='x' ) {
\r
4146 /* output move, normal -> Shogi */
\r
4147 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4148 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4149 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4150 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4151 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4153 if (appData.debugMode) {
\r
4154 fprintf(debugFP, " out = '%s'\n", move);
\r
4158 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4163 ChessMove *moveType;
\r
4164 int *fromX, *fromY, *toX, *toY;
\r
4167 if (appData.debugMode) {
\r
4168 fprintf(debugFP, "move to parse: %s\n", move);
\r
4170 *moveType = yylexstr(moveNum, move);
\r
4172 switch (*moveType) {
\r
4173 case WhitePromotionChancellor:
\r
4174 case BlackPromotionChancellor:
\r
4175 case WhitePromotionArchbishop:
\r
4176 case BlackPromotionArchbishop:
\r
4177 case WhitePromotionQueen:
\r
4178 case BlackPromotionQueen:
\r
4179 case WhitePromotionRook:
\r
4180 case BlackPromotionRook:
\r
4181 case WhitePromotionBishop:
\r
4182 case BlackPromotionBishop:
\r
4183 case WhitePromotionKnight:
\r
4184 case BlackPromotionKnight:
\r
4185 case WhitePromotionKing:
\r
4186 case BlackPromotionKing:
\r
4188 case WhiteCapturesEnPassant:
\r
4189 case BlackCapturesEnPassant:
\r
4190 case WhiteKingSideCastle:
\r
4191 case WhiteQueenSideCastle:
\r
4192 case BlackKingSideCastle:
\r
4193 case BlackQueenSideCastle:
\r
4194 case WhiteKingSideCastleWild:
\r
4195 case WhiteQueenSideCastleWild:
\r
4196 case BlackKingSideCastleWild:
\r
4197 case BlackQueenSideCastleWild:
\r
4198 /* Code added by Tord: */
\r
4199 case WhiteHSideCastleFR:
\r
4200 case WhiteASideCastleFR:
\r
4201 case BlackHSideCastleFR:
\r
4202 case BlackASideCastleFR:
\r
4203 /* End of code added by Tord */
\r
4204 case IllegalMove: /* bug or odd chess variant */
\r
4205 *fromX = currentMoveString[0] - AAA;
\r
4206 *fromY = currentMoveString[1] - ONE;
\r
4207 *toX = currentMoveString[2] - AAA;
\r
4208 *toY = currentMoveString[3] - ONE;
\r
4209 *promoChar = currentMoveString[4];
\r
4210 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4211 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4212 if (appData.debugMode) {
\r
4213 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4215 *fromX = *fromY = *toX = *toY = 0;
\r
4218 if (appData.testLegality) {
\r
4219 return (*moveType != IllegalMove);
\r
4221 return !(fromX == fromY && toX == toY);
\r
4226 *fromX = *moveType == WhiteDrop ?
\r
4227 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4228 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4229 *fromY = DROP_RANK;
\r
4230 *toX = currentMoveString[2] - AAA;
\r
4231 *toY = currentMoveString[3] - ONE;
\r
4232 *promoChar = NULLCHAR;
\r
4235 case AmbiguousMove:
\r
4236 case ImpossibleMove:
\r
4237 case (ChessMove) 0: /* end of file */
\r
4246 if (appData.debugMode) {
\r
4247 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4250 *fromX = *fromY = *toX = *toY = 0;
\r
4251 *promoChar = NULLCHAR;
\r
4257 /* [AS] FRC game initialization */
\r
4258 static int FindEmptySquare( Board board, int n )
\r
4263 while( board[0][i] != EmptySquare ) i++;
\r
4273 static void ShuffleFRC( Board board )
\r
4279 for( i=0; i<8; i++ ) {
\r
4280 board[0][i] = EmptySquare;
\r
4283 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4284 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4285 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4286 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4287 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4288 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4289 initialRights[1] = initialRights[4] =
\r
4290 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4291 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4292 initialRights[2] = initialRights[5] =
\r
4293 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4294 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4295 initialRights[0] = initialRights[3] =
\r
4296 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4298 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4299 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4303 static unsigned char FRC_KnightTable[10] = {
\r
4304 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4307 static void SetupFRC( Board board, int pos_index )
\r
4310 unsigned char knights;
\r
4312 /* Bring the position index into a safe range (just in case...) */
\r
4313 if( pos_index < 0 ) pos_index = 0;
\r
4317 /* Clear the board */
\r
4318 for( i=0; i<8; i++ ) {
\r
4319 board[0][i] = EmptySquare;
\r
4322 /* Place bishops and queen */
\r
4323 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4326 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4329 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4332 /* Place knigths */
\r
4333 knights = FRC_KnightTable[ pos_index ];
\r
4335 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4336 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4338 /* Place rooks and king */
\r
4339 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4340 initialRights[1] = initialRights[4] =
\r
4341 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4342 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4343 initialRights[2] = initialRights[5] =
\r
4344 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4345 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4346 initialRights[0] = initialRights[3] =
\r
4347 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4349 /* Mirror piece placement for black */
\r
4350 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4351 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4355 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4356 // All positions will have equal probability, but the current method will not provide a unique
\r
4357 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4362 int squaresLeft[4];
\r
4363 int piecesLeft[(int)BlackPawn];
\r
4364 u64 seed, nrOfShuffles;
\r
4366 void GetPositionNumber()
\r
4367 { // sets global variable seed
\r
4370 seed = appData.defaultFrcPosition;
\r
4371 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4372 srandom(time(0));
\r
4373 for(i=0; i<50; i++) seed += random();
\r
4374 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4375 if(seed<0) seed = -seed;
\r
4379 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4380 // put the piece on the (n-1)-th empty squares of the given shade
\r
4384 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4385 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4386 board[rank][i] = (ChessSquare) pieceType;
\r
4387 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4388 squaresLeft[ANY]--;
\r
4389 piecesLeft[pieceType]--;
\r
4397 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4398 // calculate where the next piece goes, (any empty square), and put it there
\r
4402 i = seed % squaresLeft[shade];
\r
4403 nrOfShuffles *= squaresLeft[shade];
\r
4404 seed /= squaresLeft[shade];
\r
4405 put(board, pieceType, rank, i, shade);
\r
4408 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4409 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4411 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4413 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4414 i = seed % k; // pick one
\r
4415 nrOfShuffles *= k;
\r
4417 while(i >= j) i -= j--;
\r
4418 j = n - 1 - j; i += j;
\r
4419 put(board, pieceType, rank, j, ANY);
\r
4420 put(board, pieceType, rank, i, ANY);
\r
4423 void SetUpShuffle(Board board, int number)
\r
4425 int i, p, first=1;
\r
4427 GetPositionNumber(); nrOfShuffles = 1;
\r
4429 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4430 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4431 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4433 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4435 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4436 p = (int) board[0][i];
\r
4437 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4438 board[0][i] = EmptySquare;
\r
4441 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4442 // shuffles restricted to allow normal castling put KRR first
\r
4443 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4444 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4445 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4446 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4447 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4448 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4449 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4450 put(board, WhiteRook, 0, 0, ANY);
\r
4451 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4454 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4455 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4456 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4457 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4458 while(piecesLeft[p] >= 2) {
\r
4459 AddOnePiece(board, p, 0, LITE);
\r
4460 AddOnePiece(board, p, 0, DARK);
\r
4462 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4465 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4466 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4467 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4468 if(p == (int)WhiteRook) continue;
\r
4469 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4470 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4473 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4475 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4476 // Last King gets castling rights
\r
4477 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4478 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4479 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4482 while(piecesLeft[(int)WhiteKing]) {
\r
4483 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4484 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4489 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4490 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4493 // Only Rooks can be left; simply place them all
\r
4494 while(piecesLeft[(int)WhiteRook]) {
\r
4495 i = put(board, WhiteRook, 0, 0, ANY);
\r
4496 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4499 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4501 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4504 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4505 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4508 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4513 int SetCharTable( char *table, const char * map )
\r
4514 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4515 /* Basically a safe strcpy that uses the last character as King */
\r
4517 int result = FALSE; int NrPieces;
\r
4519 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4520 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4521 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4523 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4524 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4525 table[i] = map[i];
\r
4526 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4528 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4529 table[(int) BlackKing] = map[NrPieces-1];
\r
4537 void Prelude(Board board)
\r
4538 { // [HGM] superchess: random selection of exo-pieces
\r
4539 int i, j, k; ChessSquare p;
\r
4540 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4542 GetPositionNumber(); // use FRC position number
\r
4544 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4545 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4546 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4547 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4550 j = seed%4; seed /= 4;
\r
4551 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4552 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4553 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4554 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4555 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4556 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4557 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4558 j = seed%3; seed /= 3;
\r
4559 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4560 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4561 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4562 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4563 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4564 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4565 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4566 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4567 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4568 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4569 put(board, exoPieces[0], 0, 0, ANY);
\r
4570 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4574 InitPosition(redraw)
\r
4577 ChessSquare (* pieces)[BOARD_SIZE];
\r
4578 int i, j, pawnRow, overrule,
\r
4579 oldx = gameInfo.boardWidth,
\r
4580 oldy = gameInfo.boardHeight,
\r
4581 oldh = gameInfo.holdingsWidth,
\r
4582 oldv = gameInfo.variant;
\r
4584 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4585 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4587 /* [AS] Initialize pv info list [HGM] and game status */
\r
4589 for( i=0; i<MAX_MOVES; i++ ) {
\r
4590 pvInfoList[i].depth = 0;
\r
4591 epStatus[i]=EP_NONE;
\r
4592 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4595 initialRulePlies = 0; /* 50-move counter start */
\r
4597 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4598 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4602 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4603 /* the initialized data only consist of the two backranks. The switch */
\r
4604 /* selects which one we will use, which is than copied to the Board */
\r
4605 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4606 /* empty squares. This initial position is then copied to boards[0], */
\r
4607 /* possibly after shuffling, so that it remains available. */
\r
4609 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4610 gameInfo.boardWidth = 8;
\r
4611 gameInfo.boardHeight = 8;
\r
4612 gameInfo.holdingsSize = 0;
\r
4613 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4614 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4615 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4617 switch (gameInfo.variant) {
\r
4618 case VariantFischeRandom:
\r
4619 shuffleOpenings = TRUE;
\r
4621 pieces = FIDEArray;
\r
4623 case VariantShatranj:
\r
4624 pieces = ShatranjArray;
\r
4625 nrCastlingRights = 0;
\r
4626 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4628 case VariantTwoKings:
\r
4629 pieces = twoKingsArray;
\r
4630 nrCastlingRights = 8; /* add rights for second King */
\r
4631 castlingRights[0][6] = initialRights[2] = 5;
\r
4632 castlingRights[0][7] = initialRights[5] = 5;
\r
4633 castlingRank[6] = 0;
\r
4634 castlingRank[7] = BOARD_HEIGHT-1;
\r
4636 case VariantCapaRandom:
\r
4637 shuffleOpenings = TRUE;
\r
4638 case VariantCapablanca:
\r
4639 pieces = CapablancaArray;
\r
4640 gameInfo.boardWidth = 10;
\r
4641 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4643 case VariantGothic:
\r
4644 pieces = GothicArray;
\r
4645 gameInfo.boardWidth = 10;
\r
4646 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4648 case VariantJanus:
\r
4649 pieces = JanusArray;
\r
4650 gameInfo.boardWidth = 10;
\r
4651 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4652 nrCastlingRights = 6;
\r
4653 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4654 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4655 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4656 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4657 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4658 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4660 case VariantFalcon:
\r
4661 pieces = FalconArray;
\r
4662 gameInfo.boardWidth = 10;
\r
4663 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4665 case VariantXiangqi:
\r
4666 pieces = XiangqiArray;
\r
4667 gameInfo.boardWidth = 9;
\r
4668 gameInfo.boardHeight = 10;
\r
4669 nrCastlingRights = 0;
\r
4670 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4672 case VariantShogi:
\r
4673 pieces = ShogiArray;
\r
4674 gameInfo.boardWidth = 9;
\r
4675 gameInfo.boardHeight = 9;
\r
4676 gameInfo.holdingsSize = 7;
\r
4677 nrCastlingRights = 0;
\r
4678 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4680 case VariantCourier:
\r
4681 pieces = CourierArray;
\r
4682 gameInfo.boardWidth = 12;
\r
4683 nrCastlingRights = 0;
\r
4684 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4685 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4687 case VariantKnightmate:
\r
4688 pieces = KnightmateArray;
\r
4689 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4691 case VariantFairy:
\r
4692 pieces = fairyArray;
\r
4693 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4695 case VariantGreat:
\r
4696 pieces = GreatArray;
\r
4697 gameInfo.boardWidth = 10;
\r
4698 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4699 gameInfo.holdingsSize = 8;
\r
4701 case VariantSuper:
\r
4702 pieces = FIDEArray;
\r
4703 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4704 gameInfo.holdingsSize = 8;
\r
4705 startedFromSetupPosition = TRUE;
\r
4707 case VariantCrazyhouse:
\r
4708 case VariantBughouse:
\r
4709 pieces = FIDEArray;
\r
4710 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4711 gameInfo.holdingsSize = 5;
\r
4713 case VariantWildCastle:
\r
4714 pieces = FIDEArray;
\r
4715 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4716 shuffleOpenings = 1;
\r
4718 case VariantNoCastle:
\r
4719 pieces = FIDEArray;
\r
4720 nrCastlingRights = 0;
\r
4721 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4722 /* !!?unconstrained back-rank shuffle */
\r
4723 shuffleOpenings = 1;
\r
4728 if(appData.NrFiles >= 0) {
\r
4729 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4730 gameInfo.boardWidth = appData.NrFiles;
\r
4732 if(appData.NrRanks >= 0) {
\r
4733 gameInfo.boardHeight = appData.NrRanks;
\r
4735 if(appData.holdingsSize >= 0) {
\r
4736 i = appData.holdingsSize;
\r
4737 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4738 gameInfo.holdingsSize = i;
\r
4740 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4741 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4742 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4744 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4745 if(pawnRow < 1) pawnRow = 1;
\r
4747 /* User pieceToChar list overrules defaults */
\r
4748 if(appData.pieceToCharTable != NULL)
\r
4749 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4751 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4753 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4754 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4755 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4756 initialPosition[i][j] = s;
\r
4758 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4759 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4760 initialPosition[pawnRow][j] = WhitePawn;
\r
4761 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4762 if(gameInfo.variant == VariantXiangqi) {
\r
4764 initialPosition[pawnRow][j] =
\r
4765 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4766 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4767 initialPosition[2][j] = WhiteCannon;
\r
4768 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4772 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4774 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4777 initialPosition[1][j] = WhiteBishop;
\r
4778 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4780 initialPosition[1][j] = WhiteRook;
\r
4781 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4784 if( nrCastlingRights == -1) {
\r
4785 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4786 /* This sets default castling rights from none to normal corners */
\r
4787 /* Variants with other castling rights must set them themselves above */
\r
4788 nrCastlingRights = 6;
\r
4790 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4791 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4792 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4793 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4794 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4795 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4798 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4799 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4800 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;
\r
4801 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;
\r
4802 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4803 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4806 if(gameInfo.variant == VariantFischeRandom) {
\r
4807 if( appData.defaultFrcPosition < 0 ) {
\r
4808 ShuffleFRC( initialPosition );
\r
4811 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4813 startedFromSetupPosition = TRUE;
\r
4816 if (appData.debugMode) {
\r
4817 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4819 if(shuffleOpenings) {
\r
4820 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4821 startedFromSetupPosition = TRUE;
\r
4824 if(startedFromPositionFile) {
\r
4825 /* [HGM] loadPos: use PositionFile for every new game */
\r
4826 CopyBoard(initialPosition, filePosition);
\r
4827 for(i=0; i<nrCastlingRights; i++)
\r
4828 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4829 startedFromSetupPosition = TRUE;
\r
4832 CopyBoard(boards[0], initialPosition);
\r
4834 if(oldx != gameInfo.boardWidth ||
\r
4835 oldy != gameInfo.boardHeight ||
\r
4836 oldh != gameInfo.holdingsWidth
\r
4838 || oldv == VariantGothic || // For licensing popups
\r
4839 gameInfo.variant == VariantGothic
\r
4842 || oldv == VariantFalcon ||
\r
4843 gameInfo.variant == VariantFalcon
\r
4846 InitDrawingSizes(-2 ,0);
\r
4849 DrawPosition(TRUE, boards[currentMove]);
\r
4853 SendBoard(cps, moveNum)
\r
4854 ChessProgramState *cps;
\r
4857 char message[MSG_SIZ];
\r
4859 if (cps->useSetboard) {
\r
4860 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4861 sprintf(message, "setboard %s\n", fen);
\r
4862 SendToProgram(message, cps);
\r
4868 /* Kludge to set black to move, avoiding the troublesome and now
\r
4869 * deprecated "black" command.
\r
4871 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4873 SendToProgram("edit\n", cps);
\r
4874 SendToProgram("#\n", cps);
\r
4875 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4876 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4877 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4878 if ((int) *bp < (int) BlackPawn) {
\r
4879 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4880 AAA + j, ONE + i);
\r
4881 if(message[0] == '+' || message[0] == '~') {
\r
4882 sprintf(message, "%c%c%c+\n",
\r
4883 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4884 AAA + j, ONE + i);
\r
4886 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4887 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4888 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4890 SendToProgram(message, cps);
\r
4895 SendToProgram("c\n", cps);
\r
4896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4897 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4898 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4899 if (((int) *bp != (int) EmptySquare)
\r
4900 && ((int) *bp >= (int) BlackPawn)) {
\r
4901 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4902 AAA + j, ONE + i);
\r
4903 if(message[0] == '+' || message[0] == '~') {
\r
4904 sprintf(message, "%c%c%c+\n",
\r
4905 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4906 AAA + j, ONE + i);
\r
4908 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4909 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4910 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4912 SendToProgram(message, cps);
\r
4917 SendToProgram(".\n", cps);
\r
4919 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4923 IsPromotion(fromX, fromY, toX, toY)
\r
4924 int fromX, fromY, toX, toY;
\r
4926 /* [HGM] add Shogi promotions */
\r
4927 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4928 ChessSquare piece;
\r
4930 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4931 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4932 /* [HGM] Note to self: line above also weeds out drops */
\r
4933 piece = boards[currentMove][fromY][fromX];
\r
4934 if(gameInfo.variant == VariantShogi) {
\r
4935 promotionZoneSize = 3;
\r
4936 highestPromotingPiece = (int)WhiteKing;
\r
4937 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4938 and if in normal chess we then allow promotion to King, why not
\r
4939 allow promotion of other piece in Shogi? */
\r
4941 if((int)piece >= BlackPawn) {
\r
4942 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4944 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4946 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4947 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4949 return ( (int)piece <= highestPromotingPiece );
\r
4953 InPalace(row, column)
\r
4955 { /* [HGM] for Xiangqi */
\r
4956 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4957 column < (BOARD_WIDTH + 4)/2 &&
\r
4958 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4963 PieceForSquare (x, y)
\r
4967 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4970 return boards[currentMove][y][x];
\r
4974 OKToStartUserMove(x, y)
\r
4977 ChessSquare from_piece;
\r
4980 if (matchMode) return FALSE;
\r
4981 if (gameMode == EditPosition) return TRUE;
\r
4983 if (x >= 0 && y >= 0)
\r
4984 from_piece = boards[currentMove][y][x];
\r
4986 from_piece = EmptySquare;
\r
4988 if (from_piece == EmptySquare) return FALSE;
\r
4990 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4991 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4993 switch (gameMode) {
\r
4994 case PlayFromGameFile:
\r
4996 case TwoMachinesPlay:
\r
5000 case IcsObserving:
\r
5004 case MachinePlaysWhite:
\r
5005 case IcsPlayingBlack:
\r
5006 if (appData.zippyPlay) return FALSE;
\r
5007 if (white_piece) {
\r
5008 DisplayMoveError(_("You are playing Black"));
\r
5013 case MachinePlaysBlack:
\r
5014 case IcsPlayingWhite:
\r
5015 if (appData.zippyPlay) return FALSE;
\r
5016 if (!white_piece) {
\r
5017 DisplayMoveError(_("You are playing White"));
\r
5023 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5024 DisplayMoveError(_("It is White's turn"));
\r
5027 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5028 DisplayMoveError(_("It is Black's turn"));
\r
5031 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
5032 /* Editing correspondence game history */
\r
5033 /* Could disallow this or prompt for confirmation */
\r
5034 cmailOldMove = -1;
\r
5036 if (currentMove < forwardMostMove) {
\r
5037 /* Discarding moves */
\r
5038 /* Could prompt for confirmation here,
\r
5039 but I don't think that's such a good idea */
\r
5040 forwardMostMove = currentMove;
\r
5044 case BeginningOfGame:
\r
5045 if (appData.icsActive) return FALSE;
\r
5046 if (!appData.noChessProgram) {
\r
5047 if (!white_piece) {
\r
5048 DisplayMoveError(_("You are playing White"));
\r
5055 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5056 DisplayMoveError(_("It is White's turn"));
\r
5059 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5060 DisplayMoveError(_("It is Black's turn"));
\r
5066 case IcsExamining:
\r
5069 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
5070 && gameMode != AnalyzeFile && gameMode != Training) {
\r
5071 DisplayMoveError(_("Displayed position is not current"));
\r
5077 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
5078 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
5079 int lastLoadGameUseList = FALSE;
\r
5080 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
5081 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
5085 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
5086 int fromX, fromY, toX, toY;
\r
5089 ChessMove moveType;
\r
5090 ChessSquare pdown, pup;
\r
5092 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
5093 if ((fromX == toX) && (fromY == toY)) {
\r
5094 return ImpossibleMove;
\r
5097 /* [HGM] suppress all moves into holdings area and guard band */
\r
5098 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5099 return ImpossibleMove;
\r
5101 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5102 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5103 pdown = boards[currentMove][fromY][fromX];
\r
5104 pup = boards[currentMove][toY][toX];
\r
5105 if ( gameMode != EditPosition &&
\r
5106 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5107 WhitePawn <= pup && pup < BlackPawn ||
\r
5108 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5109 BlackPawn <= pup && pup < EmptySquare
\r
5110 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5111 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5112 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5114 return ImpossibleMove;
\r
5116 /* Check if the user is playing in turn. This is complicated because we
\r
5117 let the user "pick up" a piece before it is his turn. So the piece he
\r
5118 tried to pick up may have been captured by the time he puts it down!
\r
5119 Therefore we use the color the user is supposed to be playing in this
\r
5120 test, not the color of the piece that is currently on the starting
\r
5121 square---except in EditGame mode, where the user is playing both
\r
5122 sides; fortunately there the capture race can't happen. (It can
\r
5123 now happen in IcsExamining mode, but that's just too bad. The user
\r
5124 will get a somewhat confusing message in that case.)
\r
5127 switch (gameMode) {
\r
5128 case PlayFromGameFile:
\r
5130 case TwoMachinesPlay:
\r
5132 case IcsObserving:
\r
5134 /* We switched into a game mode where moves are not accepted,
\r
5135 perhaps while the mouse button was down. */
\r
5136 return ImpossibleMove;
\r
5138 case MachinePlaysWhite:
\r
5139 /* User is moving for Black */
\r
5140 if (WhiteOnMove(currentMove)) {
\r
5141 DisplayMoveError(_("It is White's turn"));
\r
5142 return ImpossibleMove;
\r
5146 case MachinePlaysBlack:
\r
5147 /* User is moving for White */
\r
5148 if (!WhiteOnMove(currentMove)) {
\r
5149 DisplayMoveError(_("It is Black's turn"));
\r
5150 return ImpossibleMove;
\r
5155 case IcsExamining:
\r
5156 case BeginningOfGame:
\r
5159 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5160 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5161 /* User is moving for Black */
\r
5162 if (WhiteOnMove(currentMove)) {
\r
5163 DisplayMoveError(_("It is White's turn"));
\r
5164 return ImpossibleMove;
\r
5167 /* User is moving for White */
\r
5168 if (!WhiteOnMove(currentMove)) {
\r
5169 DisplayMoveError(_("It is Black's turn"));
\r
5170 return ImpossibleMove;
\r
5175 case IcsPlayingBlack:
\r
5176 /* User is moving for Black */
\r
5177 if (WhiteOnMove(currentMove)) {
\r
5178 if (!appData.premove) {
\r
5179 DisplayMoveError(_("It is White's turn"));
\r
5180 } else if (toX >= 0 && toY >= 0) {
\r
5183 premoveFromX = fromX;
\r
5184 premoveFromY = fromY;
\r
5185 premovePromoChar = promoChar;
\r
5187 if (appData.debugMode)
\r
5188 fprintf(debugFP, "Got premove: fromX %d,"
\r
5189 "fromY %d, toX %d, toY %d\n",
\r
5190 fromX, fromY, toX, toY);
\r
5192 return ImpossibleMove;
\r
5196 case IcsPlayingWhite:
\r
5197 /* User is moving for White */
\r
5198 if (!WhiteOnMove(currentMove)) {
\r
5199 if (!appData.premove) {
\r
5200 DisplayMoveError(_("It is Black's turn"));
\r
5201 } else if (toX >= 0 && toY >= 0) {
\r
5204 premoveFromX = fromX;
\r
5205 premoveFromY = fromY;
\r
5206 premovePromoChar = promoChar;
\r
5208 if (appData.debugMode)
\r
5209 fprintf(debugFP, "Got premove: fromX %d,"
\r
5210 "fromY %d, toX %d, toY %d\n",
\r
5211 fromX, fromY, toX, toY);
\r
5213 return ImpossibleMove;
\r
5220 case EditPosition:
\r
5221 /* EditPosition, empty square, or different color piece;
\r
5222 click-click move is possible */
\r
5223 if (toX == -2 || toY == -2) {
\r
5224 boards[0][fromY][fromX] = EmptySquare;
\r
5225 return AmbiguousMove;
\r
5226 } else if (toX >= 0 && toY >= 0) {
\r
5227 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5228 boards[0][fromY][fromX] = EmptySquare;
\r
5229 return AmbiguousMove;
\r
5231 return ImpossibleMove;
\r
5234 /* [HGM] If move started in holdings, it means a drop */
\r
5235 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5236 if( pup != EmptySquare ) return ImpossibleMove;
\r
5237 if(appData.testLegality) {
\r
5238 /* it would be more logical if LegalityTest() also figured out
\r
5239 * which drops are legal. For now we forbid pawns on back rank.
\r
5240 * Shogi is on its own here...
\r
5242 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5243 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5244 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5246 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5249 userOfferedDraw = FALSE;
\r
5251 /* [HGM] always test for legality, to get promotion info */
\r
5252 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5253 epStatus[currentMove], castlingRights[currentMove],
\r
5254 fromY, fromX, toY, toX, promoChar);
\r
5256 /* [HGM] but possibly ignore an IllegalMove result */
\r
5257 if (appData.testLegality) {
\r
5258 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5259 DisplayMoveError(_("Illegal move"));
\r
5260 return ImpossibleMove;
\r
5263 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5265 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5266 function is made into one that returns an OK move type if FinishMove
\r
5267 should be called. This to give the calling driver routine the
\r
5268 opportunity to finish the userMove input with a promotion popup,
\r
5269 without bothering the user with this for invalid or illegal moves */
\r
5271 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5274 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5276 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5277 ChessMove moveType;
\r
5278 int fromX, fromY, toX, toY;
\r
5279 /*char*/int promoChar;
\r
5281 char *bookHit = 0;
\r
5282 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5283 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5284 // [HGM] superchess: suppress promotions to non-available piece
\r
5285 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5286 if(WhiteOnMove(currentMove)) {
\r
5287 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5289 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5293 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5294 move type in caller when we know the move is a legal promotion */
\r
5295 if(moveType == NormalMove && promoChar)
\r
5296 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5297 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5298 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5299 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5300 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5301 fromX = boards[currentMove][fromY][fromX];
\r
5302 fromY = DROP_RANK;
\r
5305 /* [HGM] <popupFix> The following if has been moved here from
\r
5306 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5307 piece drops in training games?), and because it can only be
\r
5308 performed after it is known to what we promote. */
\r
5309 if (gameMode == Training) {
\r
5310 /* compare the move played on the board to the next move in the
\r
5311 * game. If they match, display the move and the opponent's response.
\r
5312 * If they don't match, display an error message.
\r
5316 CopyBoard(testBoard, boards[currentMove]);
\r
5317 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5319 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5320 ForwardInner(currentMove+1);
\r
5322 /* Autoplay the opponent's response.
\r
5323 * if appData.animate was TRUE when Training mode was entered,
\r
5324 * the response will be animated.
\r
5326 saveAnimate = appData.animate;
\r
5327 appData.animate = animateTraining;
\r
5328 ForwardInner(currentMove+1);
\r
5329 appData.animate = saveAnimate;
\r
5331 /* check for the end of the game */
\r
5332 if (currentMove >= forwardMostMove) {
\r
5333 gameMode = PlayFromGameFile;
\r
5335 SetTrainingModeOff();
\r
5336 DisplayInformation(_("End of game"));
\r
5339 DisplayError(_("Incorrect move"), 0);
\r
5344 /* Ok, now we know that the move is good, so we can kill
\r
5345 the previous line in Analysis Mode */
\r
5346 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5347 forwardMostMove = currentMove;
\r
5350 /* If we need the chess program but it's dead, restart it */
\r
5351 ResurrectChessProgram();
\r
5353 /* A user move restarts a paused game*/
\r
5357 thinkOutput[0] = NULLCHAR;
\r
5359 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5361 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5362 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5363 // [HGM] superchess: take promotion piece out of holdings
\r
5364 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5365 if(WhiteOnMove(forwardMostMove-1)) {
\r
5366 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5367 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5369 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5370 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5374 if (gameMode == BeginningOfGame) {
\r
5375 if (appData.noChessProgram) {
\r
5376 gameMode = EditGame;
\r
5379 char buf[MSG_SIZ];
\r
5380 gameMode = MachinePlaysBlack;
\r
5383 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5384 DisplayTitle(buf);
\r
5385 if (first.sendName) {
\r
5386 sprintf(buf, "name %s\n", gameInfo.white);
\r
5387 SendToProgram(buf, &first);
\r
5393 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5394 /* Relay move to ICS or chess engine */
\r
5395 if (appData.icsActive) {
\r
5396 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5397 gameMode == IcsExamining) {
\r
5398 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5399 ics_user_moved = 1;
\r
5402 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5403 gameMode == MachinePlaysWhite ||
\r
5404 gameMode == MachinePlaysBlack)) {
\r
5405 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5407 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5408 // [HGM] book: if program might be playing, let it use book
\r
5409 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5410 first.maybeThinking = TRUE;
\r
5411 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5412 if (currentMove == cmailOldMove + 1) {
\r
5413 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5417 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5419 switch (gameMode) {
\r
5421 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5422 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5426 case MT_CHECKMATE:
\r
5427 if (WhiteOnMove(currentMove)) {
\r
5428 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5430 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5433 case MT_STALEMATE:
\r
5434 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5439 case MachinePlaysBlack:
\r
5440 case MachinePlaysWhite:
\r
5441 /* disable certain menu options while machine is thinking */
\r
5442 SetMachineThinkingEnables();
\r
5449 if(bookHit) { // [HGM] book: simulate book reply
\r
5450 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5452 programStats.nodes = programStats.depth = programStats.time =
\r
5453 programStats.score = programStats.got_only_move = 0;
\r
5454 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5456 strcpy(bookMove, "move ");
\r
5457 strcat(bookMove, bookHit);
\r
5458 HandleMachineMove(bookMove, &first);
\r
5464 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5465 int fromX, fromY, toX, toY;
\r
5468 /* [HGM] This routine was added to allow calling of its two logical
\r
5469 parts from other modules in the old way. Before, UserMoveEvent()
\r
5470 automatically called FinishMove() if the move was OK, and returned
\r
5471 otherwise. I separated the two, in order to make it possible to
\r
5472 slip a promotion popup in between. But that it always needs two
\r
5473 calls, to the first part, (now called UserMoveTest() ), and to
\r
5474 FinishMove if the first part succeeded. Calls that do not need
\r
5475 to do anything in between, can call this routine the old way.
\r
5477 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5478 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5479 if(moveType != ImpossibleMove)
\r
5480 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5483 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5485 // char * hint = lastHint;
\r
5486 FrontEndProgramStats stats;
\r
5488 stats.which = cps == &first ? 0 : 1;
\r
5489 stats.depth = cpstats->depth;
\r
5490 stats.nodes = cpstats->nodes;
\r
5491 stats.score = cpstats->score;
\r
5492 stats.time = cpstats->time;
\r
5493 stats.pv = cpstats->movelist;
\r
5494 stats.hint = lastHint;
\r
5495 stats.an_move_index = 0;
\r
5496 stats.an_move_count = 0;
\r
5498 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5499 stats.hint = cpstats->move_name;
\r
5500 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5501 stats.an_move_count = cpstats->nr_moves;
\r
5504 SetProgramStats( &stats );
\r
5507 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5508 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5509 char *bookHit = NULL;
\r
5511 //first determine if the incoming move brings opponent into his book
\r
5512 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5513 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5514 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5515 if(bookHit != NULL && !cps->bookSuspend) {
\r
5516 // make sure opponent is not going to reply after receiving move to book position
\r
5517 SendToProgram("force\n", cps);
\r
5518 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5520 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5521 // now arrange restart after book miss
\r
5523 // after a book hit we never send 'go', and the code after the call to this routine
\r
5524 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5525 char buf[MSG_SIZ];
\r
5526 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5527 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5528 SendToProgram(buf, cps);
\r
5529 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5530 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5531 SendToProgram("go\n", cps);
\r
5532 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5533 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5534 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5535 SendToProgram("go\n", cps);
\r
5536 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5538 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5541 char *savedMessage;
\r
5542 ChessProgramState *savedState;
\r
5543 void DeferredBookMove(void)
\r
5545 if(savedState->lastPing != savedState->lastPong)
\r
5546 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5548 HandleMachineMove(savedMessage, savedState);
\r
5552 HandleMachineMove(message, cps)
\r
5554 ChessProgramState *cps;
\r
5556 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5557 char realname[MSG_SIZ];
\r
5558 int fromX, fromY, toX, toY;
\r
5559 ChessMove moveType;
\r
5565 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5567 * Kludge to ignore BEL characters
\r
5569 while (*message == '\007') message++;
\r
5572 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5574 if(cps->debug && *message == '#') return;
\r
5577 * Look for book output
\r
5579 if (cps == &first && bookRequested) {
\r
5580 if (message[0] == '\t' || message[0] == ' ') {
\r
5581 /* Part of the book output is here; append it */
\r
5582 strcat(bookOutput, message);
\r
5583 strcat(bookOutput, " \n");
\r
5585 } else if (bookOutput[0] != NULLCHAR) {
\r
5586 /* All of book output has arrived; display it */
\r
5587 char *p = bookOutput;
\r
5588 while (*p != NULLCHAR) {
\r
5589 if (*p == '\t') *p = ' ';
\r
5592 DisplayInformation(bookOutput);
\r
5593 bookRequested = FALSE;
\r
5594 /* Fall through to parse the current output */
\r
5599 * Look for machine move.
\r
5601 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5602 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5604 /* This method is only useful on engines that support ping */
\r
5605 if (cps->lastPing != cps->lastPong) {
\r
5606 if (gameMode == BeginningOfGame) {
\r
5607 /* Extra move from before last new; ignore */
\r
5608 if (appData.debugMode) {
\r
5609 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5612 if (appData.debugMode) {
\r
5613 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5614 cps->which, gameMode);
\r
5617 SendToProgram("undo\n", cps);
\r
5622 switch (gameMode) {
\r
5623 case BeginningOfGame:
\r
5624 /* Extra move from before last reset; ignore */
\r
5625 if (appData.debugMode) {
\r
5626 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5633 /* Extra move after we tried to stop. The mode test is
\r
5634 not a reliable way of detecting this problem, but it's
\r
5635 the best we can do on engines that don't support ping.
\r
5637 if (appData.debugMode) {
\r
5638 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5639 cps->which, gameMode);
\r
5641 SendToProgram("undo\n", cps);
\r
5644 case MachinePlaysWhite:
\r
5645 case IcsPlayingWhite:
\r
5646 machineWhite = TRUE;
\r
5649 case MachinePlaysBlack:
\r
5650 case IcsPlayingBlack:
\r
5651 machineWhite = FALSE;
\r
5654 case TwoMachinesPlay:
\r
5655 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5658 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5659 if (appData.debugMode) {
\r
5661 "Ignoring move out of turn by %s, gameMode %d"
\r
5662 ", forwardMost %d\n",
\r
5663 cps->which, gameMode, forwardMostMove);
\r
5668 if (appData.debugMode) { int f = forwardMostMove;
\r
5669 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5670 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5672 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5673 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5674 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5675 /* Machine move could not be parsed; ignore it. */
\r
5676 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5677 machineMove, cps->which);
\r
5678 DisplayError(buf1, 0);
\r
5679 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5680 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5681 if (gameMode == TwoMachinesPlay) {
\r
5682 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5688 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5689 /* So we have to redo legality test with true e.p. status here, */
\r
5690 /* to make sure an illegal e.p. capture does not slip through, */
\r
5691 /* to cause a forfeit on a justified illegal-move complaint */
\r
5692 /* of the opponent. */
\r
5693 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5694 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5696 ChessMove moveType;
\r
5697 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5698 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5699 fromY, fromX, toY, toX, promoChar);
\r
5700 if (appData.debugMode) {
\r
5702 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5703 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5704 fprintf(debugFP, "castling rights\n");
\r
5706 if(moveType == IllegalMove) {
\r
5707 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5708 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5709 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5711 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5712 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5713 when they shouldn't (like TSCP-Gothic) */
\r
5714 switch(moveType) {
\r
5715 case WhiteASideCastleFR:
\r
5716 case BlackASideCastleFR:
\r
5718 currentMoveString[2]++;
\r
5720 case WhiteHSideCastleFR:
\r
5721 case BlackHSideCastleFR:
\r
5723 currentMoveString[2]--;
\r
5725 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5728 hintRequested = FALSE;
\r
5729 lastHint[0] = NULLCHAR;
\r
5730 bookRequested = FALSE;
\r
5731 /* Program may be pondering now */
\r
5732 cps->maybeThinking = TRUE;
\r
5733 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5734 if (cps->offeredDraw) cps->offeredDraw--;
\r
5737 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5739 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5740 ics_user_moved = 1;
\r
5741 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5742 char buf[3*MSG_SIZ];
\r
5744 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5745 programStats.depth,
\r
5746 programStats.score / 100.,
\r
5747 programStats.time / 100.,
\r
5748 u64ToDouble(programStats.nodes),
\r
5749 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5750 programStats.movelist);
\r
5755 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5756 strcpy(machineMove, currentMoveString);
\r
5757 strcat(machineMove, "\n");
\r
5758 strcpy(moveList[forwardMostMove], machineMove);
\r
5760 /* [AS] Save move info and clear stats for next move */
\r
5761 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5762 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5763 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5764 ClearProgramStats();
\r
5765 thinkOutput[0] = NULLCHAR;
\r
5766 hiddenThinkOutputState = 0;
\r
5768 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5770 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5771 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5774 while( count < adjudicateLossPlies ) {
\r
5775 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5778 score = -score; /* Flip score for winning side */
\r
5781 if( score > adjudicateLossThreshold ) {
\r
5788 if( count >= adjudicateLossPlies ) {
\r
5789 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5791 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5792 "Xboard adjudication",
\r
5799 if( gameMode == TwoMachinesPlay ) {
\r
5800 // [HGM] some adjudications useful with buggy engines
\r
5801 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5802 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5804 if(appData.testLegality)
\r
5805 // don't wait for engine to announce game end if we can judge ourselves
\r
5806 switch (MateTest(boards[forwardMostMove],
\r
5807 PosFlags(forwardMostMove), epFile,
\r
5808 castlingRights[forwardMostMove]) ) {
\r
5813 case MT_STALEMATE:
\r
5814 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5815 if(appData.checkMates) {
\r
5816 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5817 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5818 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5822 case MT_CHECKMATE:
\r
5823 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5824 if(appData.checkMates) {
\r
5825 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5826 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5827 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5828 "Xboard adjudication: Checkmate",
\r
5834 if( appData.testLegality )
\r
5835 { /* [HGM] Some more adjudications for obstinate engines */
\r
5836 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5837 NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,
\r
5838 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5839 static int moveCount = 6;
\r
5841 /* First absolutely insufficient mating material. Count what is on board. */
\r
5842 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5843 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5847 { /* count B,N,R and other of each side */
\r
5851 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5852 bishopsColor |= 1 << ((i^j)&1);
\r
5857 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5858 bishopsColor |= 1 << ((i^j)&1);
\r
5868 case EmptySquare:
\r
5873 PawnAdvance += m; NrPawns++;
\r
5875 NrPieces += (p != EmptySquare);
\r
5876 NrW += ((int)p < (int)BlackPawn);
\r
5877 if(gameInfo.variant == VariantXiangqi &&
\r
5878 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5879 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5880 NrW -= ((int)p < (int)BlackPawn);
\r
5884 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5885 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5886 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5887 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5889 /* always flag draws, for judging claims */
\r
5890 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5892 if(appData.materialDraws) {
\r
5893 /* but only adjudicate them if adjudication enabled */
\r
5894 SendToProgram("force\n", cps->other); // suppress reply
\r
5895 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5896 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5897 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5902 /* Shatranj baring rule */
\r
5903 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5906 if(--bare < 0 && appData.checkMates) {
\r
5907 /* but only adjudicate them if adjudication enabled */
\r
5908 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5909 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5910 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5911 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5916 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5917 if(NrPieces == 4 &&
\r
5918 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5919 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5920 || NrWN==2 || NrBN==2 /* KNNK */
\r
5921 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5923 if(--moveCount < 0 && appData.trivialDraws)
\r
5924 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5925 SendToProgram("force\n", cps->other); // suppress reply
\r
5926 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5927 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5928 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5931 } else moveCount = 6;
\r
5935 if (appData.debugMode) { int i;
\r
5936 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5937 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5938 appData.drawRepeats);
\r
5939 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5940 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5944 /* Check for rep-draws */
\r
5946 for(k = forwardMostMove-2;
\r
5947 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5948 epStatus[k] < EP_UNKNOWN &&
\r
5949 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5953 if (appData.debugMode) {
\r
5954 fprintf(debugFP, " loop\n");
\r
5957 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5959 if (appData.debugMode) {
\r
5960 fprintf(debugFP, "match\n");
\r
5963 /* compare castling rights */
\r
5964 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5965 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5966 rights++; /* King lost rights, while rook still had them */
\r
5967 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5968 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5969 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5970 rights++; /* but at least one rook lost them */
\r
5972 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5973 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5975 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5976 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5977 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5981 if (appData.debugMode) {
\r
5982 for(i=0; i<nrCastlingRights; i++)
\r
5983 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5986 if (appData.debugMode) {
\r
5987 fprintf(debugFP, " %d %d\n", rights, k);
\r
5990 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5991 && appData.drawRepeats > 1) {
\r
5992 /* adjudicate after user-specified nr of repeats */
\r
5993 SendToProgram("force\n", cps->other); // suppress reply
\r
5994 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5995 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5996 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5997 // [HGM] xiangqi: check for forbidden perpetuals
\r
5998 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5999 for(m=forwardMostMove; m>k; m-=2) {
\r
6000 if(MateTest(boards[m], PosFlags(m),
\r
6001 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
6002 ourPerpetual = 0; // the current mover did not always check
\r
6003 if(MateTest(boards[m-1], PosFlags(m-1),
\r
6004 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
6005 hisPerpetual = 0; // the opponent did not always check
\r
6007 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
6008 ourPerpetual, hisPerpetual);
\r
6009 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6010 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6011 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6014 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6015 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6016 // Now check for perpetual chases
\r
6017 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
6018 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
6019 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
6020 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6021 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6022 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
6025 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
6026 break; // Abort repetition-checking loop.
\r
6028 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
6030 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6033 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6034 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6038 /* Now we test for 50-move draws. Determine ply count */
\r
6039 count = forwardMostMove;
\r
6040 /* look for last irreversble move */
\r
6041 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6043 /* if we hit starting position, add initial plies */
\r
6044 if( count == backwardMostMove )
\r
6045 count -= initialRulePlies;
\r
6046 count = forwardMostMove - count;
\r
6048 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6049 /* this is used to judge if draw claims are legal */
\r
6050 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6051 SendToProgram("force\n", cps->other); // suppress reply
\r
6052 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6053 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6054 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6058 /* if draw offer is pending, treat it as a draw claim
\r
6059 * when draw condition present, to allow engines a way to
\r
6060 * claim draws before making their move to avoid a race
\r
6061 * condition occurring after their move
\r
6063 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6065 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6066 p = "Draw claim: 50-move rule";
\r
6067 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6068 p = "Draw claim: 3-fold repetition";
\r
6069 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6070 p = "Draw claim: insufficient mating material";
\r
6072 SendToProgram("force\n", cps->other); // suppress reply
\r
6073 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6074 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6075 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6081 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6082 SendToProgram("force\n", cps->other); // suppress reply
\r
6083 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6084 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6086 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6093 if (gameMode == TwoMachinesPlay) {
\r
6094 /* [HGM] relaying draw offers moved to after reception of move */
\r
6095 /* and interpreting offer as claim if it brings draw condition */
\r
6096 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6097 SendToProgram("draw\n", cps->other);
\r
6099 if (cps->other->sendTime) {
\r
6100 SendTimeRemaining(cps->other,
\r
6101 cps->other->twoMachinesColor[0] == 'w');
\r
6103 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6104 if (firstMove && !bookHit) {
\r
6105 firstMove = FALSE;
\r
6106 if (cps->other->useColors) {
\r
6107 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6109 SendToProgram("go\n", cps->other);
\r
6111 cps->other->maybeThinking = TRUE;
\r
6114 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6116 if (!pausing && appData.ringBellAfterMoves) {
\r
6121 * Reenable menu items that were disabled while
\r
6122 * machine was thinking
\r
6124 if (gameMode != TwoMachinesPlay)
\r
6125 SetUserThinkingEnables();
\r
6127 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6128 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6129 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6131 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6133 strcpy(bookMove, "move ");
\r
6134 strcat(bookMove, bookHit);
\r
6135 message = bookMove;
\r
6137 programStats.nodes = programStats.depth = programStats.time =
\r
6138 programStats.score = programStats.got_only_move = 0;
\r
6139 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6141 if(cps->lastPing != cps->lastPong) {
\r
6142 savedMessage = message; // args for deferred call
\r
6144 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6147 goto FakeBookMove;
\r
6153 /* Set special modes for chess engines. Later something general
\r
6154 * could be added here; for now there is just one kludge feature,
\r
6155 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6156 * when "xboard" is given as an interactive command.
\r
6158 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6159 cps->useSigint = FALSE;
\r
6160 cps->useSigterm = FALSE;
\r
6163 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6164 * want this, I was asked to put it in, and obliged.
\r
6166 if (!strncmp(message, "setboard ", 9)) {
\r
6167 Board initial_position; int i;
\r
6169 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6171 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6172 DisplayError(_("Bad FEN received from engine"), 0);
\r
6175 Reset(FALSE, FALSE);
\r
6176 CopyBoard(boards[0], initial_position);
\r
6177 initialRulePlies = FENrulePlies;
\r
6178 epStatus[0] = FENepStatus;
\r
6179 for( i=0; i<nrCastlingRights; i++ )
\r
6180 castlingRights[0][i] = FENcastlingRights[i];
\r
6181 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6182 else gameMode = MachinePlaysBlack;
\r
6183 DrawPosition(FALSE, boards[currentMove]);
\r
6189 * Look for communication commands
\r
6191 if (!strncmp(message, "telluser ", 9)) {
\r
6192 DisplayNote(message + 9);
\r
6195 if (!strncmp(message, "tellusererror ", 14)) {
\r
6196 DisplayError(message + 14, 0);
\r
6199 if (!strncmp(message, "tellopponent ", 13)) {
\r
6200 if (appData.icsActive) {
\r
6202 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6206 DisplayNote(message + 13);
\r
6210 if (!strncmp(message, "tellothers ", 11)) {
\r
6211 if (appData.icsActive) {
\r
6213 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6219 if (!strncmp(message, "tellall ", 8)) {
\r
6220 if (appData.icsActive) {
\r
6222 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6226 DisplayNote(message + 8);
\r
6230 if (strncmp(message, "warning", 7) == 0) {
\r
6231 /* Undocumented feature, use tellusererror in new code */
\r
6232 DisplayError(message, 0);
\r
6235 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6236 strcpy(realname, cps->tidy);
\r
6237 strcat(realname, " query");
\r
6238 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6241 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6242 * sent until we are logged on. Crafty kibitzes have been known to
\r
6243 * interfere with the login process.
\r
6246 if (!strncmp(message, "tellics ", 8)) {
\r
6247 SendToICS(message + 8);
\r
6251 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6252 SendToICS(ics_prefix);
\r
6253 SendToICS(message + 15);
\r
6257 /* The following are for backward compatibility only */
\r
6258 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6259 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6260 SendToICS(ics_prefix);
\r
6261 SendToICS(message);
\r
6266 if (strncmp(message, "feature ", 8) == 0) {
\r
6267 ParseFeatures(message+8, cps);
\r
6269 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6273 * If the move is illegal, cancel it and redraw the board.
\r
6274 * Also deal with other error cases. Matching is rather loose
\r
6275 * here to accommodate engines written before the spec.
\r
6277 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6278 strncmp(message, "Error", 5) == 0) {
\r
6279 if (StrStr(message, "name") ||
\r
6280 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6281 StrStr(message, "result") || StrStr(message, "board") ||
\r
6282 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6283 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6284 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6285 StrStr(message, "accepted")) {
\r
6288 if (StrStr(message, "protover")) {
\r
6289 /* Program is responding to input, so it's apparently done
\r
6290 initializing, and this error message indicates it is
\r
6291 protocol version 1. So we don't need to wait any longer
\r
6292 for it to initialize and send feature commands. */
\r
6293 FeatureDone(cps, 1);
\r
6294 cps->protocolVersion = 1;
\r
6297 cps->maybeThinking = FALSE;
\r
6299 if (StrStr(message, "draw")) {
\r
6300 /* Program doesn't have "draw" command */
\r
6301 cps->sendDrawOffers = 0;
\r
6304 if (cps->sendTime != 1 &&
\r
6305 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6306 /* Program apparently doesn't have "time" or "otim" command */
\r
6307 cps->sendTime = 0;
\r
6310 if (StrStr(message, "analyze")) {
\r
6311 cps->analysisSupport = FALSE;
\r
6312 cps->analyzing = FALSE;
\r
6313 Reset(FALSE, TRUE);
\r
6314 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6315 DisplayError(buf2, 0);
\r
6318 if (StrStr(message, "(no matching move)st")) {
\r
6319 /* Special kludge for GNU Chess 4 only */
\r
6320 cps->stKludge = TRUE;
\r
6321 SendTimeControl(cps, movesPerSession, timeControl,
\r
6322 timeIncrement, appData.searchDepth,
\r
6326 if (StrStr(message, "(no matching move)sd")) {
\r
6327 /* Special kludge for GNU Chess 4 only */
\r
6328 cps->sdKludge = TRUE;
\r
6329 SendTimeControl(cps, movesPerSession, timeControl,
\r
6330 timeIncrement, appData.searchDepth,
\r
6334 if (!StrStr(message, "llegal")) {
\r
6337 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6338 gameMode == IcsIdle) return;
\r
6339 if (forwardMostMove <= backwardMostMove) return;
\r
6341 /* Following removed: it caused a bug where a real illegal move
\r
6342 message in analyze mored would be ignored. */
\r
6343 if (cps == &first && programStats.ok_to_send == 0) {
\r
6344 /* Bogus message from Crafty responding to "." This filtering
\r
6345 can miss some of the bad messages, but fortunately the bug
\r
6346 is fixed in current Crafty versions, so it doesn't matter. */
\r
6350 if (pausing) PauseEvent();
\r
6351 if (gameMode == PlayFromGameFile) {
\r
6352 /* Stop reading this game file */
\r
6353 gameMode = EditGame;
\r
6356 currentMove = --forwardMostMove;
\r
6357 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6359 DisplayBothClocks();
\r
6360 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6361 parseList[currentMove], cps->which);
\r
6362 DisplayMoveError(buf1);
\r
6363 DrawPosition(FALSE, boards[currentMove]);
\r
6365 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6366 /* only passes fully legal moves */
\r
6367 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6368 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6369 "False illegal-move claim", GE_XBOARD );
\r
6373 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6374 /* Program has a broken "time" command that
\r
6375 outputs a string not ending in newline.
\r
6377 cps->sendTime = 0;
\r
6381 * If chess program startup fails, exit with an error message.
\r
6382 * Attempts to recover here are futile.
\r
6384 if ((StrStr(message, "unknown host") != NULL)
\r
6385 || (StrStr(message, "No remote directory") != NULL)
\r
6386 || (StrStr(message, "not found") != NULL)
\r
6387 || (StrStr(message, "No such file") != NULL)
\r
6388 || (StrStr(message, "can't alloc") != NULL)
\r
6389 || (StrStr(message, "Permission denied") != NULL)) {
\r
6391 cps->maybeThinking = FALSE;
\r
6392 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6393 cps->which, cps->program, cps->host, message);
\r
6394 RemoveInputSource(cps->isr);
\r
6395 DisplayFatalError(buf1, 0, 1);
\r
6400 * Look for hint output
\r
6402 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6403 if (cps == &first && hintRequested) {
\r
6404 hintRequested = FALSE;
\r
6405 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6406 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6407 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6408 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6409 fromY, fromX, toY, toX, promoChar, buf1);
\r
6410 sprintf(buf2, _("Hint: %s"), buf1);
\r
6411 DisplayInformation(buf2);
\r
6413 /* Hint move could not be parsed!? */
\r
6415 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6416 buf1, cps->which);
\r
6417 DisplayError(buf2, 0);
\r
6420 strcpy(lastHint, buf1);
\r
6426 * Ignore other messages if game is not in progress
\r
6428 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6429 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6432 * look for win, lose, draw, or draw offer
\r
6434 if (strncmp(message, "1-0", 3) == 0) {
\r
6435 char *p, *q, *r = "";
\r
6436 p = strchr(message, '{');
\r
6438 q = strchr(p, '}');
\r
6444 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6446 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6447 char *p, *q, *r = "";
\r
6448 p = strchr(message, '{');
\r
6450 q = strchr(p, '}');
\r
6456 /* Kludge for Arasan 4.1 bug */
\r
6457 if (strcmp(r, "Black resigns") == 0) {
\r
6458 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6461 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6463 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6464 char *p, *q, *r = "";
\r
6465 p = strchr(message, '{');
\r
6467 q = strchr(p, '}');
\r
6474 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6477 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6478 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6480 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6481 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6483 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6484 strncmp(message, "Black matches", 13) == 0 ) {
\r
6485 /* [HGM] ignore GNUShogi noises */
\r
6487 } else if (strncmp(message, "White", 5) == 0 &&
\r
6488 message[5] != '(' &&
\r
6489 StrStr(message, "Black") == NULL) {
\r
6490 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6492 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6493 message[5] != '(') {
\r
6494 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6496 } else if (strcmp(message, "resign") == 0 ||
\r
6497 strcmp(message, "computer resigns") == 0) {
\r
6498 switch (gameMode) {
\r
6499 case MachinePlaysBlack:
\r
6500 case IcsPlayingBlack:
\r
6501 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6503 case MachinePlaysWhite:
\r
6504 case IcsPlayingWhite:
\r
6505 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6507 case TwoMachinesPlay:
\r
6508 if (cps->twoMachinesColor[0] == 'w')
\r
6509 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6511 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6514 /* can't happen */
\r
6518 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6519 switch (gameMode) {
\r
6520 case MachinePlaysBlack:
\r
6521 case IcsPlayingBlack:
\r
6522 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6524 case MachinePlaysWhite:
\r
6525 case IcsPlayingWhite:
\r
6526 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6528 case TwoMachinesPlay:
\r
6529 if (cps->twoMachinesColor[0] == 'w')
\r
6530 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6532 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6535 /* can't happen */
\r
6539 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6540 switch (gameMode) {
\r
6541 case MachinePlaysBlack:
\r
6542 case IcsPlayingBlack:
\r
6543 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6545 case MachinePlaysWhite:
\r
6546 case IcsPlayingWhite:
\r
6547 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6549 case TwoMachinesPlay:
\r
6550 if (cps->twoMachinesColor[0] == 'w')
\r
6551 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6553 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6556 /* can't happen */
\r
6560 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6561 if (WhiteOnMove(forwardMostMove)) {
\r
6562 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6564 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6567 } else if (strstr(message, "Draw") != NULL ||
\r
6568 strstr(message, "game is a draw") != NULL) {
\r
6569 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6571 } else if (strstr(message, "offer") != NULL &&
\r
6572 strstr(message, "draw") != NULL) {
\r
6574 if (appData.zippyPlay && first.initDone) {
\r
6575 /* Relay offer to ICS */
\r
6576 SendToICS(ics_prefix);
\r
6577 SendToICS("draw\n");
\r
6580 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6581 if (gameMode == TwoMachinesPlay) {
\r
6582 if (cps->other->offeredDraw) {
\r
6583 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6584 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6585 /* until after we also have move, to see if it is really claim */
\r
6589 if (cps->other->sendDrawOffers) {
\r
6590 SendToProgram("draw\n", cps->other);
\r
6594 } else if (gameMode == MachinePlaysWhite ||
\r
6595 gameMode == MachinePlaysBlack) {
\r
6596 if (userOfferedDraw) {
\r
6597 DisplayInformation(_("Machine accepts your draw offer"));
\r
6598 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6600 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6607 * Look for thinking output
\r
6609 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6610 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6612 int plylev, mvleft, mvtot, curscore, time;
\r
6613 char mvname[MOVE_LEN];
\r
6614 u64 nodes; // [DM]
\r
6616 int ignore = FALSE;
\r
6617 int prefixHint = FALSE;
\r
6618 mvname[0] = NULLCHAR;
\r
6620 switch (gameMode) {
\r
6621 case MachinePlaysBlack:
\r
6622 case IcsPlayingBlack:
\r
6623 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6625 case MachinePlaysWhite:
\r
6626 case IcsPlayingWhite:
\r
6627 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6632 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6633 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6635 case TwoMachinesPlay:
\r
6636 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6646 buf1[0] = NULLCHAR;
\r
6647 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6648 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6650 if (plyext != ' ' && plyext != '\t') {
\r
6654 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6655 if( cps->scoreIsAbsolute &&
\r
6656 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6658 curscore = -curscore;
\r
6662 programStats.depth = plylev;
\r
6663 programStats.nodes = nodes;
\r
6664 programStats.time = time;
\r
6665 programStats.score = curscore;
\r
6666 programStats.got_only_move = 0;
\r
6668 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6671 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6672 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6673 if(WhiteOnMove(forwardMostMove))
\r
6674 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6675 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6678 /* Buffer overflow protection */
\r
6679 if (buf1[0] != NULLCHAR) {
\r
6680 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6681 && appData.debugMode) {
\r
6683 "PV is too long; using the first %d bytes.\n",
\r
6684 sizeof(programStats.movelist) - 1);
\r
6687 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6689 sprintf(programStats.movelist, " no PV\n");
\r
6692 if (programStats.seen_stat) {
\r
6693 programStats.ok_to_send = 1;
\r
6696 if (strchr(programStats.movelist, '(') != NULL) {
\r
6697 programStats.line_is_book = 1;
\r
6698 programStats.nr_moves = 0;
\r
6699 programStats.moves_left = 0;
\r
6701 programStats.line_is_book = 0;
\r
6704 SendProgramStatsToFrontend( cps, &programStats );
\r
6707 [AS] Protect the thinkOutput buffer from overflow... this
\r
6708 is only useful if buf1 hasn't overflowed first!
\r
6710 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6712 (gameMode == TwoMachinesPlay ?
\r
6713 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6714 ((double) curscore) / 100.0,
\r
6715 prefixHint ? lastHint : "",
\r
6716 prefixHint ? " " : "" );
\r
6718 if( buf1[0] != NULLCHAR ) {
\r
6719 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6721 if( strlen(buf1) > max_len ) {
\r
6722 if( appData.debugMode) {
\r
6723 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6725 buf1[max_len+1] = '\0';
\r
6728 strcat( thinkOutput, buf1 );
\r
6731 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6732 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6733 DisplayMove(currentMove - 1);
\r
6734 DisplayAnalysis();
\r
6738 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6739 /* crafty (9.25+) says "(only move) <move>"
\r
6740 * if there is only 1 legal move
\r
6742 sscanf(p, "(only move) %s", buf1);
\r
6743 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6744 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6745 programStats.depth = 1;
\r
6746 programStats.nr_moves = 1;
\r
6747 programStats.moves_left = 1;
\r
6748 programStats.nodes = 1;
\r
6749 programStats.time = 1;
\r
6750 programStats.got_only_move = 1;
\r
6752 /* Not really, but we also use this member to
\r
6753 mean "line isn't going to change" (Crafty
\r
6754 isn't searching, so stats won't change) */
\r
6755 programStats.line_is_book = 1;
\r
6757 SendProgramStatsToFrontend( cps, &programStats );
\r
6759 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6760 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6761 DisplayMove(currentMove - 1);
\r
6762 DisplayAnalysis();
\r
6765 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6766 &time, &nodes, &plylev, &mvleft,
\r
6767 &mvtot, mvname) >= 5) {
\r
6768 /* The stat01: line is from Crafty (9.29+) in response
\r
6769 to the "." command */
\r
6770 programStats.seen_stat = 1;
\r
6771 cps->maybeThinking = TRUE;
\r
6773 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6776 programStats.depth = plylev;
\r
6777 programStats.time = time;
\r
6778 programStats.nodes = nodes;
\r
6779 programStats.moves_left = mvleft;
\r
6780 programStats.nr_moves = mvtot;
\r
6781 strcpy(programStats.move_name, mvname);
\r
6782 programStats.ok_to_send = 1;
\r
6783 programStats.movelist[0] = '\0';
\r
6785 SendProgramStatsToFrontend( cps, &programStats );
\r
6787 DisplayAnalysis();
\r
6790 } else if (strncmp(message,"++",2) == 0) {
\r
6791 /* Crafty 9.29+ outputs this */
\r
6792 programStats.got_fail = 2;
\r
6795 } else if (strncmp(message,"--",2) == 0) {
\r
6796 /* Crafty 9.29+ outputs this */
\r
6797 programStats.got_fail = 1;
\r
6800 } else if (thinkOutput[0] != NULLCHAR &&
\r
6801 strncmp(message, " ", 4) == 0) {
\r
6802 unsigned message_len;
\r
6805 while (*p && *p == ' ') p++;
\r
6807 message_len = strlen( p );
\r
6809 /* [AS] Avoid buffer overflow */
\r
6810 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6811 strcat(thinkOutput, " ");
\r
6812 strcat(thinkOutput, p);
\r
6815 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6816 strcat(programStats.movelist, " ");
\r
6817 strcat(programStats.movelist, p);
\r
6820 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6821 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6822 DisplayMove(currentMove - 1);
\r
6823 DisplayAnalysis();
\r
6829 buf1[0] = NULLCHAR;
\r
6831 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6832 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6834 ChessProgramStats cpstats;
\r
6836 if (plyext != ' ' && plyext != '\t') {
\r
6840 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6841 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6842 curscore = -curscore;
\r
6845 cpstats.depth = plylev;
\r
6846 cpstats.nodes = nodes;
\r
6847 cpstats.time = time;
\r
6848 cpstats.score = curscore;
\r
6849 cpstats.got_only_move = 0;
\r
6850 cpstats.movelist[0] = '\0';
\r
6852 if (buf1[0] != NULLCHAR) {
\r
6853 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6856 cpstats.ok_to_send = 0;
\r
6857 cpstats.line_is_book = 0;
\r
6858 cpstats.nr_moves = 0;
\r
6859 cpstats.moves_left = 0;
\r
6861 SendProgramStatsToFrontend( cps, &cpstats );
\r
6868 /* Parse a game score from the character string "game", and
\r
6869 record it as the history of the current game. The game
\r
6870 score is NOT assumed to start from the standard position.
\r
6871 The display is not updated in any way.
\r
6874 ParseGameHistory(game)
\r
6877 ChessMove moveType;
\r
6878 int fromX, fromY, toX, toY, boardIndex;
\r
6881 char buf[MSG_SIZ];
\r
6883 if (appData.debugMode)
\r
6884 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6886 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6887 gameInfo.site = StrSave(appData.icsHost);
\r
6888 gameInfo.date = PGNDate();
\r
6889 gameInfo.round = StrSave("-");
\r
6891 /* Parse out names of players */
\r
6892 while (*game == ' ') game++;
\r
6894 while (*game != ' ') *p++ = *game++;
\r
6896 gameInfo.white = StrSave(buf);
\r
6897 while (*game == ' ') game++;
\r
6899 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6901 gameInfo.black = StrSave(buf);
\r
6904 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6907 yyboardindex = boardIndex;
\r
6908 moveType = (ChessMove) yylex();
\r
6909 switch (moveType) {
\r
6910 case IllegalMove: /* maybe suicide chess, etc. */
\r
6911 if (appData.debugMode) {
\r
6912 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6913 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6914 setbuf(debugFP, NULL);
\r
6916 case WhitePromotionChancellor:
\r
6917 case BlackPromotionChancellor:
\r
6918 case WhitePromotionArchbishop:
\r
6919 case BlackPromotionArchbishop:
\r
6920 case WhitePromotionQueen:
\r
6921 case BlackPromotionQueen:
\r
6922 case WhitePromotionRook:
\r
6923 case BlackPromotionRook:
\r
6924 case WhitePromotionBishop:
\r
6925 case BlackPromotionBishop:
\r
6926 case WhitePromotionKnight:
\r
6927 case BlackPromotionKnight:
\r
6928 case WhitePromotionKing:
\r
6929 case BlackPromotionKing:
\r
6931 case WhiteCapturesEnPassant:
\r
6932 case BlackCapturesEnPassant:
\r
6933 case WhiteKingSideCastle:
\r
6934 case WhiteQueenSideCastle:
\r
6935 case BlackKingSideCastle:
\r
6936 case BlackQueenSideCastle:
\r
6937 case WhiteKingSideCastleWild:
\r
6938 case WhiteQueenSideCastleWild:
\r
6939 case BlackKingSideCastleWild:
\r
6940 case BlackQueenSideCastleWild:
\r
6942 case WhiteHSideCastleFR:
\r
6943 case WhiteASideCastleFR:
\r
6944 case BlackHSideCastleFR:
\r
6945 case BlackASideCastleFR:
\r
6947 fromX = currentMoveString[0] - AAA;
\r
6948 fromY = currentMoveString[1] - ONE;
\r
6949 toX = currentMoveString[2] - AAA;
\r
6950 toY = currentMoveString[3] - ONE;
\r
6951 promoChar = currentMoveString[4];
\r
6955 fromX = moveType == WhiteDrop ?
\r
6956 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6957 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6958 fromY = DROP_RANK;
\r
6959 toX = currentMoveString[2] - AAA;
\r
6960 toY = currentMoveString[3] - ONE;
\r
6961 promoChar = NULLCHAR;
\r
6963 case AmbiguousMove:
\r
6965 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6966 if (appData.debugMode) {
\r
6967 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6968 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6969 setbuf(debugFP, NULL);
\r
6971 DisplayError(buf, 0);
\r
6973 case ImpossibleMove:
\r
6975 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6976 if (appData.debugMode) {
\r
6977 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6978 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6979 setbuf(debugFP, NULL);
\r
6981 DisplayError(buf, 0);
\r
6983 case (ChessMove) 0: /* end of file */
\r
6984 if (boardIndex < backwardMostMove) {
\r
6985 /* Oops, gap. How did that happen? */
\r
6986 DisplayError(_("Gap in move list"), 0);
\r
6989 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6990 if (boardIndex > forwardMostMove) {
\r
6991 forwardMostMove = boardIndex;
\r
6995 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6996 strcat(parseList[boardIndex-1], " ");
\r
6997 strcat(parseList[boardIndex-1], yy_text);
\r
7009 case GameUnfinished:
\r
7010 if (gameMode == IcsExamining) {
\r
7011 if (boardIndex < backwardMostMove) {
\r
7012 /* Oops, gap. How did that happen? */
\r
7015 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7018 gameInfo.result = moveType;
\r
7019 p = strchr(yy_text, '{');
\r
7020 if (p == NULL) p = strchr(yy_text, '(');
\r
7023 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7025 q = strchr(p, *p == '{' ? '}' : ')');
\r
7026 if (q != NULL) *q = NULLCHAR;
\r
7029 gameInfo.resultDetails = StrSave(p);
\r
7032 if (boardIndex >= forwardMostMove &&
\r
7033 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7034 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7037 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7038 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7039 parseList[boardIndex]);
\r
7040 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7041 /* currentMoveString is set as a side-effect of yylex */
\r
7042 strcpy(moveList[boardIndex], currentMoveString);
\r
7043 strcat(moveList[boardIndex], "\n");
\r
7045 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7046 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7047 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7049 case MT_STALEMATE:
\r
7053 if(gameInfo.variant != VariantShogi)
\r
7054 strcat(parseList[boardIndex - 1], "+");
\r
7056 case MT_CHECKMATE:
\r
7057 strcat(parseList[boardIndex - 1], "#");
\r
7064 /* Apply a move to the given board */
\r
7066 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7067 int fromX, fromY, toX, toY;
\r
7071 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7073 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7074 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7075 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7078 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7079 oldEP = epStatus[p-1];
\r
7080 epStatus[p] = EP_NONE;
\r
7082 if( board[toY][toX] != EmptySquare )
\r
7083 epStatus[p] = EP_CAPTURE;
\r
7085 if( board[fromY][fromX] == WhitePawn ) {
\r
7086 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7087 epStatus[p] = EP_PAWN_MOVE;
\r
7088 if( toY-fromY==2) {
\r
7089 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7090 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7091 epStatus[p] = toX | berolina;
\r
7092 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7093 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7094 epStatus[p] = toX;
\r
7097 if( board[fromY][fromX] == BlackPawn ) {
\r
7098 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7099 epStatus[p] = EP_PAWN_MOVE;
\r
7100 if( toY-fromY== -2) {
\r
7101 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7102 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7103 epStatus[p] = toX | berolina;
\r
7104 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7105 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7106 epStatus[p] = toX;
\r
7110 for(i=0; i<nrCastlingRights; i++) {
\r
7111 castlingRights[p][i] = castlingRights[p-1][i];
\r
7112 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7113 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7114 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7119 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7120 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7121 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7123 if (fromX == toX && fromY == toY) return;
\r
7125 if (fromY == DROP_RANK) {
\r
7126 /* must be first */
\r
7127 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7129 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7130 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7131 if(gameInfo.variant == VariantKnightmate)
\r
7132 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7134 /* Code added by Tord: */
\r
7135 /* FRC castling assumed when king captures friendly rook. */
\r
7136 if (board[fromY][fromX] == WhiteKing &&
\r
7137 board[toY][toX] == WhiteRook) {
\r
7138 board[fromY][fromX] = EmptySquare;
\r
7139 board[toY][toX] = EmptySquare;
\r
7141 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7143 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7145 } else if (board[fromY][fromX] == BlackKing &&
\r
7146 board[toY][toX] == BlackRook) {
\r
7147 board[fromY][fromX] = EmptySquare;
\r
7148 board[toY][toX] = EmptySquare;
\r
7150 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7152 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7154 /* End of code added by Tord */
\r
7156 } else if (board[fromY][fromX] == king
\r
7157 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7158 && toY == fromY && toX > fromX+1) {
\r
7159 board[fromY][fromX] = EmptySquare;
\r
7160 board[toY][toX] = king;
\r
7161 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7162 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7163 } else if (board[fromY][fromX] == king
\r
7164 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7165 && toY == fromY && toX < fromX-1) {
\r
7166 board[fromY][fromX] = EmptySquare;
\r
7167 board[toY][toX] = king;
\r
7168 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7169 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7170 } else if (board[fromY][fromX] == WhitePawn
\r
7171 && toY == BOARD_HEIGHT-1
\r
7172 && gameInfo.variant != VariantXiangqi
\r
7174 /* white pawn promotion */
\r
7175 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7176 if (board[toY][toX] == EmptySquare) {
\r
7177 board[toY][toX] = WhiteQueen;
\r
7179 if(gameInfo.variant==VariantBughouse ||
\r
7180 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7181 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7182 board[fromY][fromX] = EmptySquare;
\r
7183 } else if ((fromY == BOARD_HEIGHT-4)
\r
7185 && gameInfo.variant != VariantXiangqi
\r
7186 && gameInfo.variant != VariantBerolina
\r
7187 && (board[fromY][fromX] == WhitePawn)
\r
7188 && (board[toY][toX] == EmptySquare)) {
\r
7189 board[fromY][fromX] = EmptySquare;
\r
7190 board[toY][toX] = WhitePawn;
\r
7191 captured = board[toY - 1][toX];
\r
7192 board[toY - 1][toX] = EmptySquare;
\r
7193 } else if ((fromY == BOARD_HEIGHT-4)
\r
7195 && gameInfo.variant == VariantBerolina
\r
7196 && (board[fromY][fromX] == WhitePawn)
\r
7197 && (board[toY][toX] == EmptySquare)) {
\r
7198 board[fromY][fromX] = EmptySquare;
\r
7199 board[toY][toX] = WhitePawn;
\r
7200 if(oldEP & EP_BEROLIN_A) {
\r
7201 captured = board[fromY][fromX-1];
\r
7202 board[fromY][fromX-1] = EmptySquare;
\r
7203 }else{ captured = board[fromY][fromX+1];
\r
7204 board[fromY][fromX+1] = EmptySquare;
\r
7206 } else if (board[fromY][fromX] == king
\r
7207 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7208 && toY == fromY && toX > fromX+1) {
\r
7209 board[fromY][fromX] = EmptySquare;
\r
7210 board[toY][toX] = king;
\r
7211 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7212 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7213 } else if (board[fromY][fromX] == king
\r
7214 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7215 && toY == fromY && toX < fromX-1) {
\r
7216 board[fromY][fromX] = EmptySquare;
\r
7217 board[toY][toX] = king;
\r
7218 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7219 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7220 } else if (fromY == 7 && fromX == 3
\r
7221 && board[fromY][fromX] == BlackKing
\r
7222 && toY == 7 && toX == 5) {
\r
7223 board[fromY][fromX] = EmptySquare;
\r
7224 board[toY][toX] = BlackKing;
\r
7225 board[fromY][7] = EmptySquare;
\r
7226 board[toY][4] = BlackRook;
\r
7227 } else if (fromY == 7 && fromX == 3
\r
7228 && board[fromY][fromX] == BlackKing
\r
7229 && toY == 7 && toX == 1) {
\r
7230 board[fromY][fromX] = EmptySquare;
\r
7231 board[toY][toX] = BlackKing;
\r
7232 board[fromY][0] = EmptySquare;
\r
7233 board[toY][2] = BlackRook;
\r
7234 } else if (board[fromY][fromX] == BlackPawn
\r
7236 && gameInfo.variant != VariantXiangqi
\r
7238 /* black pawn promotion */
\r
7239 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7240 if (board[0][toX] == EmptySquare) {
\r
7241 board[0][toX] = BlackQueen;
\r
7243 if(gameInfo.variant==VariantBughouse ||
\r
7244 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7245 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7246 board[fromY][fromX] = EmptySquare;
\r
7247 } else if ((fromY == 3)
\r
7249 && gameInfo.variant != VariantXiangqi
\r
7250 && gameInfo.variant != VariantBerolina
\r
7251 && (board[fromY][fromX] == BlackPawn)
\r
7252 && (board[toY][toX] == EmptySquare)) {
\r
7253 board[fromY][fromX] = EmptySquare;
\r
7254 board[toY][toX] = BlackPawn;
\r
7255 captured = board[toY + 1][toX];
\r
7256 board[toY + 1][toX] = EmptySquare;
\r
7257 } else if ((fromY == 3)
\r
7259 && gameInfo.variant == VariantBerolina
\r
7260 && (board[fromY][fromX] == BlackPawn)
\r
7261 && (board[toY][toX] == EmptySquare)) {
\r
7262 board[fromY][fromX] = EmptySquare;
\r
7263 board[toY][toX] = BlackPawn;
\r
7264 if(oldEP & EP_BEROLIN_A) {
\r
7265 captured = board[fromY][fromX-1];
\r
7266 board[fromY][fromX-1] = EmptySquare;
\r
7267 }else{ captured = board[fromY][fromX+1];
\r
7268 board[fromY][fromX+1] = EmptySquare;
\r
7271 board[toY][toX] = board[fromY][fromX];
\r
7272 board[fromY][fromX] = EmptySquare;
\r
7275 /* [HGM] now we promote for Shogi, if needed */
\r
7276 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7277 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7280 if (gameInfo.holdingsWidth != 0) {
\r
7282 /* !!A lot more code needs to be written to support holdings */
\r
7283 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7284 /* penultimate board files, so they are automaticlly stored */
\r
7285 /* in the game history. */
\r
7286 if (fromY == DROP_RANK) {
\r
7287 /* Delete from holdings, by decreasing count */
\r
7288 /* and erasing image if necessary */
\r
7290 if(p < (int) BlackPawn) { /* white drop */
\r
7291 p -= (int)WhitePawn;
\r
7292 if(p >= gameInfo.holdingsSize) p = 0;
\r
7293 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7294 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7295 } else { /* black drop */
\r
7296 p -= (int)BlackPawn;
\r
7297 if(p >= gameInfo.holdingsSize) p = 0;
\r
7298 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7299 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7302 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7303 && gameInfo.variant != VariantBughouse ) {
\r
7304 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7305 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7306 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7307 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7309 p = (int) captured;
\r
7310 if (p >= (int) BlackPawn) {
\r
7311 p -= (int)BlackPawn;
\r
7312 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7313 /* in Shogi restore piece to its original first */
\r
7314 captured = (ChessSquare) (DEMOTED captured);
\r
7317 p = PieceToNumber((ChessSquare)p);
\r
7318 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7319 board[p][BOARD_WIDTH-2]++;
\r
7320 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7322 p -= (int)WhitePawn;
\r
7323 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7324 captured = (ChessSquare) (DEMOTED captured);
\r
7327 p = PieceToNumber((ChessSquare)p);
\r
7328 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7329 board[BOARD_HEIGHT-1-p][1]++;
\r
7330 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7334 } else if (gameInfo.variant == VariantAtomic) {
\r
7335 if (captured != EmptySquare) {
\r
7337 for (y = toY-1; y <= toY+1; y++) {
\r
7338 for (x = toX-1; x <= toX+1; x++) {
\r
7339 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7340 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7341 board[y][x] = EmptySquare;
\r
7345 board[toY][toX] = EmptySquare;
\r
7348 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7349 /* [HGM] Shogi promotions */
\r
7350 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7355 /* Updates forwardMostMove */
\r
7357 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7358 int fromX, fromY, toX, toY;
\r
7361 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7363 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7364 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7365 piece = boards[forwardMostMove][fromY][fromX];
\r
7366 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7367 if(gameInfo.variant == VariantKnightmate)
\r
7368 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7369 if(forwardMostMove == 0) {
\r
7370 if(blackPlaysFirst)
\r
7371 fprintf(serverMoves, "%s;", second.tidy);
\r
7372 fprintf(serverMoves, "%s;", first.tidy);
\r
7373 if(!blackPlaysFirst)
\r
7374 fprintf(serverMoves, "%s;", second.tidy);
\r
7375 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7376 lastLoadFlag = loadFlag;
\r
7377 // print base move
\r
7378 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7379 // print castling suffix
\r
7380 if( toY == fromY && piece == king ) {
\r
7382 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7384 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7387 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7388 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7389 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7391 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7392 // promotion suffix
\r
7393 if(promoChar != NULLCHAR)
\r
7394 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7396 fprintf(serverMoves, "/%d/%d",
\r
7397 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7398 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7399 else timeLeft = blackTimeRemaining/1000;
\r
7400 fprintf(serverMoves, "/%d", timeLeft);
\r
7402 fflush(serverMoves);
\r
7405 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7406 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7411 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7412 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7413 if (commentList[forwardMostMove+1] != NULL) {
\r
7414 free(commentList[forwardMostMove+1]);
\r
7415 commentList[forwardMostMove+1] = NULL;
\r
7417 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7418 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7419 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7420 gameInfo.result = GameUnfinished;
\r
7421 if (gameInfo.resultDetails != NULL) {
\r
7422 free(gameInfo.resultDetails);
\r
7423 gameInfo.resultDetails = NULL;
\r
7425 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7426 moveList[forwardMostMove - 1]);
\r
7427 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7428 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7429 fromY, fromX, toY, toX, promoChar,
\r
7430 parseList[forwardMostMove - 1]);
\r
7431 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7432 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7433 castlingRights[forwardMostMove]) ) {
\r
7435 case MT_STALEMATE:
\r
7439 if(gameInfo.variant != VariantShogi)
\r
7440 strcat(parseList[forwardMostMove - 1], "+");
\r
7442 case MT_CHECKMATE:
\r
7443 strcat(parseList[forwardMostMove - 1], "#");
\r
7446 if (appData.debugMode) {
\r
7447 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7452 /* Updates currentMove if not pausing */
\r
7454 ShowMove(fromX, fromY, toX, toY)
\r
7456 int instant = (gameMode == PlayFromGameFile) ?
\r
7457 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7458 if(appData.noGUI) return;
\r
7459 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7461 if (forwardMostMove == currentMove + 1) {
\r
7462 AnimateMove(boards[forwardMostMove - 1],
\r
7463 fromX, fromY, toX, toY);
\r
7465 if (appData.highlightLastMove) {
\r
7466 SetHighlights(fromX, fromY, toX, toY);
\r
7469 currentMove = forwardMostMove;
\r
7472 if (instant) return;
\r
7474 DisplayMove(currentMove - 1);
\r
7475 DrawPosition(FALSE, boards[currentMove]);
\r
7476 DisplayBothClocks();
\r
7477 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7480 void SendEgtPath(ChessProgramState *cps)
\r
7481 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7482 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7484 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7487 char c, *q = name+1, *r, *s;
\r
7489 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7490 while(*p && *p != ',') *q++ = *p++;
\r
7491 *q++ = ':'; *q = 0;
\r
7492 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7493 strcmp(name, ",nalimov:") == 0 ) {
\r
7494 // take nalimov path from the menu-changeable option first, if it is defined
\r
7495 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7496 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7498 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7499 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7500 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7501 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7502 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7503 c = *r; *r = 0; // temporarily null-terminate path info
\r
7504 *--q = 0; // strip of trailig ':' from name
\r
7505 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7507 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7509 if(*p == ',') p++; // read away comma to position for next format name
\r
7514 InitChessProgram(cps, setup)
\r
7515 ChessProgramState *cps;
\r
7516 int setup; /* [HGM] needed to setup FRC opening position */
\r
7518 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7519 if (appData.noChessProgram) return;
\r
7520 hintRequested = FALSE;
\r
7521 bookRequested = FALSE;
\r
7523 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7524 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7525 if(cps->memSize) { /* [HGM] memory */
\r
7526 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7527 SendToProgram(buf, cps);
\r
7529 SendEgtPath(cps); /* [HGM] EGT */
\r
7530 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7531 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7532 SendToProgram(buf, cps);
\r
7535 SendToProgram(cps->initString, cps);
\r
7536 if (gameInfo.variant != VariantNormal &&
\r
7537 gameInfo.variant != VariantLoadable
\r
7538 /* [HGM] also send variant if board size non-standard */
\r
7539 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7541 char *v = VariantName(gameInfo.variant);
\r
7542 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7543 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7544 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7545 DisplayFatalError(buf, 0, 1);
\r
7549 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7550 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7551 if( gameInfo.variant == VariantXiangqi )
\r
7552 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7553 if( gameInfo.variant == VariantShogi )
\r
7554 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7555 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7556 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7557 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7558 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7559 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7560 if( gameInfo.variant == VariantCourier )
\r
7561 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7562 if( gameInfo.variant == VariantSuper )
\r
7563 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7564 if( gameInfo.variant == VariantGreat )
\r
7565 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7568 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7569 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7570 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7571 if(StrStr(cps->variants, b) == NULL) {
\r
7572 // specific sized variant not known, check if general sizing allowed
\r
7573 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7574 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7575 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7576 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7577 DisplayFatalError(buf, 0, 1);
\r
7580 /* [HGM] here we really should compare with the maximum supported board size */
\r
7583 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7584 sprintf(buf, "variant %s\n", b);
\r
7585 SendToProgram(buf, cps);
\r
7587 currentlyInitializedVariant = gameInfo.variant;
\r
7589 /* [HGM] send opening position in FRC to first engine */
\r
7591 SendToProgram("force\n", cps);
\r
7592 SendBoard(cps, 0);
\r
7593 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7594 setboardSpoiledMachineBlack = 1;
\r
7597 if (cps->sendICS) {
\r
7598 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7599 SendToProgram(buf, cps);
\r
7601 cps->maybeThinking = FALSE;
\r
7602 cps->offeredDraw = 0;
\r
7603 if (!appData.icsActive) {
\r
7604 SendTimeControl(cps, movesPerSession, timeControl,
\r
7605 timeIncrement, appData.searchDepth,
\r
7608 if (appData.showThinking
\r
7609 // [HGM] thinking: four options require thinking output to be sent
\r
7610 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7612 SendToProgram("post\n", cps);
\r
7614 SendToProgram("hard\n", cps);
\r
7615 if (!appData.ponderNextMove) {
\r
7616 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7617 it without being sure what state we are in first. "hard"
\r
7618 is not a toggle, so that one is OK.
\r
7620 SendToProgram("easy\n", cps);
\r
7622 if (cps->usePing) {
\r
7623 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7624 SendToProgram(buf, cps);
\r
7626 cps->initDone = TRUE;
\r
7631 StartChessProgram(cps)
\r
7632 ChessProgramState *cps;
\r
7634 char buf[MSG_SIZ];
\r
7637 if (appData.noChessProgram) return;
\r
7638 cps->initDone = FALSE;
\r
7640 if (strcmp(cps->host, "localhost") == 0) {
\r
7641 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7642 } else if (*appData.remoteShell == NULLCHAR) {
\r
7643 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7645 if (*appData.remoteUser == NULLCHAR) {
\r
7646 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7649 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7650 cps->host, appData.remoteUser, cps->program);
\r
7652 err = StartChildProcess(buf, "", &cps->pr);
\r
7656 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7657 DisplayFatalError(buf, err, 1);
\r
7663 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7664 if (cps->protocolVersion > 1) {
\r
7665 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7666 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7667 cps->comboCnt = 0; // and values of combo boxes
\r
7668 SendToProgram(buf, cps);
\r
7670 SendToProgram("xboard\n", cps);
\r
7676 TwoMachinesEventIfReady P((void))
\r
7678 if (first.lastPing != first.lastPong) {
\r
7679 DisplayMessage("", _("Waiting for first chess program"));
\r
7680 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7683 if (second.lastPing != second.lastPong) {
\r
7684 DisplayMessage("", _("Waiting for second chess program"));
\r
7685 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7689 TwoMachinesEvent();
\r
7693 NextMatchGame P((void))
\r
7695 int index; /* [HGM] autoinc: step lod index during match */
\r
7696 Reset(FALSE, TRUE);
\r
7697 if (*appData.loadGameFile != NULLCHAR) {
\r
7698 index = appData.loadGameIndex;
\r
7699 if(index < 0) { // [HGM] autoinc
\r
7700 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7701 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7703 LoadGameFromFile(appData.loadGameFile,
\r
7705 appData.loadGameFile, FALSE);
\r
7706 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7707 index = appData.loadPositionIndex;
\r
7708 if(index < 0) { // [HGM] autoinc
\r
7709 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7710 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7712 LoadPositionFromFile(appData.loadPositionFile,
\r
7714 appData.loadPositionFile);
\r
7716 TwoMachinesEventIfReady();
\r
7719 void UserAdjudicationEvent( int result )
\r
7721 ChessMove gameResult = GameIsDrawn;
\r
7723 if( result > 0 ) {
\r
7724 gameResult = WhiteWins;
\r
7726 else if( result < 0 ) {
\r
7727 gameResult = BlackWins;
\r
7730 if( gameMode == TwoMachinesPlay ) {
\r
7731 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7737 GameEnds(result, resultDetails, whosays)
\r
7739 char *resultDetails;
\r
7742 GameMode nextGameMode;
\r
7744 char buf[MSG_SIZ];
\r
7746 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7749 if (appData.debugMode) {
\r
7750 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7751 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7754 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7755 /* If we are playing on ICS, the server decides when the
\r
7756 game is over, but the engine can offer to draw, claim
\r
7757 a draw, or resign.
\r
7760 if (appData.zippyPlay && first.initDone) {
\r
7761 if (result == GameIsDrawn) {
\r
7762 /* In case draw still needs to be claimed */
\r
7763 SendToICS(ics_prefix);
\r
7764 SendToICS("draw\n");
\r
7765 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7766 SendToICS(ics_prefix);
\r
7767 SendToICS("resign\n");
\r
7771 endingGame = 0; /* [HGM] crash */
\r
7775 /* If we're loading the game from a file, stop */
\r
7776 if (whosays == GE_FILE) {
\r
7777 (void) StopLoadGameTimer();
\r
7778 gameFileFP = NULL;
\r
7781 /* Cancel draw offers */
\r
7782 first.offeredDraw = second.offeredDraw = 0;
\r
7784 /* If this is an ICS game, only ICS can really say it's done;
\r
7785 if not, anyone can. */
\r
7786 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7787 gameMode == IcsPlayingBlack ||
\r
7788 gameMode == IcsObserving ||
\r
7789 gameMode == IcsExamining);
\r
7791 if (!isIcsGame || whosays == GE_ICS) {
\r
7792 /* OK -- not an ICS game, or ICS said it was done */
\r
7794 if (!isIcsGame && !appData.noChessProgram)
\r
7795 SetUserThinkingEnables();
\r
7797 /* [HGM] if a machine claims the game end we verify this claim */
\r
7798 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7799 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7802 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7803 first.twoMachinesColor[0] :
\r
7804 second.twoMachinesColor[0] ;
\r
7805 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7806 (result == WhiteWins && claimer == 'w' ||
\r
7807 result == BlackWins && claimer == 'b' ) ) {
\r
7808 if (appData.debugMode) {
\r
7809 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7810 result, epStatus[forwardMostMove], forwardMostMove);
\r
7812 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7813 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7814 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7815 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7816 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7817 resultDetails = buf;
\r
7820 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7821 && (forwardMostMove <= backwardMostMove ||
\r
7822 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7823 (claimer=='b')==(forwardMostMove&1))
\r
7825 /* [HGM] verify: draws that were not flagged are false claims */
\r
7826 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7827 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7828 resultDetails = buf;
\r
7830 /* (Claiming a loss is accepted no questions asked!) */
\r
7832 /* [HGM] bare: don't allow bare King to win */
\r
7833 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7834 && result != GameIsDrawn)
\r
7835 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7836 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7837 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7838 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7840 if (appData.debugMode) {
\r
7841 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7842 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7845 result = GameIsDrawn;
\r
7846 sprintf(buf, "%s but bare king", resultDetails);
\r
7847 resultDetails = buf;
\r
7853 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7854 if(result==WhiteWins) c = '+';
\r
7855 if(result==BlackWins) c = '-';
\r
7856 if(resultDetails != NULL)
\r
7857 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7859 if (resultDetails != NULL) {
\r
7860 gameInfo.result = result;
\r
7861 gameInfo.resultDetails = StrSave(resultDetails);
\r
7863 /* display last move only if game was not loaded from file */
\r
7864 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7865 DisplayMove(currentMove - 1);
\r
7867 if (forwardMostMove != 0) {
\r
7868 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7869 if (*appData.saveGameFile != NULLCHAR) {
\r
7870 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7871 } else if (appData.autoSaveGames) {
\r
7874 if (*appData.savePositionFile != NULLCHAR) {
\r
7875 SavePositionToFile(appData.savePositionFile);
\r
7880 /* Tell program how game ended in case it is learning */
\r
7881 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7882 /* engine died and we got here through time loss. In that */
\r
7883 /* case we will get a fatal error writing the pipe, which */
\r
7884 /* would otherwise lose us the PGN. */
\r
7885 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7886 /* output during GameEnds should never be fatal anymore */
\r
7887 if (gameMode == MachinePlaysWhite ||
\r
7888 gameMode == MachinePlaysBlack ||
\r
7889 gameMode == TwoMachinesPlay ||
\r
7890 gameMode == IcsPlayingWhite ||
\r
7891 gameMode == IcsPlayingBlack ||
\r
7892 gameMode == BeginningOfGame) {
\r
7893 char buf[MSG_SIZ];
\r
7894 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7896 if (first.pr != NoProc) {
\r
7897 SendToProgram(buf, &first);
\r
7899 if (second.pr != NoProc &&
\r
7900 gameMode == TwoMachinesPlay) {
\r
7901 SendToProgram(buf, &second);
\r
7906 if (appData.icsActive) {
\r
7907 if (appData.quietPlay &&
\r
7908 (gameMode == IcsPlayingWhite ||
\r
7909 gameMode == IcsPlayingBlack)) {
\r
7910 SendToICS(ics_prefix);
\r
7911 SendToICS("set shout 1\n");
\r
7913 nextGameMode = IcsIdle;
\r
7914 ics_user_moved = FALSE;
\r
7915 /* clean up premove. It's ugly when the game has ended and the
\r
7916 * premove highlights are still on the board.
\r
7919 gotPremove = FALSE;
\r
7920 ClearPremoveHighlights();
\r
7921 DrawPosition(FALSE, boards[currentMove]);
\r
7923 if (whosays == GE_ICS) {
\r
7926 if (gameMode == IcsPlayingWhite)
\r
7927 PlayIcsWinSound();
\r
7928 else if(gameMode == IcsPlayingBlack)
\r
7929 PlayIcsLossSound();
\r
7932 if (gameMode == IcsPlayingBlack)
\r
7933 PlayIcsWinSound();
\r
7934 else if(gameMode == IcsPlayingWhite)
\r
7935 PlayIcsLossSound();
\r
7938 PlayIcsDrawSound();
\r
7941 PlayIcsUnfinishedSound();
\r
7944 } else if (gameMode == EditGame ||
\r
7945 gameMode == PlayFromGameFile ||
\r
7946 gameMode == AnalyzeMode ||
\r
7947 gameMode == AnalyzeFile) {
\r
7948 nextGameMode = gameMode;
\r
7950 nextGameMode = EndOfGame;
\r
7955 nextGameMode = gameMode;
\r
7958 if (appData.noChessProgram) {
\r
7959 gameMode = nextGameMode;
\r
7961 endingGame = 0; /* [HGM] crash */
\r
7965 if (first.reuse) {
\r
7966 /* Put first chess program into idle state */
\r
7967 if (first.pr != NoProc &&
\r
7968 (gameMode == MachinePlaysWhite ||
\r
7969 gameMode == MachinePlaysBlack ||
\r
7970 gameMode == TwoMachinesPlay ||
\r
7971 gameMode == IcsPlayingWhite ||
\r
7972 gameMode == IcsPlayingBlack ||
\r
7973 gameMode == BeginningOfGame)) {
\r
7974 SendToProgram("force\n", &first);
\r
7975 if (first.usePing) {
\r
7976 char buf[MSG_SIZ];
\r
7977 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7978 SendToProgram(buf, &first);
\r
7981 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7982 /* Kill off first chess program */
\r
7983 if (first.isr != NULL)
\r
7984 RemoveInputSource(first.isr);
\r
7987 if (first.pr != NoProc) {
\r
7988 ExitAnalyzeMode();
\r
7989 DoSleep( appData.delayBeforeQuit );
\r
7990 SendToProgram("quit\n", &first);
\r
7991 DoSleep( appData.delayAfterQuit );
\r
7992 DestroyChildProcess(first.pr, first.useSigterm);
\r
7994 first.pr = NoProc;
\r
7996 if (second.reuse) {
\r
7997 /* Put second chess program into idle state */
\r
7998 if (second.pr != NoProc &&
\r
7999 gameMode == TwoMachinesPlay) {
\r
8000 SendToProgram("force\n", &second);
\r
8001 if (second.usePing) {
\r
8002 char buf[MSG_SIZ];
\r
8003 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
8004 SendToProgram(buf, &second);
\r
8007 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8008 /* Kill off second chess program */
\r
8009 if (second.isr != NULL)
\r
8010 RemoveInputSource(second.isr);
\r
8011 second.isr = NULL;
\r
8013 if (second.pr != NoProc) {
\r
8014 DoSleep( appData.delayBeforeQuit );
\r
8015 SendToProgram("quit\n", &second);
\r
8016 DoSleep( appData.delayAfterQuit );
\r
8017 DestroyChildProcess(second.pr, second.useSigterm);
\r
8019 second.pr = NoProc;
\r
8022 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8025 if (first.twoMachinesColor[0] == 'w') {
\r
8026 first.matchWins++;
\r
8028 second.matchWins++;
\r
8032 if (first.twoMachinesColor[0] == 'b') {
\r
8033 first.matchWins++;
\r
8035 second.matchWins++;
\r
8041 if (matchGame < appData.matchGames) {
\r
8043 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8044 tmp = first.twoMachinesColor;
\r
8045 first.twoMachinesColor = second.twoMachinesColor;
\r
8046 second.twoMachinesColor = tmp;
\r
8048 gameMode = nextGameMode;
\r
8050 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8051 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8052 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8053 endingGame = 0; /* [HGM] crash */
\r
8056 char buf[MSG_SIZ];
\r
8057 gameMode = nextGameMode;
\r
8058 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8059 first.tidy, second.tidy,
\r
8060 first.matchWins, second.matchWins,
\r
8061 appData.matchGames - (first.matchWins + second.matchWins));
\r
8062 DisplayFatalError(buf, 0, 0);
\r
8065 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8066 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8067 ExitAnalyzeMode();
\r
8068 gameMode = nextGameMode;
\r
8070 endingGame = 0; /* [HGM] crash */
\r
8073 /* Assumes program was just initialized (initString sent).
\r
8074 Leaves program in force mode. */
\r
8076 FeedMovesToProgram(cps, upto)
\r
8077 ChessProgramState *cps;
\r
8082 if (appData.debugMode)
\r
8083 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8084 startedFromSetupPosition ? "position and " : "",
\r
8085 backwardMostMove, upto, cps->which);
\r
8086 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8087 // [HGM] variantswitch: make engine aware of new variant
\r
8088 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8089 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8090 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8091 SendToProgram(buf, cps);
\r
8092 currentlyInitializedVariant = gameInfo.variant;
\r
8094 SendToProgram("force\n", cps);
\r
8095 if (startedFromSetupPosition) {
\r
8096 SendBoard(cps, backwardMostMove);
\r
8097 if (appData.debugMode) {
\r
8098 fprintf(debugFP, "feedMoves\n");
\r
8101 for (i = backwardMostMove; i < upto; i++) {
\r
8102 SendMoveToProgram(i, cps);
\r
8108 ResurrectChessProgram()
\r
8110 /* The chess program may have exited.
\r
8111 If so, restart it and feed it all the moves made so far. */
\r
8113 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8115 StartChessProgram(&first);
\r
8116 InitChessProgram(&first, FALSE);
\r
8117 FeedMovesToProgram(&first, currentMove);
\r
8119 if (!first.sendTime) {
\r
8120 /* can't tell gnuchess what its clock should read,
\r
8121 so we bow to its notion. */
\r
8123 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8124 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8127 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8128 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8129 SendToProgram("analyze\n", &first);
\r
8130 first.analyzing = TRUE;
\r
8135 * Button procedures
\r
8138 Reset(redraw, init)
\r
8143 if (appData.debugMode) {
\r
8144 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8145 redraw, init, gameMode);
\r
8147 pausing = pauseExamInvalid = FALSE;
\r
8148 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8150 whiteFlag = blackFlag = FALSE;
\r
8151 userOfferedDraw = FALSE;
\r
8152 hintRequested = bookRequested = FALSE;
\r
8153 first.maybeThinking = FALSE;
\r
8154 second.maybeThinking = FALSE;
\r
8155 first.bookSuspend = FALSE; // [HGM] book
\r
8156 second.bookSuspend = FALSE;
\r
8157 thinkOutput[0] = NULLCHAR;
\r
8158 lastHint[0] = NULLCHAR;
\r
8159 ClearGameInfo(&gameInfo);
\r
8160 gameInfo.variant = StringToVariant(appData.variant);
\r
8161 ics_user_moved = ics_clock_paused = FALSE;
\r
8162 ics_getting_history = H_FALSE;
\r
8164 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8165 ClearProgramStats();
\r
8166 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8169 ClearHighlights();
\r
8170 flipView = appData.flipView;
\r
8171 ClearPremoveHighlights();
\r
8172 gotPremove = FALSE;
\r
8173 alarmSounded = FALSE;
\r
8175 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8176 if(appData.serverMovesName != NULL) {
\r
8177 /* [HGM] prepare to make moves file for broadcasting */
\r
8178 clock_t t = clock();
\r
8179 if(serverMoves != NULL) fclose(serverMoves);
\r
8180 serverMoves = fopen(appData.serverMovesName, "r");
\r
8181 if(serverMoves != NULL) {
\r
8182 fclose(serverMoves);
\r
8183 /* delay 15 sec before overwriting, so all clients can see end */
\r
8184 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8186 serverMoves = fopen(appData.serverMovesName, "w");
\r
8189 ExitAnalyzeMode();
\r
8190 gameMode = BeginningOfGame;
\r
8192 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8193 InitPosition(redraw);
\r
8194 for (i = 0; i < MAX_MOVES; i++) {
\r
8195 if (commentList[i] != NULL) {
\r
8196 free(commentList[i]);
\r
8197 commentList[i] = NULL;
\r
8201 timeRemaining[0][0] = whiteTimeRemaining;
\r
8202 timeRemaining[1][0] = blackTimeRemaining;
\r
8203 if (first.pr == NULL) {
\r
8204 StartChessProgram(&first);
\r
8207 InitChessProgram(&first, startedFromSetupPosition);
\r
8210 DisplayMessage("", "");
\r
8211 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8215 AutoPlayGameLoop()
\r
8218 if (!AutoPlayOneMove())
\r
8220 if (matchMode || appData.timeDelay == 0)
\r
8222 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8224 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8233 int fromX, fromY, toX, toY;
\r
8235 if (appData.debugMode) {
\r
8236 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8239 if (gameMode != PlayFromGameFile)
\r
8242 if (currentMove >= forwardMostMove) {
\r
8243 gameMode = EditGame;
\r
8246 /* [AS] Clear current move marker at the end of a game */
\r
8247 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8252 toX = moveList[currentMove][2] - AAA;
\r
8253 toY = moveList[currentMove][3] - ONE;
\r
8255 if (moveList[currentMove][1] == '@') {
\r
8256 if (appData.highlightLastMove) {
\r
8257 SetHighlights(-1, -1, toX, toY);
\r
8260 fromX = moveList[currentMove][0] - AAA;
\r
8261 fromY = moveList[currentMove][1] - ONE;
\r
8263 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8265 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8267 if (appData.highlightLastMove) {
\r
8268 SetHighlights(fromX, fromY, toX, toY);
\r
8271 DisplayMove(currentMove);
\r
8272 SendMoveToProgram(currentMove++, &first);
\r
8273 DisplayBothClocks();
\r
8274 DrawPosition(FALSE, boards[currentMove]);
\r
8275 // [HGM] PV info: always display, routine tests if empty
\r
8276 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8282 LoadGameOneMove(readAhead)
\r
8283 ChessMove readAhead;
\r
8285 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8286 char promoChar = NULLCHAR;
\r
8287 ChessMove moveType;
\r
8288 char move[MSG_SIZ];
\r
8291 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8292 gameMode != AnalyzeMode && gameMode != Training) {
\r
8293 gameFileFP = NULL;
\r
8297 yyboardindex = forwardMostMove;
\r
8298 if (readAhead != (ChessMove)0) {
\r
8299 moveType = readAhead;
\r
8301 if (gameFileFP == NULL)
\r
8303 moveType = (ChessMove) yylex();
\r
8307 switch (moveType) {
\r
8309 if (appData.debugMode)
\r
8310 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8312 if (*p == '{' || *p == '[' || *p == '(') {
\r
8313 p[strlen(p) - 1] = NULLCHAR;
\r
8317 /* append the comment but don't display it */
\r
8318 while (*p == '\n') p++;
\r
8319 AppendComment(currentMove, p);
\r
8322 case WhiteCapturesEnPassant:
\r
8323 case BlackCapturesEnPassant:
\r
8324 case WhitePromotionChancellor:
\r
8325 case BlackPromotionChancellor:
\r
8326 case WhitePromotionArchbishop:
\r
8327 case BlackPromotionArchbishop:
\r
8328 case WhitePromotionCentaur:
\r
8329 case BlackPromotionCentaur:
\r
8330 case WhitePromotionQueen:
\r
8331 case BlackPromotionQueen:
\r
8332 case WhitePromotionRook:
\r
8333 case BlackPromotionRook:
\r
8334 case WhitePromotionBishop:
\r
8335 case BlackPromotionBishop:
\r
8336 case WhitePromotionKnight:
\r
8337 case BlackPromotionKnight:
\r
8338 case WhitePromotionKing:
\r
8339 case BlackPromotionKing:
\r
8341 case WhiteKingSideCastle:
\r
8342 case WhiteQueenSideCastle:
\r
8343 case BlackKingSideCastle:
\r
8344 case BlackQueenSideCastle:
\r
8345 case WhiteKingSideCastleWild:
\r
8346 case WhiteQueenSideCastleWild:
\r
8347 case BlackKingSideCastleWild:
\r
8348 case BlackQueenSideCastleWild:
\r
8350 case WhiteHSideCastleFR:
\r
8351 case WhiteASideCastleFR:
\r
8352 case BlackHSideCastleFR:
\r
8353 case BlackASideCastleFR:
\r
8355 if (appData.debugMode)
\r
8356 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8357 fromX = currentMoveString[0] - AAA;
\r
8358 fromY = currentMoveString[1] - ONE;
\r
8359 toX = currentMoveString[2] - AAA;
\r
8360 toY = currentMoveString[3] - ONE;
\r
8361 promoChar = currentMoveString[4];
\r
8366 if (appData.debugMode)
\r
8367 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8368 fromX = moveType == WhiteDrop ?
\r
8369 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8370 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8371 fromY = DROP_RANK;
\r
8372 toX = currentMoveString[2] - AAA;
\r
8373 toY = currentMoveString[3] - ONE;
\r
8379 case GameUnfinished:
\r
8380 if (appData.debugMode)
\r
8381 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8382 p = strchr(yy_text, '{');
\r
8383 if (p == NULL) p = strchr(yy_text, '(');
\r
8386 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8388 q = strchr(p, *p == '{' ? '}' : ')');
\r
8389 if (q != NULL) *q = NULLCHAR;
\r
8392 GameEnds(moveType, p, GE_FILE);
\r
8394 if (cmailMsgLoaded) {
\r
8395 ClearHighlights();
\r
8396 flipView = WhiteOnMove(currentMove);
\r
8397 if (moveType == GameUnfinished) flipView = !flipView;
\r
8398 if (appData.debugMode)
\r
8399 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8403 case (ChessMove) 0: /* end of file */
\r
8404 if (appData.debugMode)
\r
8405 fprintf(debugFP, "Parser hit end of file\n");
\r
8406 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8407 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8411 case MT_CHECKMATE:
\r
8412 if (WhiteOnMove(currentMove)) {
\r
8413 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8415 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8418 case MT_STALEMATE:
\r
8419 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8425 case MoveNumberOne:
\r
8426 if (lastLoadGameStart == GNUChessGame) {
\r
8427 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8428 if (appData.debugMode)
\r
8429 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8430 yy_text, (int) moveType);
\r
8431 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8433 /* else fall thru */
\r
8436 case GNUChessGame:
\r
8438 /* Reached start of next game in file */
\r
8439 if (appData.debugMode)
\r
8440 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8441 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8442 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8446 case MT_CHECKMATE:
\r
8447 if (WhiteOnMove(currentMove)) {
\r
8448 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8450 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8453 case MT_STALEMATE:
\r
8454 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8460 case PositionDiagram: /* should not happen; ignore */
\r
8461 case ElapsedTime: /* ignore */
\r
8462 case NAG: /* ignore */
\r
8463 if (appData.debugMode)
\r
8464 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8465 yy_text, (int) moveType);
\r
8466 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8469 if (appData.testLegality) {
\r
8470 if (appData.debugMode)
\r
8471 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8472 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8473 (forwardMostMove / 2) + 1,
\r
8474 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8475 DisplayError(move, 0);
\r
8478 if (appData.debugMode)
\r
8479 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8480 yy_text, currentMoveString);
\r
8481 fromX = currentMoveString[0] - AAA;
\r
8482 fromY = currentMoveString[1] - ONE;
\r
8483 toX = currentMoveString[2] - AAA;
\r
8484 toY = currentMoveString[3] - ONE;
\r
8485 promoChar = currentMoveString[4];
\r
8489 case AmbiguousMove:
\r
8490 if (appData.debugMode)
\r
8491 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8492 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8493 (forwardMostMove / 2) + 1,
\r
8494 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8495 DisplayError(move, 0);
\r
8500 case ImpossibleMove:
\r
8501 if (appData.debugMode)
\r
8502 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8503 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8504 (forwardMostMove / 2) + 1,
\r
8505 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8506 DisplayError(move, 0);
\r
8512 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8513 DrawPosition(FALSE, boards[currentMove]);
\r
8514 DisplayBothClocks();
\r
8515 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8516 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8518 (void) StopLoadGameTimer();
\r
8519 gameFileFP = NULL;
\r
8520 cmailOldMove = forwardMostMove;
\r
8523 /* currentMoveString is set as a side-effect of yylex */
\r
8524 strcat(currentMoveString, "\n");
\r
8525 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8527 thinkOutput[0] = NULLCHAR;
\r
8528 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8529 currentMove = forwardMostMove;
\r
8534 /* Load the nth game from the given file */
\r
8536 LoadGameFromFile(filename, n, title, useList)
\r
8540 /*Boolean*/ int useList;
\r
8543 char buf[MSG_SIZ];
\r
8545 if (strcmp(filename, "-") == 0) {
\r
8549 f = fopen(filename, "rb");
\r
8551 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8552 DisplayError(buf, errno);
\r
8556 if (fseek(f, 0, 0) == -1) {
\r
8557 /* f is not seekable; probably a pipe */
\r
8560 if (useList && n == 0) {
\r
8561 int error = GameListBuild(f);
\r
8563 DisplayError(_("Cannot build game list"), error);
\r
8564 } else if (!ListEmpty(&gameList) &&
\r
8565 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8566 GameListPopUp(f, title);
\r
8569 GameListDestroy();
\r
8572 if (n == 0) n = 1;
\r
8573 return LoadGame(f, n, title, FALSE);
\r
8578 MakeRegisteredMove()
\r
8580 int fromX, fromY, toX, toY;
\r
8582 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8583 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8586 if (appData.debugMode)
\r
8587 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8588 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8590 thinkOutput[0] = NULLCHAR;
\r
8591 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8592 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8593 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8594 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8595 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8596 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8597 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8598 ShowMove(fromX, fromY, toX, toY);
\r
8600 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8601 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8606 case MT_CHECKMATE:
\r
8607 if (WhiteOnMove(currentMove)) {
\r
8608 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8610 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8614 case MT_STALEMATE:
\r
8615 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8621 case CMAIL_RESIGN:
\r
8622 if (WhiteOnMove(currentMove)) {
\r
8623 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8625 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8629 case CMAIL_ACCEPT:
\r
8630 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8641 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8643 CmailLoadGame(f, gameNumber, title, useList)
\r
8651 if (gameNumber > nCmailGames) {
\r
8652 DisplayError(_("No more games in this message"), 0);
\r
8655 if (f == lastLoadGameFP) {
\r
8656 int offset = gameNumber - lastLoadGameNumber;
\r
8657 if (offset == 0) {
\r
8658 cmailMsg[0] = NULLCHAR;
\r
8659 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8660 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8661 nCmailMovesRegistered--;
\r
8663 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8664 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8665 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8668 if (! RegisterMove()) return FALSE;
\r
8672 retVal = LoadGame(f, gameNumber, title, useList);
\r
8674 /* Make move registered during previous look at this game, if any */
\r
8675 MakeRegisteredMove();
\r
8677 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8678 commentList[currentMove]
\r
8679 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8680 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8686 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8688 ReloadGame(offset)
\r
8691 int gameNumber = lastLoadGameNumber + offset;
\r
8692 if (lastLoadGameFP == NULL) {
\r
8693 DisplayError(_("No game has been loaded yet"), 0);
\r
8696 if (gameNumber <= 0) {
\r
8697 DisplayError(_("Can't back up any further"), 0);
\r
8700 if (cmailMsgLoaded) {
\r
8701 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8702 lastLoadGameTitle, lastLoadGameUseList);
\r
8704 return LoadGame(lastLoadGameFP, gameNumber,
\r
8705 lastLoadGameTitle, lastLoadGameUseList);
\r
8711 /* Load the nth game from open file f */
\r
8713 LoadGame(f, gameNumber, title, useList)
\r
8720 char buf[MSG_SIZ];
\r
8721 int gn = gameNumber;
\r
8722 ListGame *lg = NULL;
\r
8723 int numPGNTags = 0;
\r
8725 GameMode oldGameMode;
\r
8726 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8728 if (appData.debugMode)
\r
8729 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8731 if (gameMode == Training )
\r
8732 SetTrainingModeOff();
\r
8734 oldGameMode = gameMode;
\r
8735 if (gameMode != BeginningOfGame) {
\r
8736 Reset(FALSE, TRUE);
\r
8740 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8741 fclose(lastLoadGameFP);
\r
8745 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8748 fseek(f, lg->offset, 0);
\r
8749 GameListHighlight(gameNumber);
\r
8753 DisplayError(_("Game number out of range"), 0);
\r
8757 GameListDestroy();
\r
8758 if (fseek(f, 0, 0) == -1) {
\r
8759 if (f == lastLoadGameFP ?
\r
8760 gameNumber == lastLoadGameNumber + 1 :
\r
8761 gameNumber == 1) {
\r
8764 DisplayError(_("Can't seek on game file"), 0);
\r
8769 lastLoadGameFP = f;
\r
8770 lastLoadGameNumber = gameNumber;
\r
8771 strcpy(lastLoadGameTitle, title);
\r
8772 lastLoadGameUseList = useList;
\r
8776 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8777 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8778 lg->gameInfo.black);
\r
8779 DisplayTitle(buf);
\r
8780 } else if (*title != NULLCHAR) {
\r
8781 if (gameNumber > 1) {
\r
8782 sprintf(buf, "%s %d", title, gameNumber);
\r
8783 DisplayTitle(buf);
\r
8785 DisplayTitle(title);
\r
8789 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8790 gameMode = PlayFromGameFile;
\r
8794 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8795 CopyBoard(boards[0], initialPosition);
\r
8799 * Skip the first gn-1 games in the file.
\r
8800 * Also skip over anything that precedes an identifiable
\r
8801 * start of game marker, to avoid being confused by
\r
8802 * garbage at the start of the file. Currently
\r
8803 * recognized start of game markers are the move number "1",
\r
8804 * the pattern "gnuchess .* game", the pattern
\r
8805 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8806 * A game that starts with one of the latter two patterns
\r
8807 * will also have a move number 1, possibly
\r
8808 * following a position diagram.
\r
8809 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8810 * start with an unnumbered move. Does that break anything?
\r
8812 cm = lastLoadGameStart = (ChessMove) 0;
\r
8814 yyboardindex = forwardMostMove;
\r
8815 cm = (ChessMove) yylex();
\r
8817 case (ChessMove) 0:
\r
8818 if (cmailMsgLoaded) {
\r
8819 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8821 Reset(TRUE, TRUE);
\r
8822 DisplayError(_("Game not found in file"), 0);
\r
8826 case GNUChessGame:
\r
8829 lastLoadGameStart = cm;
\r
8832 case MoveNumberOne:
\r
8833 switch (lastLoadGameStart) {
\r
8834 case GNUChessGame:
\r
8838 case MoveNumberOne:
\r
8839 case (ChessMove) 0:
\r
8840 gn--; /* count this game */
\r
8841 lastLoadGameStart = cm;
\r
8850 switch (lastLoadGameStart) {
\r
8851 case GNUChessGame:
\r
8853 case MoveNumberOne:
\r
8854 case (ChessMove) 0:
\r
8855 gn--; /* count this game */
\r
8856 lastLoadGameStart = cm;
\r
8859 lastLoadGameStart = cm; /* game counted already */
\r
8867 yyboardindex = forwardMostMove;
\r
8868 cm = (ChessMove) yylex();
\r
8869 } while (cm == PGNTag || cm == Comment);
\r
8876 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8877 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8878 != CMAIL_OLD_RESULT) {
\r
8879 nCmailResults ++ ;
\r
8880 cmailResult[ CMAIL_MAX_GAMES
\r
8881 - gn - 1] = CMAIL_OLD_RESULT;
\r
8887 /* Only a NormalMove can be at the start of a game
\r
8888 * without a position diagram. */
\r
8889 if (lastLoadGameStart == (ChessMove) 0) {
\r
8891 lastLoadGameStart = MoveNumberOne;
\r
8900 if (appData.debugMode)
\r
8901 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8903 if (cm == XBoardGame) {
\r
8904 /* Skip any header junk before position diagram and/or move 1 */
\r
8906 yyboardindex = forwardMostMove;
\r
8907 cm = (ChessMove) yylex();
\r
8909 if (cm == (ChessMove) 0 ||
\r
8910 cm == GNUChessGame || cm == XBoardGame) {
\r
8911 /* Empty game; pretend end-of-file and handle later */
\r
8912 cm = (ChessMove) 0;
\r
8916 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8917 cm == PGNTag || cm == Comment)
\r
8920 } else if (cm == GNUChessGame) {
\r
8921 if (gameInfo.event != NULL) {
\r
8922 free(gameInfo.event);
\r
8924 gameInfo.event = StrSave(yy_text);
\r
8927 startedFromSetupPosition = FALSE;
\r
8928 while (cm == PGNTag) {
\r
8929 if (appData.debugMode)
\r
8930 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8931 err = ParsePGNTag(yy_text, &gameInfo);
\r
8932 if (!err) numPGNTags++;
\r
8934 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8935 if(gameInfo.variant != oldVariant) {
\r
8936 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8937 InitPosition(TRUE);
\r
8938 oldVariant = gameInfo.variant;
\r
8939 if (appData.debugMode)
\r
8940 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8944 if (gameInfo.fen != NULL) {
\r
8945 Board initial_position;
\r
8946 startedFromSetupPosition = TRUE;
\r
8947 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8948 Reset(TRUE, TRUE);
\r
8949 DisplayError(_("Bad FEN position in file"), 0);
\r
8952 CopyBoard(boards[0], initial_position);
\r
8953 if (blackPlaysFirst) {
\r
8954 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8955 CopyBoard(boards[1], initial_position);
\r
8956 strcpy(moveList[0], "");
\r
8957 strcpy(parseList[0], "");
\r
8958 timeRemaining[0][1] = whiteTimeRemaining;
\r
8959 timeRemaining[1][1] = blackTimeRemaining;
\r
8960 if (commentList[0] != NULL) {
\r
8961 commentList[1] = commentList[0];
\r
8962 commentList[0] = NULL;
\r
8965 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8967 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8969 initialRulePlies = FENrulePlies;
\r
8970 epStatus[forwardMostMove] = FENepStatus;
\r
8971 for( i=0; i< nrCastlingRights; i++ )
\r
8972 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8974 yyboardindex = forwardMostMove;
\r
8975 free(gameInfo.fen);
\r
8976 gameInfo.fen = NULL;
\r
8979 yyboardindex = forwardMostMove;
\r
8980 cm = (ChessMove) yylex();
\r
8982 /* Handle comments interspersed among the tags */
\r
8983 while (cm == Comment) {
\r
8985 if (appData.debugMode)
\r
8986 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8988 if (*p == '{' || *p == '[' || *p == '(') {
\r
8989 p[strlen(p) - 1] = NULLCHAR;
\r
8992 while (*p == '\n') p++;
\r
8993 AppendComment(currentMove, p);
\r
8994 yyboardindex = forwardMostMove;
\r
8995 cm = (ChessMove) yylex();
\r
8999 /* don't rely on existence of Event tag since if game was
\r
9000 * pasted from clipboard the Event tag may not exist
\r
9002 if (numPGNTags > 0){
\r
9004 if (gameInfo.variant == VariantNormal) {
\r
9005 gameInfo.variant = StringToVariant(gameInfo.event);
\r
9008 if( appData.autoDisplayTags ) {
\r
9009 tags = PGNTags(&gameInfo);
\r
9010 TagsPopUp(tags, CmailMsg());
\r
9015 /* Make something up, but don't display it now */
\r
9020 if (cm == PositionDiagram) {
\r
9023 Board initial_position;
\r
9025 if (appData.debugMode)
\r
9026 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9028 if (!startedFromSetupPosition) {
\r
9030 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9031 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9041 initial_position[i][j++] = CharToPiece(*p);
\r
9044 while (*p == ' ' || *p == '\t' ||
\r
9045 *p == '\n' || *p == '\r') p++;
\r
9047 if (strncmp(p, "black", strlen("black"))==0)
\r
9048 blackPlaysFirst = TRUE;
\r
9050 blackPlaysFirst = FALSE;
\r
9051 startedFromSetupPosition = TRUE;
\r
9053 CopyBoard(boards[0], initial_position);
\r
9054 if (blackPlaysFirst) {
\r
9055 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9056 CopyBoard(boards[1], initial_position);
\r
9057 strcpy(moveList[0], "");
\r
9058 strcpy(parseList[0], "");
\r
9059 timeRemaining[0][1] = whiteTimeRemaining;
\r
9060 timeRemaining[1][1] = blackTimeRemaining;
\r
9061 if (commentList[0] != NULL) {
\r
9062 commentList[1] = commentList[0];
\r
9063 commentList[0] = NULL;
\r
9066 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9069 yyboardindex = forwardMostMove;
\r
9070 cm = (ChessMove) yylex();
\r
9073 if (first.pr == NoProc) {
\r
9074 StartChessProgram(&first);
\r
9076 InitChessProgram(&first, FALSE);
\r
9077 SendToProgram("force\n", &first);
\r
9078 if (startedFromSetupPosition) {
\r
9079 SendBoard(&first, forwardMostMove);
\r
9080 if (appData.debugMode) {
\r
9081 fprintf(debugFP, "Load Game\n");
\r
9083 DisplayBothClocks();
\r
9086 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9087 loadFlag = appData.suppressLoadMoves;
\r
9089 while (cm == Comment) {
\r
9091 if (appData.debugMode)
\r
9092 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9094 if (*p == '{' || *p == '[' || *p == '(') {
\r
9095 p[strlen(p) - 1] = NULLCHAR;
\r
9098 while (*p == '\n') p++;
\r
9099 AppendComment(currentMove, p);
\r
9100 yyboardindex = forwardMostMove;
\r
9101 cm = (ChessMove) yylex();
\r
9104 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9105 cm == WhiteWins || cm == BlackWins ||
\r
9106 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9107 DisplayMessage("", _("No moves in game"));
\r
9108 if (cmailMsgLoaded) {
\r
9109 if (appData.debugMode)
\r
9110 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9111 ClearHighlights();
\r
9114 DrawPosition(FALSE, boards[currentMove]);
\r
9115 DisplayBothClocks();
\r
9116 gameMode = EditGame;
\r
9118 gameFileFP = NULL;
\r
9123 // [HGM] PV info: routine tests if comment empty
\r
9124 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9125 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9127 if (!matchMode && appData.timeDelay != 0)
\r
9128 DrawPosition(FALSE, boards[currentMove]);
\r
9130 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9131 programStats.ok_to_send = 1;
\r
9134 /* if the first token after the PGN tags is a move
\r
9135 * and not move number 1, retrieve it from the parser
\r
9137 if (cm != MoveNumberOne)
\r
9138 LoadGameOneMove(cm);
\r
9140 /* load the remaining moves from the file */
\r
9141 while (LoadGameOneMove((ChessMove)0)) {
\r
9142 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9143 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9146 /* rewind to the start of the game */
\r
9147 currentMove = backwardMostMove;
\r
9149 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9151 if (oldGameMode == AnalyzeFile ||
\r
9152 oldGameMode == AnalyzeMode) {
\r
9153 AnalyzeFileEvent();
\r
9156 if (matchMode || appData.timeDelay == 0) {
\r
9158 gameMode = EditGame;
\r
9160 } else if (appData.timeDelay > 0) {
\r
9161 AutoPlayGameLoop();
\r
9164 if (appData.debugMode)
\r
9165 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9167 loadFlag = 0; /* [HGM] true game starts */
\r
9171 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9173 ReloadPosition(offset)
\r
9176 int positionNumber = lastLoadPositionNumber + offset;
\r
9177 if (lastLoadPositionFP == NULL) {
\r
9178 DisplayError(_("No position has been loaded yet"), 0);
\r
9181 if (positionNumber <= 0) {
\r
9182 DisplayError(_("Can't back up any further"), 0);
\r
9185 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9186 lastLoadPositionTitle);
\r
9189 /* Load the nth position from the given file */
\r
9191 LoadPositionFromFile(filename, n, title)
\r
9197 char buf[MSG_SIZ];
\r
9199 if (strcmp(filename, "-") == 0) {
\r
9200 return LoadPosition(stdin, n, "stdin");
\r
9202 f = fopen(filename, "rb");
\r
9204 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9205 DisplayError(buf, errno);
\r
9208 return LoadPosition(f, n, title);
\r
9213 /* Load the nth position from the given open file, and close it */
\r
9215 LoadPosition(f, positionNumber, title)
\r
9217 int positionNumber;
\r
9220 char *p, line[MSG_SIZ];
\r
9221 Board initial_position;
\r
9222 int i, j, fenMode, pn;
\r
9224 if (gameMode == Training )
\r
9225 SetTrainingModeOff();
\r
9227 if (gameMode != BeginningOfGame) {
\r
9228 Reset(FALSE, TRUE);
\r
9230 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9231 fclose(lastLoadPositionFP);
\r
9233 if (positionNumber == 0) positionNumber = 1;
\r
9234 lastLoadPositionFP = f;
\r
9235 lastLoadPositionNumber = positionNumber;
\r
9236 strcpy(lastLoadPositionTitle, title);
\r
9237 if (first.pr == NoProc) {
\r
9238 StartChessProgram(&first);
\r
9239 InitChessProgram(&first, FALSE);
\r
9241 pn = positionNumber;
\r
9242 if (positionNumber < 0) {
\r
9243 /* Negative position number means to seek to that byte offset */
\r
9244 if (fseek(f, -positionNumber, 0) == -1) {
\r
9245 DisplayError(_("Can't seek on position file"), 0);
\r
9250 if (fseek(f, 0, 0) == -1) {
\r
9251 if (f == lastLoadPositionFP ?
\r
9252 positionNumber == lastLoadPositionNumber + 1 :
\r
9253 positionNumber == 1) {
\r
9256 DisplayError(_("Can't seek on position file"), 0);
\r
9261 /* See if this file is FEN or old-style xboard */
\r
9262 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9263 DisplayError(_("Position not found in file"), 0);
\r
9267 switch (line[0]) {
\r
9268 case '#': case 'x':
\r
9272 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9273 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9274 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9275 case '7': case '8': case '9':
\r
9276 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9277 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9278 case 'C': case 'W': case 'c': case 'w':
\r
9283 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9284 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9288 if (fenMode || line[0] == '#') pn--;
\r
9290 /* skip positions before number pn */
\r
9291 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9292 Reset(TRUE, TRUE);
\r
9293 DisplayError(_("Position not found in file"), 0);
\r
9296 if (fenMode || line[0] == '#') pn--;
\r
9301 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9302 DisplayError(_("Bad FEN position in file"), 0);
\r
9306 (void) fgets(line, MSG_SIZ, f);
\r
9307 (void) fgets(line, MSG_SIZ, f);
\r
9309 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9310 (void) fgets(line, MSG_SIZ, f);
\r
9311 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9314 initial_position[i][j++] = CharToPiece(*p);
\r
9318 blackPlaysFirst = FALSE;
\r
9320 (void) fgets(line, MSG_SIZ, f);
\r
9321 if (strncmp(line, "black", strlen("black"))==0)
\r
9322 blackPlaysFirst = TRUE;
\r
9325 startedFromSetupPosition = TRUE;
\r
9327 SendToProgram("force\n", &first);
\r
9328 CopyBoard(boards[0], initial_position);
\r
9329 if (blackPlaysFirst) {
\r
9330 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9331 strcpy(moveList[0], "");
\r
9332 strcpy(parseList[0], "");
\r
9333 CopyBoard(boards[1], initial_position);
\r
9334 DisplayMessage("", _("Black to play"));
\r
9336 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9337 DisplayMessage("", _("White to play"));
\r
9339 /* [HGM] copy FEN attributes as well */
\r
9341 initialRulePlies = FENrulePlies;
\r
9342 epStatus[forwardMostMove] = FENepStatus;
\r
9343 for( i=0; i< nrCastlingRights; i++ )
\r
9344 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9346 SendBoard(&first, forwardMostMove);
\r
9347 if (appData.debugMode) {
\r
9349 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9350 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9351 fprintf(debugFP, "Load Position\n");
\r
9354 if (positionNumber > 1) {
\r
9355 sprintf(line, "%s %d", title, positionNumber);
\r
9356 DisplayTitle(line);
\r
9358 DisplayTitle(title);
\r
9360 gameMode = EditGame;
\r
9363 timeRemaining[0][1] = whiteTimeRemaining;
\r
9364 timeRemaining[1][1] = blackTimeRemaining;
\r
9365 DrawPosition(FALSE, boards[currentMove]);
\r
9372 CopyPlayerNameIntoFileName(dest, src)
\r
9373 char **dest, *src;
\r
9375 while (*src != NULLCHAR && *src != ',') {
\r
9376 if (*src == ' ') {
\r
9380 *(*dest)++ = *src++;
\r
9385 char *DefaultFileName(ext)
\r
9388 static char def[MSG_SIZ];
\r
9391 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9393 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9395 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9399 def[0] = NULLCHAR;
\r
9404 /* Save the current game to the given file */
\r
9406 SaveGameToFile(filename, append)
\r
9411 char buf[MSG_SIZ];
\r
9413 if (strcmp(filename, "-") == 0) {
\r
9414 return SaveGame(stdout, 0, NULL);
\r
9416 f = fopen(filename, append ? "a" : "w");
\r
9418 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9419 DisplayError(buf, errno);
\r
9422 return SaveGame(f, 0, NULL);
\r
9431 static char buf[MSG_SIZ];
\r
9434 p = strchr(str, ' ');
\r
9435 if (p == NULL) return str;
\r
9436 strncpy(buf, str, p - str);
\r
9437 buf[p - str] = NULLCHAR;
\r
9441 #define PGN_MAX_LINE 75
\r
9443 #define PGN_SIDE_WHITE 0
\r
9444 #define PGN_SIDE_BLACK 1
\r
9447 static int FindFirstMoveOutOfBook( int side )
\r
9451 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9452 int index = backwardMostMove;
\r
9453 int has_book_hit = 0;
\r
9455 if( (index % 2) != side ) {
\r
9459 while( index < forwardMostMove ) {
\r
9460 /* Check to see if engine is in book */
\r
9461 int depth = pvInfoList[index].depth;
\r
9462 int score = pvInfoList[index].score;
\r
9465 if( depth <= 2 ) {
\r
9468 else if( score == 0 && depth == 63 ) {
\r
9469 in_book = 1; /* Zappa */
\r
9471 else if( score == 2 && depth == 99 ) {
\r
9472 in_book = 1; /* Abrok */
\r
9475 has_book_hit += in_book;
\r
9491 void GetOutOfBookInfo( char * buf )
\r
9495 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9497 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9498 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9502 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9503 for( i=0; i<2; i++ ) {
\r
9507 if( i > 0 && oob[0] >= 0 ) {
\r
9508 strcat( buf, " " );
\r
9511 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9512 sprintf( buf+strlen(buf), "%s%.2f",
\r
9513 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9514 pvInfoList[idx].score / 100.0 );
\r
9520 /* Save game in PGN style and close the file */
\r
9525 int i, offset, linelen, newblock;
\r
9527 // char *movetext;
\r
9529 int movelen, numlen, blank;
\r
9530 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9532 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9534 tm = time((time_t *) NULL);
\r
9536 PrintPGNTags(f, &gameInfo);
\r
9538 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9539 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9540 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9541 fprintf(f, "\n{--------------\n");
\r
9542 PrintPosition(f, backwardMostMove);
\r
9543 fprintf(f, "--------------}\n");
\r
9547 /* [AS] Out of book annotation */
\r
9548 if( appData.saveOutOfBookInfo ) {
\r
9551 GetOutOfBookInfo( buf );
\r
9553 if( buf[0] != '\0' ) {
\r
9554 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9561 i = backwardMostMove;
\r
9565 while (i < forwardMostMove) {
\r
9566 /* Print comments preceding this move */
\r
9567 if (commentList[i] != NULL) {
\r
9568 if (linelen > 0) fprintf(f, "\n");
\r
9569 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9574 /* Format move number */
\r
9575 if ((i % 2) == 0) {
\r
9576 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9579 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9581 numtext[0] = NULLCHAR;
\r
9584 numlen = strlen(numtext);
\r
9587 /* Print move number */
\r
9588 blank = linelen > 0 && numlen > 0;
\r
9589 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9598 fprintf(f, numtext);
\r
9599 linelen += numlen;
\r
9602 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9605 blank = linelen > 0 && movelen > 0;
\r
9606 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9615 fprintf(f, parseList[i]);
\r
9616 linelen += movelen;
\r
9618 /* [AS] Add PV info if present */
\r
9619 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9620 /* [HGM] add time */
\r
9621 char buf[MSG_SIZ]; int seconds = 0;
\r
9624 if(i >= backwardMostMove) {
\r
9625 if(WhiteOnMove(i))
\r
9626 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9627 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9629 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9630 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9632 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9634 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9637 if( seconds <= 0) buf[0] = 0; else
\r
9638 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9639 seconds = (seconds + 4)/10; // round to full seconds
\r
9640 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9641 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9644 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9645 pvInfoList[i].score >= 0 ? "+" : "",
\r
9646 pvInfoList[i].score / 100.0,
\r
9647 pvInfoList[i].depth,
\r
9650 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9652 /* Print score/depth */
\r
9653 blank = linelen > 0 && movelen > 0;
\r
9654 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9663 fprintf(f, move_buffer);
\r
9664 linelen += movelen;
\r
9670 /* Start a new line */
\r
9671 if (linelen > 0) fprintf(f, "\n");
\r
9673 /* Print comments after last move */
\r
9674 if (commentList[i] != NULL) {
\r
9675 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9678 /* Print result */
\r
9679 if (gameInfo.resultDetails != NULL &&
\r
9680 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9681 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9682 PGNResult(gameInfo.result));
\r
9684 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9691 /* Save game in old style and close the file */
\r
9693 SaveGameOldStyle(f)
\r
9699 tm = time((time_t *) NULL);
\r
9701 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9702 PrintOpponents(f);
\r
9704 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9705 fprintf(f, "\n[--------------\n");
\r
9706 PrintPosition(f, backwardMostMove);
\r
9707 fprintf(f, "--------------]\n");
\r
9712 i = backwardMostMove;
\r
9713 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9715 while (i < forwardMostMove) {
\r
9716 if (commentList[i] != NULL) {
\r
9717 fprintf(f, "[%s]\n", commentList[i]);
\r
9720 if ((i % 2) == 1) {
\r
9721 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9724 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9726 if (commentList[i] != NULL) {
\r
9730 if (i >= forwardMostMove) {
\r
9734 fprintf(f, "%s\n", parseList[i]);
\r
9739 if (commentList[i] != NULL) {
\r
9740 fprintf(f, "[%s]\n", commentList[i]);
\r
9743 /* This isn't really the old style, but it's close enough */
\r
9744 if (gameInfo.resultDetails != NULL &&
\r
9745 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9746 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9747 gameInfo.resultDetails);
\r
9749 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9756 /* Save the current game to open file f and close the file */
\r
9758 SaveGame(f, dummy, dummy2)
\r
9763 if (gameMode == EditPosition) EditPositionDone();
\r
9764 if (appData.oldSaveStyle)
\r
9765 return SaveGameOldStyle(f);
\r
9767 return SaveGamePGN(f);
\r
9770 /* Save the current position to the given file */
\r
9772 SavePositionToFile(filename)
\r
9776 char buf[MSG_SIZ];
\r
9778 if (strcmp(filename, "-") == 0) {
\r
9779 return SavePosition(stdout, 0, NULL);
\r
9781 f = fopen(filename, "a");
\r
9783 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9784 DisplayError(buf, errno);
\r
9787 SavePosition(f, 0, NULL);
\r
9793 /* Save the current position to the given open file and close the file */
\r
9795 SavePosition(f, dummy, dummy2)
\r
9803 if (appData.oldSaveStyle) {
\r
9804 tm = time((time_t *) NULL);
\r
9806 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9807 PrintOpponents(f);
\r
9808 fprintf(f, "[--------------\n");
\r
9809 PrintPosition(f, currentMove);
\r
9810 fprintf(f, "--------------]\n");
\r
9812 fen = PositionToFEN(currentMove, 1);
\r
9813 fprintf(f, "%s\n", fen);
\r
9821 ReloadCmailMsgEvent(unregister)
\r
9825 static char *inFilename = NULL;
\r
9826 static char *outFilename;
\r
9828 struct stat inbuf, outbuf;
\r
9831 /* Any registered moves are unregistered if unregister is set, */
\r
9832 /* i.e. invoked by the signal handler */
\r
9834 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9835 cmailMoveRegistered[i] = FALSE;
\r
9836 if (cmailCommentList[i] != NULL) {
\r
9837 free(cmailCommentList[i]);
\r
9838 cmailCommentList[i] = NULL;
\r
9841 nCmailMovesRegistered = 0;
\r
9844 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9845 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9847 nCmailResults = 0;
\r
9849 if (inFilename == NULL) {
\r
9850 /* Because the filenames are static they only get malloced once */
\r
9851 /* and they never get freed */
\r
9852 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9853 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9855 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9856 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9859 status = stat(outFilename, &outbuf);
\r
9861 cmailMailedMove = FALSE;
\r
9863 status = stat(inFilename, &inbuf);
\r
9864 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9867 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9868 counts the games, notes how each one terminated, etc.
\r
9870 It would be nice to remove this kludge and instead gather all
\r
9871 the information while building the game list. (And to keep it
\r
9872 in the game list nodes instead of having a bunch of fixed-size
\r
9873 parallel arrays.) Note this will require getting each game's
\r
9874 termination from the PGN tags, as the game list builder does
\r
9875 not process the game moves. --mann
\r
9877 cmailMsgLoaded = TRUE;
\r
9878 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9880 /* Load first game in the file or popup game menu */
\r
9881 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9883 #endif /* !WIN32 */
\r
9891 char string[MSG_SIZ];
\r
9893 if ( cmailMailedMove
\r
9894 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9895 return TRUE; /* Allow free viewing */
\r
9898 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9899 /* with the move registered when the conditions for registering no */
\r
9901 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9902 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9903 nCmailMovesRegistered --;
\r
9905 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9907 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9908 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9912 if (cmailOldMove == -1) {
\r
9913 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9917 if (currentMove > cmailOldMove + 1) {
\r
9918 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9922 if (currentMove < cmailOldMove) {
\r
9923 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9927 if (forwardMostMove > currentMove) {
\r
9928 /* Silently truncate extra moves */
\r
9932 if ( (currentMove == cmailOldMove + 1)
\r
9933 || ( (currentMove == cmailOldMove)
\r
9934 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9935 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9936 if (gameInfo.result != GameUnfinished) {
\r
9937 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9940 if (commentList[currentMove] != NULL) {
\r
9941 cmailCommentList[lastLoadGameNumber - 1]
\r
9942 = StrSave(commentList[currentMove]);
\r
9944 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9946 if (appData.debugMode)
\r
9947 fprintf(debugFP, "Saving %s for game %d\n",
\r
9948 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9951 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9953 f = fopen(string, "w");
\r
9954 if (appData.oldSaveStyle) {
\r
9955 SaveGameOldStyle(f); /* also closes the file */
\r
9957 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9958 f = fopen(string, "w");
\r
9959 SavePosition(f, 0, NULL); /* also closes the file */
\r
9961 fprintf(f, "{--------------\n");
\r
9962 PrintPosition(f, currentMove);
\r
9963 fprintf(f, "--------------}\n\n");
\r
9965 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9968 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9969 nCmailMovesRegistered ++;
\r
9970 } else if (nCmailGames == 1) {
\r
9971 DisplayError(_("You have not made a move yet"), 0);
\r
9982 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9983 FILE *commandOutput;
\r
9984 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9985 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9991 if (! cmailMsgLoaded) {
\r
9992 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9996 if (nCmailGames == nCmailResults) {
\r
9997 DisplayError(_("No unfinished games"), 0);
\r
10001 #if CMAIL_PROHIBIT_REMAIL
\r
10002 if (cmailMailedMove) {
\r
10003 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
10004 DisplayError(msg, 0);
\r
10009 if (! (cmailMailedMove || RegisterMove())) return;
\r
10011 if ( cmailMailedMove
\r
10012 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10013 sprintf(string, partCommandString,
\r
10014 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10015 commandOutput = popen(string, "r");
\r
10017 if (commandOutput == NULL) {
\r
10018 DisplayError(_("Failed to invoke cmail"), 0);
\r
10020 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10021 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10023 if (nBuffers > 1) {
\r
10024 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10025 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10026 nBytes = MSG_SIZ - 1;
\r
10028 (void) memcpy(msg, buffer, nBytes);
\r
10030 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10032 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10033 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10036 for (i = 0; i < nCmailGames; i ++) {
\r
10037 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10038 archived = FALSE;
\r
10042 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10044 sprintf(buffer, "%s/%s.%s.archive",
\r
10046 appData.cmailGameName,
\r
10048 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10049 cmailMsgLoaded = FALSE;
\r
10053 DisplayInformation(msg);
\r
10054 pclose(commandOutput);
\r
10057 if ((*cmailMsg) != '\0') {
\r
10058 DisplayInformation(cmailMsg);
\r
10063 #endif /* !WIN32 */
\r
10072 int prependComma = 0;
\r
10074 char string[MSG_SIZ]; /* Space for game-list */
\r
10077 if (!cmailMsgLoaded) return "";
\r
10079 if (cmailMailedMove) {
\r
10080 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10082 /* Create a list of games left */
\r
10083 sprintf(string, "[");
\r
10084 for (i = 0; i < nCmailGames; i ++) {
\r
10085 if (! ( cmailMoveRegistered[i]
\r
10086 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10087 if (prependComma) {
\r
10088 sprintf(number, ",%d", i + 1);
\r
10090 sprintf(number, "%d", i + 1);
\r
10091 prependComma = 1;
\r
10094 strcat(string, number);
\r
10097 strcat(string, "]");
\r
10099 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10100 switch (nCmailGames) {
\r
10102 sprintf(cmailMsg,
\r
10103 _("Still need to make move for game\n"));
\r
10107 sprintf(cmailMsg,
\r
10108 _("Still need to make moves for both games\n"));
\r
10112 sprintf(cmailMsg,
\r
10113 _("Still need to make moves for all %d games\n"),
\r
10118 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10120 sprintf(cmailMsg,
\r
10121 _("Still need to make a move for game %s\n"),
\r
10126 if (nCmailResults == nCmailGames) {
\r
10127 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10129 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10134 sprintf(cmailMsg,
\r
10135 _("Still need to make moves for games %s\n"),
\r
10141 #endif /* WIN32 */
\r
10147 if (gameMode == Training)
\r
10148 SetTrainingModeOff();
\r
10150 Reset(TRUE, TRUE);
\r
10151 cmailMsgLoaded = FALSE;
\r
10152 if (appData.icsActive) {
\r
10153 SendToICS(ics_prefix);
\r
10154 SendToICS("refresh\n");
\r
10159 ExitEvent(status)
\r
10163 if (exiting > 2) {
\r
10164 /* Give up on clean exit */
\r
10167 if (exiting > 1) {
\r
10168 /* Keep trying for clean exit */
\r
10172 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10174 if (telnetISR != NULL) {
\r
10175 RemoveInputSource(telnetISR);
\r
10177 if (icsPR != NoProc) {
\r
10178 DestroyChildProcess(icsPR, TRUE);
\r
10181 /* Save game if resource set and not already saved by GameEnds() */
\r
10182 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10183 && forwardMostMove > 0) {
\r
10184 if (*appData.saveGameFile != NULLCHAR) {
\r
10185 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10186 } else if (appData.autoSaveGames) {
\r
10189 if (*appData.savePositionFile != NULLCHAR) {
\r
10190 SavePositionToFile(appData.savePositionFile);
\r
10193 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10195 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10196 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10198 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10199 /* make sure this other one finishes before killing it! */
\r
10200 if(endingGame) { int count = 0;
\r
10201 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10202 while(endingGame && count++ < 10) DoSleep(1);
\r
10203 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10206 /* Kill off chess programs */
\r
10207 if (first.pr != NoProc) {
\r
10208 ExitAnalyzeMode();
\r
10210 DoSleep( appData.delayBeforeQuit );
\r
10211 SendToProgram("quit\n", &first);
\r
10212 DoSleep( appData.delayAfterQuit );
\r
10213 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10215 if (second.pr != NoProc) {
\r
10216 DoSleep( appData.delayBeforeQuit );
\r
10217 SendToProgram("quit\n", &second);
\r
10218 DoSleep( appData.delayAfterQuit );
\r
10219 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10221 if (first.isr != NULL) {
\r
10222 RemoveInputSource(first.isr);
\r
10224 if (second.isr != NULL) {
\r
10225 RemoveInputSource(second.isr);
\r
10228 ShutDownFrontEnd();
\r
10235 if (appData.debugMode)
\r
10236 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10240 if (gameMode == MachinePlaysWhite ||
\r
10241 gameMode == MachinePlaysBlack) {
\r
10244 DisplayBothClocks();
\r
10246 if (gameMode == PlayFromGameFile) {
\r
10247 if (appData.timeDelay >= 0)
\r
10248 AutoPlayGameLoop();
\r
10249 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10250 Reset(FALSE, TRUE);
\r
10251 SendToICS(ics_prefix);
\r
10252 SendToICS("refresh\n");
\r
10253 } else if (currentMove < forwardMostMove) {
\r
10254 ForwardInner(forwardMostMove);
\r
10256 pauseExamInvalid = FALSE;
\r
10258 switch (gameMode) {
\r
10261 case IcsExamining:
\r
10262 pauseExamForwardMostMove = forwardMostMove;
\r
10263 pauseExamInvalid = FALSE;
\r
10264 /* fall through */
\r
10265 case IcsObserving:
\r
10266 case IcsPlayingWhite:
\r
10267 case IcsPlayingBlack:
\r
10271 case PlayFromGameFile:
\r
10272 (void) StopLoadGameTimer();
\r
10276 case BeginningOfGame:
\r
10277 if (appData.icsActive) return;
\r
10278 /* else fall through */
\r
10279 case MachinePlaysWhite:
\r
10280 case MachinePlaysBlack:
\r
10281 case TwoMachinesPlay:
\r
10282 if (forwardMostMove == 0)
\r
10283 return; /* don't pause if no one has moved */
\r
10284 if ((gameMode == MachinePlaysWhite &&
\r
10285 !WhiteOnMove(forwardMostMove)) ||
\r
10286 (gameMode == MachinePlaysBlack &&
\r
10287 WhiteOnMove(forwardMostMove))) {
\r
10298 EditCommentEvent()
\r
10300 char title[MSG_SIZ];
\r
10302 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10303 strcpy(title, _("Edit comment"));
\r
10305 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10306 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10307 parseList[currentMove - 1]);
\r
10310 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10317 char *tags = PGNTags(&gameInfo);
\r
10318 EditTagsPopUp(tags);
\r
10323 AnalyzeModeEvent()
\r
10325 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10328 if (gameMode != AnalyzeFile) {
\r
10329 if (!appData.icsEngineAnalyze) {
\r
10331 if (gameMode != EditGame) return;
\r
10333 ResurrectChessProgram();
\r
10334 SendToProgram("analyze\n", &first);
\r
10335 first.analyzing = TRUE;
\r
10336 /*first.maybeThinking = TRUE;*/
\r
10337 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10338 AnalysisPopUp(_("Analysis"),
\r
10339 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10341 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10346 StartAnalysisClock();
\r
10347 GetTimeMark(&lastNodeCountTime);
\r
10348 lastNodeCount = 0;
\r
10352 AnalyzeFileEvent()
\r
10354 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10357 if (gameMode != AnalyzeMode) {
\r
10359 if (gameMode != EditGame) return;
\r
10360 ResurrectChessProgram();
\r
10361 SendToProgram("analyze\n", &first);
\r
10362 first.analyzing = TRUE;
\r
10363 /*first.maybeThinking = TRUE;*/
\r
10364 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10365 AnalysisPopUp(_("Analysis"),
\r
10366 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10368 gameMode = AnalyzeFile;
\r
10373 StartAnalysisClock();
\r
10374 GetTimeMark(&lastNodeCountTime);
\r
10375 lastNodeCount = 0;
\r
10379 MachineWhiteEvent()
\r
10381 char buf[MSG_SIZ];
\r
10382 char *bookHit = NULL;
\r
10384 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10388 if (gameMode == PlayFromGameFile ||
\r
10389 gameMode == TwoMachinesPlay ||
\r
10390 gameMode == Training ||
\r
10391 gameMode == AnalyzeMode ||
\r
10392 gameMode == EndOfGame)
\r
10395 if (gameMode == EditPosition)
\r
10396 EditPositionDone();
\r
10398 if (!WhiteOnMove(currentMove)) {
\r
10399 DisplayError(_("It is not White's turn"), 0);
\r
10403 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10404 ExitAnalyzeMode();
\r
10406 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10407 gameMode == AnalyzeFile)
\r
10410 ResurrectChessProgram(); /* in case it isn't running */
\r
10411 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10412 gameMode = MachinePlaysWhite;
\r
10415 gameMode = MachinePlaysWhite;
\r
10419 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10420 DisplayTitle(buf);
\r
10421 if (first.sendName) {
\r
10422 sprintf(buf, "name %s\n", gameInfo.black);
\r
10423 SendToProgram(buf, &first);
\r
10425 if (first.sendTime) {
\r
10426 if (first.useColors) {
\r
10427 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10429 SendTimeRemaining(&first, TRUE);
\r
10431 if (first.useColors) {
\r
10432 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10434 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10435 SetMachineThinkingEnables();
\r
10436 first.maybeThinking = TRUE;
\r
10439 if (appData.autoFlipView && !flipView) {
\r
10440 flipView = !flipView;
\r
10441 DrawPosition(FALSE, NULL);
\r
10442 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10445 if(bookHit) { // [HGM] book: simulate book reply
\r
10446 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10448 programStats.nodes = programStats.depth = programStats.time =
\r
10449 programStats.score = programStats.got_only_move = 0;
\r
10450 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10452 strcpy(bookMove, "move ");
\r
10453 strcat(bookMove, bookHit);
\r
10454 HandleMachineMove(bookMove, &first);
\r
10459 MachineBlackEvent()
\r
10461 char buf[MSG_SIZ];
\r
10462 char *bookHit = NULL;
\r
10464 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10468 if (gameMode == PlayFromGameFile ||
\r
10469 gameMode == TwoMachinesPlay ||
\r
10470 gameMode == Training ||
\r
10471 gameMode == AnalyzeMode ||
\r
10472 gameMode == EndOfGame)
\r
10475 if (gameMode == EditPosition)
\r
10476 EditPositionDone();
\r
10478 if (WhiteOnMove(currentMove)) {
\r
10479 DisplayError(_("It is not Black's turn"), 0);
\r
10483 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10484 ExitAnalyzeMode();
\r
10486 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10487 gameMode == AnalyzeFile)
\r
10490 ResurrectChessProgram(); /* in case it isn't running */
\r
10491 gameMode = MachinePlaysBlack;
\r
10495 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10496 DisplayTitle(buf);
\r
10497 if (first.sendName) {
\r
10498 sprintf(buf, "name %s\n", gameInfo.white);
\r
10499 SendToProgram(buf, &first);
\r
10501 if (first.sendTime) {
\r
10502 if (first.useColors) {
\r
10503 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10505 SendTimeRemaining(&first, FALSE);
\r
10507 if (first.useColors) {
\r
10508 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10510 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10511 SetMachineThinkingEnables();
\r
10512 first.maybeThinking = TRUE;
\r
10515 if (appData.autoFlipView && flipView) {
\r
10516 flipView = !flipView;
\r
10517 DrawPosition(FALSE, NULL);
\r
10518 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10520 if(bookHit) { // [HGM] book: simulate book reply
\r
10521 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10523 programStats.nodes = programStats.depth = programStats.time =
\r
10524 programStats.score = programStats.got_only_move = 0;
\r
10525 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10527 strcpy(bookMove, "move ");
\r
10528 strcat(bookMove, bookHit);
\r
10529 HandleMachineMove(bookMove, &first);
\r
10535 DisplayTwoMachinesTitle()
\r
10537 char buf[MSG_SIZ];
\r
10538 if (appData.matchGames > 0) {
\r
10539 if (first.twoMachinesColor[0] == 'w') {
\r
10540 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10541 gameInfo.white, gameInfo.black,
\r
10542 first.matchWins, second.matchWins,
\r
10543 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10545 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10546 gameInfo.white, gameInfo.black,
\r
10547 second.matchWins, first.matchWins,
\r
10548 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10551 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10553 DisplayTitle(buf);
\r
10557 TwoMachinesEvent P((void))
\r
10560 char buf[MSG_SIZ];
\r
10561 ChessProgramState *onmove;
\r
10562 char *bookHit = NULL;
\r
10564 if (appData.noChessProgram) return;
\r
10566 switch (gameMode) {
\r
10567 case TwoMachinesPlay:
\r
10569 case MachinePlaysWhite:
\r
10570 case MachinePlaysBlack:
\r
10571 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10572 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10575 /* fall through */
\r
10576 case BeginningOfGame:
\r
10577 case PlayFromGameFile:
\r
10580 if (gameMode != EditGame) return;
\r
10582 case EditPosition:
\r
10583 EditPositionDone();
\r
10585 case AnalyzeMode:
\r
10586 case AnalyzeFile:
\r
10587 ExitAnalyzeMode();
\r
10594 forwardMostMove = currentMove;
\r
10595 ResurrectChessProgram(); /* in case first program isn't running */
\r
10597 if (second.pr == NULL) {
\r
10598 StartChessProgram(&second);
\r
10599 if (second.protocolVersion == 1) {
\r
10600 TwoMachinesEventIfReady();
\r
10602 /* kludge: allow timeout for initial "feature" command */
\r
10604 DisplayMessage("", _("Starting second chess program"));
\r
10605 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10609 DisplayMessage("", "");
\r
10610 InitChessProgram(&second, FALSE);
\r
10611 SendToProgram("force\n", &second);
\r
10612 if (startedFromSetupPosition) {
\r
10613 SendBoard(&second, backwardMostMove);
\r
10614 if (appData.debugMode) {
\r
10615 fprintf(debugFP, "Two Machines\n");
\r
10618 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10619 SendMoveToProgram(i, &second);
\r
10622 gameMode = TwoMachinesPlay;
\r
10626 DisplayTwoMachinesTitle();
\r
10627 firstMove = TRUE;
\r
10628 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10631 onmove = &second;
\r
10634 SendToProgram(first.computerString, &first);
\r
10635 if (first.sendName) {
\r
10636 sprintf(buf, "name %s\n", second.tidy);
\r
10637 SendToProgram(buf, &first);
\r
10639 SendToProgram(second.computerString, &second);
\r
10640 if (second.sendName) {
\r
10641 sprintf(buf, "name %s\n", first.tidy);
\r
10642 SendToProgram(buf, &second);
\r
10646 if (!first.sendTime || !second.sendTime) {
\r
10647 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10648 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10650 if (onmove->sendTime) {
\r
10651 if (onmove->useColors) {
\r
10652 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10654 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10656 if (onmove->useColors) {
\r
10657 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10659 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10660 // SendToProgram("go\n", onmove);
\r
10661 onmove->maybeThinking = TRUE;
\r
10662 SetMachineThinkingEnables();
\r
10666 if(bookHit) { // [HGM] book: simulate book reply
\r
10667 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10669 programStats.nodes = programStats.depth = programStats.time =
\r
10670 programStats.score = programStats.got_only_move = 0;
\r
10671 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10673 strcpy(bookMove, "move ");
\r
10674 strcat(bookMove, bookHit);
\r
10675 HandleMachineMove(bookMove, &first);
\r
10682 if (gameMode == Training) {
\r
10683 SetTrainingModeOff();
\r
10684 gameMode = PlayFromGameFile;
\r
10685 DisplayMessage("", _("Training mode off"));
\r
10687 gameMode = Training;
\r
10688 animateTraining = appData.animate;
\r
10690 /* make sure we are not already at the end of the game */
\r
10691 if (currentMove < forwardMostMove) {
\r
10692 SetTrainingModeOn();
\r
10693 DisplayMessage("", _("Training mode on"));
\r
10695 gameMode = PlayFromGameFile;
\r
10696 DisplayError(_("Already at end of game"), 0);
\r
10705 if (!appData.icsActive) return;
\r
10706 switch (gameMode) {
\r
10707 case IcsPlayingWhite:
\r
10708 case IcsPlayingBlack:
\r
10709 case IcsObserving:
\r
10711 case BeginningOfGame:
\r
10712 case IcsExamining:
\r
10718 case EditPosition:
\r
10719 EditPositionDone();
\r
10722 case AnalyzeMode:
\r
10723 case AnalyzeFile:
\r
10724 ExitAnalyzeMode();
\r
10732 gameMode = IcsIdle;
\r
10743 switch (gameMode) {
\r
10745 SetTrainingModeOff();
\r
10747 case MachinePlaysWhite:
\r
10748 case MachinePlaysBlack:
\r
10749 case BeginningOfGame:
\r
10750 SendToProgram("force\n", &first);
\r
10751 SetUserThinkingEnables();
\r
10753 case PlayFromGameFile:
\r
10754 (void) StopLoadGameTimer();
\r
10755 if (gameFileFP != NULL) {
\r
10756 gameFileFP = NULL;
\r
10759 case EditPosition:
\r
10760 EditPositionDone();
\r
10762 case AnalyzeMode:
\r
10763 case AnalyzeFile:
\r
10764 ExitAnalyzeMode();
\r
10765 SendToProgram("force\n", &first);
\r
10767 case TwoMachinesPlay:
\r
10768 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10769 ResurrectChessProgram();
\r
10770 SetUserThinkingEnables();
\r
10773 ResurrectChessProgram();
\r
10775 case IcsPlayingBlack:
\r
10776 case IcsPlayingWhite:
\r
10777 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10779 case IcsObserving:
\r
10780 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10782 case IcsExamining:
\r
10783 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10794 first.offeredDraw = second.offeredDraw = 0;
\r
10796 if (gameMode == PlayFromGameFile) {
\r
10797 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10798 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10799 DisplayTitle("");
\r
10802 if (gameMode == MachinePlaysWhite ||
\r
10803 gameMode == MachinePlaysBlack ||
\r
10804 gameMode == TwoMachinesPlay ||
\r
10805 gameMode == EndOfGame) {
\r
10806 i = forwardMostMove;
\r
10807 while (i > currentMove) {
\r
10808 SendToProgram("undo\n", &first);
\r
10811 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10812 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10813 DisplayBothClocks();
\r
10814 if (whiteFlag || blackFlag) {
\r
10815 whiteFlag = blackFlag = 0;
\r
10817 DisplayTitle("");
\r
10820 gameMode = EditGame;
\r
10827 EditPositionEvent()
\r
10829 if (gameMode == EditPosition) {
\r
10835 if (gameMode != EditGame) return;
\r
10837 gameMode = EditPosition;
\r
10840 if (currentMove > 0)
\r
10841 CopyBoard(boards[0], boards[currentMove]);
\r
10843 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10845 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10846 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10851 ExitAnalyzeMode()
\r
10853 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10854 if (appData.icsEngineAnalyze) {
\r
10855 appData.icsEngineAnalyze = FALSE;
\r
10857 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10859 if (first.analysisSupport && first.analyzing) {
\r
10860 SendToProgram("exit\n", &first);
\r
10861 first.analyzing = FALSE;
\r
10863 AnalysisPopDown();
\r
10864 thinkOutput[0] = NULLCHAR;
\r
10868 EditPositionDone()
\r
10870 startedFromSetupPosition = TRUE;
\r
10871 InitChessProgram(&first, FALSE);
\r
10872 SendToProgram("force\n", &first);
\r
10873 if (blackPlaysFirst) {
\r
10874 strcpy(moveList[0], "");
\r
10875 strcpy(parseList[0], "");
\r
10876 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10877 CopyBoard(boards[1], boards[0]);
\r
10878 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10880 epStatus[1] = epStatus[0];
\r
10881 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10884 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10886 SendBoard(&first, forwardMostMove);
\r
10887 if (appData.debugMode) {
\r
10888 fprintf(debugFP, "EditPosDone\n");
\r
10890 DisplayTitle("");
\r
10891 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10892 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10893 gameMode = EditGame;
\r
10895 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10896 ClearHighlights(); /* [AS] */
\r
10899 /* Pause for `ms' milliseconds */
\r
10900 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10907 GetTimeMark(&m1);
\r
10909 GetTimeMark(&m2);
\r
10910 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10913 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10915 SendMultiLineToICS(buf)
\r
10918 char temp[MSG_SIZ+1], *p;
\r
10921 len = strlen(buf);
\r
10922 if (len > MSG_SIZ)
\r
10925 strncpy(temp, buf, len);
\r
10930 if (*p == '\n' || *p == '\r')
\r
10935 strcat(temp, "\n");
\r
10937 SendToPlayer(temp, strlen(temp));
\r
10941 SetWhiteToPlayEvent()
\r
10943 if (gameMode == EditPosition) {
\r
10944 blackPlaysFirst = FALSE;
\r
10945 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10946 } else if (gameMode == IcsExamining) {
\r
10947 SendToICS(ics_prefix);
\r
10948 SendToICS("tomove white\n");
\r
10953 SetBlackToPlayEvent()
\r
10955 if (gameMode == EditPosition) {
\r
10956 blackPlaysFirst = TRUE;
\r
10957 currentMove = 1; /* kludge */
\r
10958 DisplayBothClocks();
\r
10960 } else if (gameMode == IcsExamining) {
\r
10961 SendToICS(ics_prefix);
\r
10962 SendToICS("tomove black\n");
\r
10967 EditPositionMenuEvent(selection, x, y)
\r
10968 ChessSquare selection;
\r
10971 char buf[MSG_SIZ];
\r
10972 ChessSquare piece = boards[0][y][x];
\r
10974 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10976 switch (selection) {
\r
10978 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10979 SendToICS(ics_prefix);
\r
10980 SendToICS("bsetup clear\n");
\r
10981 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10982 SendToICS(ics_prefix);
\r
10983 SendToICS("clearboard\n");
\r
10985 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10986 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10987 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10988 if (gameMode == IcsExamining) {
\r
10989 if (boards[currentMove][y][x] != EmptySquare) {
\r
10990 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10991 AAA + x, ONE + y);
\r
10995 boards[0][y][x] = p;
\r
11000 if (gameMode == EditPosition) {
\r
11001 DrawPosition(FALSE, boards[0]);
\r
11006 SetWhiteToPlayEvent();
\r
11010 SetBlackToPlayEvent();
\r
11013 case EmptySquare:
\r
11014 if (gameMode == IcsExamining) {
\r
11015 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11018 boards[0][y][x] = EmptySquare;
\r
11019 DrawPosition(FALSE, boards[0]);
\r
11023 case PromotePiece:
\r
11024 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11025 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11026 selection = (ChessSquare) (PROMOTED piece);
\r
11027 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11028 else selection = (ChessSquare)((int)piece - 1);
\r
11029 goto defaultlabel;
\r
11031 case DemotePiece:
\r
11032 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11033 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11034 selection = (ChessSquare) (DEMOTED piece);
\r
11035 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11036 else selection = (ChessSquare)((int)piece + 1);
\r
11037 goto defaultlabel;
\r
11041 if(gameInfo.variant == VariantShatranj ||
\r
11042 gameInfo.variant == VariantXiangqi ||
\r
11043 gameInfo.variant == VariantCourier )
\r
11044 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11045 goto defaultlabel;
\r
11049 if(gameInfo.variant == VariantXiangqi)
\r
11050 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11051 if(gameInfo.variant == VariantKnightmate)
\r
11052 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11055 if (gameMode == IcsExamining) {
\r
11056 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11057 PieceToChar(selection), AAA + x, ONE + y);
\r
11060 boards[0][y][x] = selection;
\r
11061 DrawPosition(FALSE, boards[0]);
\r
11069 DropMenuEvent(selection, x, y)
\r
11070 ChessSquare selection;
\r
11073 ChessMove moveType;
\r
11075 switch (gameMode) {
\r
11076 case IcsPlayingWhite:
\r
11077 case MachinePlaysBlack:
\r
11078 if (!WhiteOnMove(currentMove)) {
\r
11079 DisplayMoveError(_("It is Black's turn"));
\r
11082 moveType = WhiteDrop;
\r
11084 case IcsPlayingBlack:
\r
11085 case MachinePlaysWhite:
\r
11086 if (WhiteOnMove(currentMove)) {
\r
11087 DisplayMoveError(_("It is White's turn"));
\r
11090 moveType = BlackDrop;
\r
11093 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11099 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11100 selection = (ChessSquare) ((int) selection
\r
11101 + (int) BlackPawn - (int) WhitePawn);
\r
11103 if (boards[currentMove][y][x] != EmptySquare) {
\r
11104 DisplayMoveError(_("That square is occupied"));
\r
11108 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11114 /* Accept a pending offer of any kind from opponent */
\r
11116 if (appData.icsActive) {
\r
11117 SendToICS(ics_prefix);
\r
11118 SendToICS("accept\n");
\r
11119 } else if (cmailMsgLoaded) {
\r
11120 if (currentMove == cmailOldMove &&
\r
11121 commentList[cmailOldMove] != NULL &&
\r
11122 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11123 "Black offers a draw" : "White offers a draw")) {
\r
11125 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11128 DisplayError(_("There is no pending offer on this move"), 0);
\r
11129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11132 /* Not used for offers from chess program */
\r
11139 /* Decline a pending offer of any kind from opponent */
\r
11141 if (appData.icsActive) {
\r
11142 SendToICS(ics_prefix);
\r
11143 SendToICS("decline\n");
\r
11144 } else if (cmailMsgLoaded) {
\r
11145 if (currentMove == cmailOldMove &&
\r
11146 commentList[cmailOldMove] != NULL &&
\r
11147 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11148 "Black offers a draw" : "White offers a draw")) {
\r
11150 AppendComment(cmailOldMove, "Draw declined");
\r
11151 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11152 #endif /*NOTDEF*/
\r
11154 DisplayError(_("There is no pending offer on this move"), 0);
\r
11157 /* Not used for offers from chess program */
\r
11164 /* Issue ICS rematch command */
\r
11165 if (appData.icsActive) {
\r
11166 SendToICS(ics_prefix);
\r
11167 SendToICS("rematch\n");
\r
11174 /* Call your opponent's flag (claim a win on time) */
\r
11175 if (appData.icsActive) {
\r
11176 SendToICS(ics_prefix);
\r
11177 SendToICS("flag\n");
\r
11179 switch (gameMode) {
\r
11182 case MachinePlaysWhite:
\r
11185 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11188 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11190 DisplayError(_("Your opponent is not out of time"), 0);
\r
11193 case MachinePlaysBlack:
\r
11196 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11199 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11201 DisplayError(_("Your opponent is not out of time"), 0);
\r
11211 /* Offer draw or accept pending draw offer from opponent */
\r
11213 if (appData.icsActive) {
\r
11214 /* Note: tournament rules require draw offers to be
\r
11215 made after you make your move but before you punch
\r
11216 your clock. Currently ICS doesn't let you do that;
\r
11217 instead, you immediately punch your clock after making
\r
11218 a move, but you can offer a draw at any time. */
\r
11220 SendToICS(ics_prefix);
\r
11221 SendToICS("draw\n");
\r
11222 } else if (cmailMsgLoaded) {
\r
11223 if (currentMove == cmailOldMove &&
\r
11224 commentList[cmailOldMove] != NULL &&
\r
11225 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11226 "Black offers a draw" : "White offers a draw")) {
\r
11227 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11228 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11229 } else if (currentMove == cmailOldMove + 1) {
\r
11230 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11231 "White offers a draw" : "Black offers a draw";
\r
11232 AppendComment(currentMove, offer);
\r
11233 DisplayComment(currentMove - 1, offer);
\r
11234 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11236 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11237 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11239 } else if (first.offeredDraw) {
\r
11240 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11242 if (first.sendDrawOffers) {
\r
11243 SendToProgram("draw\n", &first);
\r
11244 userOfferedDraw = TRUE;
\r
11252 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11254 if (appData.icsActive) {
\r
11255 SendToICS(ics_prefix);
\r
11256 SendToICS("adjourn\n");
\r
11258 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11266 /* Offer Abort or accept pending Abort offer from opponent */
\r
11268 if (appData.icsActive) {
\r
11269 SendToICS(ics_prefix);
\r
11270 SendToICS("abort\n");
\r
11272 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11279 /* Resign. You can do this even if it's not your turn. */
\r
11281 if (appData.icsActive) {
\r
11282 SendToICS(ics_prefix);
\r
11283 SendToICS("resign\n");
\r
11285 switch (gameMode) {
\r
11286 case MachinePlaysWhite:
\r
11287 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11289 case MachinePlaysBlack:
\r
11290 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11293 if (cmailMsgLoaded) {
\r
11295 if (WhiteOnMove(cmailOldMove)) {
\r
11296 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11298 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11311 StopObservingEvent()
\r
11313 /* Stop observing current games */
\r
11314 SendToICS(ics_prefix);
\r
11315 SendToICS("unobserve\n");
\r
11319 StopExaminingEvent()
\r
11321 /* Stop observing current game */
\r
11322 SendToICS(ics_prefix);
\r
11323 SendToICS("unexamine\n");
\r
11327 ForwardInner(target)
\r
11332 if (appData.debugMode)
\r
11333 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11334 target, currentMove, forwardMostMove);
\r
11336 if (gameMode == EditPosition)
\r
11339 if (gameMode == PlayFromGameFile && !pausing)
\r
11342 if (gameMode == IcsExamining && pausing)
\r
11343 limit = pauseExamForwardMostMove;
\r
11345 limit = forwardMostMove;
\r
11347 if (target > limit) target = limit;
\r
11349 if (target > 0 && moveList[target - 1][0]) {
\r
11350 int fromX, fromY, toX, toY;
\r
11351 toX = moveList[target - 1][2] - AAA;
\r
11352 toY = moveList[target - 1][3] - ONE;
\r
11353 if (moveList[target - 1][1] == '@') {
\r
11354 if (appData.highlightLastMove) {
\r
11355 SetHighlights(-1, -1, toX, toY);
\r
11358 fromX = moveList[target - 1][0] - AAA;
\r
11359 fromY = moveList[target - 1][1] - ONE;
\r
11360 if (target == currentMove + 1) {
\r
11361 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11363 if (appData.highlightLastMove) {
\r
11364 SetHighlights(fromX, fromY, toX, toY);
\r
11368 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11369 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11370 gameMode == AnalyzeFile) {
\r
11371 while (currentMove < target) {
\r
11372 SendMoveToProgram(currentMove++, &first);
\r
11375 currentMove = target;
\r
11378 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11379 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11380 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11382 DisplayBothClocks();
\r
11383 DisplayMove(currentMove - 1);
\r
11384 DrawPosition(FALSE, boards[currentMove]);
\r
11385 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11386 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11387 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11395 if (gameMode == IcsExamining && !pausing) {
\r
11396 SendToICS(ics_prefix);
\r
11397 SendToICS("forward\n");
\r
11399 ForwardInner(currentMove + 1);
\r
11406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11407 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11408 * the remaining moves to the engine. Otherwise we get analysis output
\r
11409 * after each move.
\r
11411 if (first.analysisSupport) {
\r
11412 SendToProgram("exit\nforce\n", &first);
\r
11413 first.analyzing = FALSE;
\r
11417 if (gameMode == IcsExamining && !pausing) {
\r
11418 SendToICS(ics_prefix);
\r
11419 SendToICS("forward 999999\n");
\r
11421 ForwardInner(forwardMostMove);
\r
11424 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11425 /* we have fed all the moves, so reactivate analysis mode */
\r
11426 SendToProgram("analyze\n", &first);
\r
11427 first.analyzing = TRUE;
\r
11428 /*first.maybeThinking = TRUE;*/
\r
11429 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11434 BackwardInner(target)
\r
11437 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11439 if (appData.debugMode)
\r
11440 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11441 target, currentMove, forwardMostMove);
\r
11443 if (gameMode == EditPosition) return;
\r
11444 if (currentMove <= backwardMostMove) {
\r
11445 ClearHighlights();
\r
11446 DrawPosition(full_redraw, boards[currentMove]);
\r
11449 if (gameMode == PlayFromGameFile && !pausing)
\r
11452 if (moveList[target][0]) {
\r
11453 int fromX, fromY, toX, toY;
\r
11454 toX = moveList[target][2] - AAA;
\r
11455 toY = moveList[target][3] - ONE;
\r
11456 if (moveList[target][1] == '@') {
\r
11457 if (appData.highlightLastMove) {
\r
11458 SetHighlights(-1, -1, toX, toY);
\r
11461 fromX = moveList[target][0] - AAA;
\r
11462 fromY = moveList[target][1] - ONE;
\r
11463 if (target == currentMove - 1) {
\r
11464 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11466 if (appData.highlightLastMove) {
\r
11467 SetHighlights(fromX, fromY, toX, toY);
\r
11471 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11472 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11473 while (currentMove > target) {
\r
11474 SendToProgram("undo\n", &first);
\r
11478 currentMove = target;
\r
11481 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11482 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11483 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11485 DisplayBothClocks();
\r
11486 DisplayMove(currentMove - 1);
\r
11487 DrawPosition(full_redraw, boards[currentMove]);
\r
11488 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11489 // [HGM] PV info: routine tests if comment empty
\r
11490 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11496 if (gameMode == IcsExamining && !pausing) {
\r
11497 SendToICS(ics_prefix);
\r
11498 SendToICS("backward\n");
\r
11500 BackwardInner(currentMove - 1);
\r
11507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11508 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11509 * all the moves. Otherwise we get analysis output after each undo.
\r
11511 if (first.analysisSupport) {
\r
11512 SendToProgram("exit\nforce\n", &first);
\r
11513 first.analyzing = FALSE;
\r
11517 if (gameMode == IcsExamining && !pausing) {
\r
11518 SendToICS(ics_prefix);
\r
11519 SendToICS("backward 999999\n");
\r
11521 BackwardInner(backwardMostMove);
\r
11524 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11525 /* we have fed all the moves, so reactivate analysis mode */
\r
11526 SendToProgram("analyze\n", &first);
\r
11527 first.analyzing = TRUE;
\r
11528 /*first.maybeThinking = TRUE;*/
\r
11529 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11534 ToNrEvent(int to)
\r
11536 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11537 if (to >= forwardMostMove) to = forwardMostMove;
\r
11538 if (to <= backwardMostMove) to = backwardMostMove;
\r
11539 if (to < currentMove) {
\r
11540 BackwardInner(to);
\r
11542 ForwardInner(to);
\r
11549 if (gameMode != IcsExamining) {
\r
11550 DisplayError(_("You are not examining a game"), 0);
\r
11554 DisplayError(_("You can't revert while pausing"), 0);
\r
11557 SendToICS(ics_prefix);
\r
11558 SendToICS("revert\n");
\r
11562 RetractMoveEvent()
\r
11564 switch (gameMode) {
\r
11565 case MachinePlaysWhite:
\r
11566 case MachinePlaysBlack:
\r
11567 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11568 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11571 if (forwardMostMove < 2) return;
\r
11572 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11573 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11574 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11575 DisplayBothClocks();
\r
11576 DisplayMove(currentMove - 1);
\r
11577 ClearHighlights();/*!! could figure this out*/
\r
11578 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11579 SendToProgram("remove\n", &first);
\r
11580 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11583 case BeginningOfGame:
\r
11587 case IcsPlayingWhite:
\r
11588 case IcsPlayingBlack:
\r
11589 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11590 SendToICS(ics_prefix);
\r
11591 SendToICS("takeback 2\n");
\r
11593 SendToICS(ics_prefix);
\r
11594 SendToICS("takeback 1\n");
\r
11603 ChessProgramState *cps;
\r
11605 switch (gameMode) {
\r
11606 case MachinePlaysWhite:
\r
11607 if (!WhiteOnMove(forwardMostMove)) {
\r
11608 DisplayError(_("It is your turn"), 0);
\r
11613 case MachinePlaysBlack:
\r
11614 if (WhiteOnMove(forwardMostMove)) {
\r
11615 DisplayError(_("It is your turn"), 0);
\r
11620 case TwoMachinesPlay:
\r
11621 if (WhiteOnMove(forwardMostMove) ==
\r
11622 (first.twoMachinesColor[0] == 'w')) {
\r
11628 case BeginningOfGame:
\r
11632 SendToProgram("?\n", cps);
\r
11636 TruncateGameEvent()
\r
11639 if (gameMode != EditGame) return;
\r
11646 if (forwardMostMove > currentMove) {
\r
11647 if (gameInfo.resultDetails != NULL) {
\r
11648 free(gameInfo.resultDetails);
\r
11649 gameInfo.resultDetails = NULL;
\r
11650 gameInfo.result = GameUnfinished;
\r
11652 forwardMostMove = currentMove;
\r
11653 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11661 if (appData.noChessProgram) return;
\r
11662 switch (gameMode) {
\r
11663 case MachinePlaysWhite:
\r
11664 if (WhiteOnMove(forwardMostMove)) {
\r
11665 DisplayError(_("Wait until your turn"), 0);
\r
11669 case BeginningOfGame:
\r
11670 case MachinePlaysBlack:
\r
11671 if (!WhiteOnMove(forwardMostMove)) {
\r
11672 DisplayError(_("Wait until your turn"), 0);
\r
11677 DisplayError(_("No hint available"), 0);
\r
11680 SendToProgram("hint\n", &first);
\r
11681 hintRequested = TRUE;
\r
11687 if (appData.noChessProgram) return;
\r
11688 switch (gameMode) {
\r
11689 case MachinePlaysWhite:
\r
11690 if (WhiteOnMove(forwardMostMove)) {
\r
11691 DisplayError(_("Wait until your turn"), 0);
\r
11695 case BeginningOfGame:
\r
11696 case MachinePlaysBlack:
\r
11697 if (!WhiteOnMove(forwardMostMove)) {
\r
11698 DisplayError(_("Wait until your turn"), 0);
\r
11702 case EditPosition:
\r
11703 EditPositionDone();
\r
11705 case TwoMachinesPlay:
\r
11710 SendToProgram("bk\n", &first);
\r
11711 bookOutput[0] = NULLCHAR;
\r
11712 bookRequested = TRUE;
\r
11718 char *tags = PGNTags(&gameInfo);
\r
11719 TagsPopUp(tags, CmailMsg());
\r
11723 /* end button procedures */
\r
11726 PrintPosition(fp, move)
\r
11732 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11733 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11734 char c = PieceToChar(boards[move][i][j]);
\r
11735 fputc(c == 'x' ? '.' : c, fp);
\r
11736 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11739 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11740 fprintf(fp, "white to play\n");
\r
11742 fprintf(fp, "black to play\n");
\r
11746 PrintOpponents(fp)
\r
11749 if (gameInfo.white != NULL) {
\r
11750 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11752 fprintf(fp, "\n");
\r
11756 /* Find last component of program's own name, using some heuristics */
\r
11758 TidyProgramName(prog, host, buf)
\r
11759 char *prog, *host, buf[MSG_SIZ];
\r
11762 int local = (strcmp(host, "localhost") == 0);
\r
11763 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11765 while (*p == ' ') p++;
\r
11768 if (*prog == '"' || *prog == '\'') {
\r
11769 q = strchr(prog + 1, *prog);
\r
11771 q = strchr(prog, ' ');
\r
11773 if (q == NULL) q = prog + strlen(prog);
\r
11775 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11777 if(p == prog && *p == '"') p++;
\r
11778 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11779 memcpy(buf, p, q - p);
\r
11780 buf[q - p] = NULLCHAR;
\r
11782 strcat(buf, "@");
\r
11783 strcat(buf, host);
\r
11788 TimeControlTagValue()
\r
11790 char buf[MSG_SIZ];
\r
11791 if (!appData.clockMode) {
\r
11792 strcpy(buf, "-");
\r
11793 } else if (movesPerSession > 0) {
\r
11794 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11795 } else if (timeIncrement == 0) {
\r
11796 sprintf(buf, "%ld", timeControl/1000);
\r
11798 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11800 return StrSave(buf);
\r
11806 /* This routine is used only for certain modes */
\r
11807 VariantClass v = gameInfo.variant;
\r
11808 ClearGameInfo(&gameInfo);
\r
11809 gameInfo.variant = v;
\r
11811 switch (gameMode) {
\r
11812 case MachinePlaysWhite:
\r
11813 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11814 gameInfo.site = StrSave(HostName());
\r
11815 gameInfo.date = PGNDate();
\r
11816 gameInfo.round = StrSave("-");
\r
11817 gameInfo.white = StrSave(first.tidy);
\r
11818 gameInfo.black = StrSave(UserName());
\r
11819 gameInfo.timeControl = TimeControlTagValue();
\r
11822 case MachinePlaysBlack:
\r
11823 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11824 gameInfo.site = StrSave(HostName());
\r
11825 gameInfo.date = PGNDate();
\r
11826 gameInfo.round = StrSave("-");
\r
11827 gameInfo.white = StrSave(UserName());
\r
11828 gameInfo.black = StrSave(first.tidy);
\r
11829 gameInfo.timeControl = TimeControlTagValue();
\r
11832 case TwoMachinesPlay:
\r
11833 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11834 gameInfo.site = StrSave(HostName());
\r
11835 gameInfo.date = PGNDate();
\r
11836 if (matchGame > 0) {
\r
11837 char buf[MSG_SIZ];
\r
11838 sprintf(buf, "%d", matchGame);
\r
11839 gameInfo.round = StrSave(buf);
\r
11841 gameInfo.round = StrSave("-");
\r
11843 if (first.twoMachinesColor[0] == 'w') {
\r
11844 gameInfo.white = StrSave(first.tidy);
\r
11845 gameInfo.black = StrSave(second.tidy);
\r
11847 gameInfo.white = StrSave(second.tidy);
\r
11848 gameInfo.black = StrSave(first.tidy);
\r
11850 gameInfo.timeControl = TimeControlTagValue();
\r
11854 gameInfo.event = StrSave("Edited game");
\r
11855 gameInfo.site = StrSave(HostName());
\r
11856 gameInfo.date = PGNDate();
\r
11857 gameInfo.round = StrSave("-");
\r
11858 gameInfo.white = StrSave("-");
\r
11859 gameInfo.black = StrSave("-");
\r
11862 case EditPosition:
\r
11863 gameInfo.event = StrSave("Edited position");
\r
11864 gameInfo.site = StrSave(HostName());
\r
11865 gameInfo.date = PGNDate();
\r
11866 gameInfo.round = StrSave("-");
\r
11867 gameInfo.white = StrSave("-");
\r
11868 gameInfo.black = StrSave("-");
\r
11871 case IcsPlayingWhite:
\r
11872 case IcsPlayingBlack:
\r
11873 case IcsObserving:
\r
11874 case IcsExamining:
\r
11877 case PlayFromGameFile:
\r
11878 gameInfo.event = StrSave("Game from non-PGN file");
\r
11879 gameInfo.site = StrSave(HostName());
\r
11880 gameInfo.date = PGNDate();
\r
11881 gameInfo.round = StrSave("-");
\r
11882 gameInfo.white = StrSave("?");
\r
11883 gameInfo.black = StrSave("?");
\r
11892 ReplaceComment(index, text)
\r
11898 while (*text == '\n') text++;
\r
11899 len = strlen(text);
\r
11900 while (len > 0 && text[len - 1] == '\n') len--;
\r
11902 if (commentList[index] != NULL)
\r
11903 free(commentList[index]);
\r
11906 commentList[index] = NULL;
\r
11909 commentList[index] = (char *) malloc(len + 2);
\r
11910 strncpy(commentList[index], text, len);
\r
11911 commentList[index][len] = '\n';
\r
11912 commentList[index][len + 1] = NULLCHAR;
\r
11925 if (ch == '\r') continue;
\r
11927 } while (ch != '\0');
\r
11931 AppendComment(index, text)
\r
11938 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11941 while (*text == '\n') text++;
\r
11942 len = strlen(text);
\r
11943 while (len > 0 && text[len - 1] == '\n') len--;
\r
11945 if (len == 0) return;
\r
11947 if (commentList[index] != NULL) {
\r
11948 old = commentList[index];
\r
11949 oldlen = strlen(old);
\r
11950 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11951 strcpy(commentList[index], old);
\r
11953 strncpy(&commentList[index][oldlen], text, len);
\r
11954 commentList[index][oldlen + len] = '\n';
\r
11955 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11957 commentList[index] = (char *) malloc(len + 2);
\r
11958 strncpy(commentList[index], text, len);
\r
11959 commentList[index][len] = '\n';
\r
11960 commentList[index][len + 1] = NULLCHAR;
\r
11964 static char * FindStr( char * text, char * sub_text )
\r
11966 char * result = strstr( text, sub_text );
\r
11968 if( result != NULL ) {
\r
11969 result += strlen( sub_text );
\r
11975 /* [AS] Try to extract PV info from PGN comment */
\r
11976 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11977 char *GetInfoFromComment( int index, char * text )
\r
11979 char * sep = text;
\r
11981 if( text != NULL && index > 0 ) {
\r
11984 int time = -1, sec = 0, deci;
\r
11985 char * s_eval = FindStr( text, "[%eval " );
\r
11986 char * s_emt = FindStr( text, "[%emt " );
\r
11988 if( s_eval != NULL || s_emt != NULL ) {
\r
11992 if( s_eval != NULL ) {
\r
11993 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11997 if( delim != ']' ) {
\r
12002 if( s_emt != NULL ) {
\r
12006 /* We expect something like: [+|-]nnn.nn/dd */
\r
12007 int score_lo = 0;
\r
12009 sep = strchr( text, '/' );
\r
12010 if( sep == NULL || sep < (text+4) ) {
\r
12014 time = -1; sec = -1; deci = -1;
\r
12015 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12016 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12017 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12018 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12022 if( score_lo < 0 || score_lo >= 100 ) {
\r
12026 if(sec >= 0) time = 600*time + 10*sec; else
\r
12027 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12029 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12031 /* [HGM] PV time: now locate end of PV info */
\r
12032 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12034 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12036 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12038 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12039 while(*sep == ' ') sep++;
\r
12042 if( depth <= 0 ) {
\r
12050 pvInfoList[index-1].depth = depth;
\r
12051 pvInfoList[index-1].score = score;
\r
12052 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12058 SendToProgram(message, cps)
\r
12060 ChessProgramState *cps;
\r
12062 int count, outCount, error;
\r
12063 char buf[MSG_SIZ];
\r
12065 if (cps->pr == NULL) return;
\r
12068 if (appData.debugMode) {
\r
12070 GetTimeMark(&now);
\r
12071 fprintf(debugFP, "%ld >%-6s: %s",
\r
12072 SubtractTimeMarks(&now, &programStartTime),
\r
12073 cps->which, message);
\r
12076 count = strlen(message);
\r
12077 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12078 if (outCount < count && !exiting
\r
12079 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12080 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12081 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12082 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12083 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12084 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12086 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12088 gameInfo.resultDetails = buf;
\r
12090 DisplayFatalError(buf, error, 1);
\r
12095 ReceiveFromProgram(isr, closure, message, count, error)
\r
12096 InputSourceRef isr;
\r
12097 VOIDSTAR closure;
\r
12103 char buf[MSG_SIZ];
\r
12104 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12106 if (isr != cps->isr) return; /* Killed intentionally */
\r
12107 if (count <= 0) {
\r
12108 if (count == 0) {
\r
12110 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12111 cps->which, cps->program);
\r
12112 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12113 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12114 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12115 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12117 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12119 gameInfo.resultDetails = buf;
\r
12121 RemoveInputSource(cps->isr);
\r
12122 DisplayFatalError(buf, 0, 1);
\r
12125 _("Error reading from %s chess program (%s)"),
\r
12126 cps->which, cps->program);
\r
12127 RemoveInputSource(cps->isr);
\r
12129 /* [AS] Program is misbehaving badly... kill it */
\r
12130 if( count == -2 ) {
\r
12131 DestroyChildProcess( cps->pr, 9 );
\r
12132 cps->pr = NoProc;
\r
12135 DisplayFatalError(buf, error, 1);
\r
12140 if ((end_str = strchr(message, '\r')) != NULL)
\r
12141 *end_str = NULLCHAR;
\r
12142 if ((end_str = strchr(message, '\n')) != NULL)
\r
12143 *end_str = NULLCHAR;
\r
12145 if (appData.debugMode) {
\r
12146 TimeMark now; int print = 1;
\r
12147 char *quote = ""; char c; int i;
\r
12149 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12150 char start = message[0];
\r
12151 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12152 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12153 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12154 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12155 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12156 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12157 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12158 { quote = "# "; print = (appData.engineComments == 2); }
\r
12159 message[0] = start; // restore original message
\r
12162 GetTimeMark(&now);
\r
12163 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12164 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12170 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12171 if (appData.icsEngineAnalyze) {
\r
12172 if (strstr(message, "whisper") != NULL ||
\r
12173 strstr(message, "kibitz") != NULL ||
\r
12174 strstr(message, "tellics") != NULL) return;
\r
12177 HandleMachineMove(message, cps);
\r
12182 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12183 ChessProgramState *cps;
\r
12184 int mps, inc, sd, st;
\r
12187 char buf[MSG_SIZ];
\r
12190 if( timeControl_2 > 0 ) {
\r
12191 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12192 tc = timeControl_2;
\r
12195 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12196 inc /= cps->timeOdds;
\r
12197 st /= cps->timeOdds;
\r
12199 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12202 /* Set exact time per move, normally using st command */
\r
12203 if (cps->stKludge) {
\r
12204 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12205 seconds = st % 60;
\r
12206 if (seconds == 0) {
\r
12207 sprintf(buf, "level 1 %d\n", st/60);
\r
12209 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12212 sprintf(buf, "st %d\n", st);
\r
12215 /* Set conventional or incremental time control, using level command */
\r
12216 if (seconds == 0) {
\r
12217 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12218 Fixed in later versions, but still avoid :seconds
\r
12219 when seconds is 0. */
\r
12220 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12222 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12223 seconds, inc/1000);
\r
12226 SendToProgram(buf, cps);
\r
12228 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12229 /* Orthogonally, limit search to given depth */
\r
12231 if (cps->sdKludge) {
\r
12232 sprintf(buf, "depth\n%d\n", sd);
\r
12234 sprintf(buf, "sd %d\n", sd);
\r
12236 SendToProgram(buf, cps);
\r
12239 if(cps->nps > 0) { /* [HGM] nps */
\r
12240 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12242 sprintf(buf, "nps %d\n", cps->nps);
\r
12243 SendToProgram(buf, cps);
\r
12248 ChessProgramState *WhitePlayer()
\r
12249 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12251 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12252 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12258 SendTimeRemaining(cps, machineWhite)
\r
12259 ChessProgramState *cps;
\r
12260 int /*boolean*/ machineWhite;
\r
12262 char message[MSG_SIZ];
\r
12263 long time, otime;
\r
12265 /* Note: this routine must be called when the clocks are stopped
\r
12266 or when they have *just* been set or switched; otherwise
\r
12267 it will be off by the time since the current tick started.
\r
12269 if (machineWhite) {
\r
12270 time = whiteTimeRemaining / 10;
\r
12271 otime = blackTimeRemaining / 10;
\r
12273 time = blackTimeRemaining / 10;
\r
12274 otime = whiteTimeRemaining / 10;
\r
12276 /* [HGM] translate opponent's time by time-odds factor */
\r
12277 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12278 if (appData.debugMode) {
\r
12279 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12282 if (time <= 0) time = 1;
\r
12283 if (otime <= 0) otime = 1;
\r
12285 sprintf(message, "time %ld\n", time);
\r
12286 SendToProgram(message, cps);
\r
12288 sprintf(message, "otim %ld\n", otime);
\r
12289 SendToProgram(message, cps);
\r
12293 BoolFeature(p, name, loc, cps)
\r
12297 ChessProgramState *cps;
\r
12299 char buf[MSG_SIZ];
\r
12300 int len = strlen(name);
\r
12302 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12304 sscanf(*p, "%d", &val);
\r
12305 *loc = (val != 0);
\r
12306 while (**p && **p != ' ') (*p)++;
\r
12307 sprintf(buf, "accepted %s\n", name);
\r
12308 SendToProgram(buf, cps);
\r
12315 IntFeature(p, name, loc, cps)
\r
12319 ChessProgramState *cps;
\r
12321 char buf[MSG_SIZ];
\r
12322 int len = strlen(name);
\r
12323 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12325 sscanf(*p, "%d", loc);
\r
12326 while (**p && **p != ' ') (*p)++;
\r
12327 sprintf(buf, "accepted %s\n", name);
\r
12328 SendToProgram(buf, cps);
\r
12335 StringFeature(p, name, loc, cps)
\r
12339 ChessProgramState *cps;
\r
12341 char buf[MSG_SIZ];
\r
12342 int len = strlen(name);
\r
12343 if (strncmp((*p), name, len) == 0
\r
12344 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12346 sscanf(*p, "%[^\"]", loc);
\r
12347 while (**p && **p != '\"') (*p)++;
\r
12348 if (**p == '\"') (*p)++;
\r
12349 sprintf(buf, "accepted %s\n", name);
\r
12350 SendToProgram(buf, cps);
\r
12357 ParseOption(Option *opt, ChessProgramState *cps)
\r
12358 // [HGM] options: process the string that defines an engine option, and determine
\r
12359 // name, type, default value, and allowed value range
\r
12361 char *p, *q, buf[MSG_SIZ];
\r
12362 int n, min = (-1)<<31, max = 1<<31, def;
\r
12364 if(p = strstr(opt->name, " -spin ")) {
\r
12365 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12366 if(max < min) max = min; // enforce consistency
\r
12367 if(def < min) def = min;
\r
12368 if(def > max) def = max;
\r
12369 opt->value = def;
\r
12372 opt->type = Spin;
\r
12373 } else if(p = strstr(opt->name, " -string ")) {
\r
12374 opt->textValue = p+9;
\r
12375 opt->type = TextBox;
\r
12376 } else if(p = strstr(opt->name, " -check ")) {
\r
12377 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12378 opt->value = (def != 0);
\r
12379 opt->type = CheckBox;
\r
12380 } else if(p = strstr(opt->name, " -combo ")) {
\r
12381 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12382 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12383 opt->value = n = 0;
\r
12384 while(q = StrStr(q, " /// ")) {
\r
12385 n++; *q = 0; // count choices, and null-terminate each of them
\r
12387 if(*q == '*') { // remember default, which is marked with * prefix
\r
12391 cps->comboList[cps->comboCnt++] = q;
\r
12393 cps->comboList[cps->comboCnt++] = NULL;
\r
12394 opt->max = n + 1;
\r
12395 opt->type = ComboBox;
\r
12396 } else if(p = strstr(opt->name, " -button")) {
\r
12397 opt->type = Button;
\r
12398 } else if(p = strstr(opt->name, " -save")) {
\r
12399 opt->type = SaveButton;
\r
12400 } else return FALSE;
\r
12401 *p = 0; // terminate option name
\r
12402 // now look if the command-line options define a setting for this engine option.
\r
12403 p = strstr(cps->optionSettings, opt->name);
\r
12404 if(p == cps->optionSettings || p[-1] == ',') {
\r
12405 sprintf(buf, "option %s", p);
\r
12406 if(p = strstr(buf, ",")) *p = 0;
\r
12407 strcat(buf, "\n");
\r
12408 SendToProgram(buf, cps);
\r
12414 FeatureDone(cps, val)
\r
12415 ChessProgramState* cps;
\r
12418 DelayedEventCallback cb = GetDelayedEvent();
\r
12419 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12420 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12421 CancelDelayedEvent();
\r
12422 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12424 cps->initDone = val;
\r
12427 /* Parse feature command from engine */
\r
12429 ParseFeatures(args, cps)
\r
12431 ChessProgramState *cps;
\r
12436 char buf[MSG_SIZ];
\r
12439 while (*p == ' ') p++;
\r
12440 if (*p == NULLCHAR) return;
\r
12442 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12443 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12444 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12445 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12446 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12447 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12448 /* Engine can disable reuse, but can't enable it if user said no */
\r
12449 if (!val) cps->reuse = FALSE;
\r
12452 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12453 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12454 if (gameMode == TwoMachinesPlay) {
\r
12455 DisplayTwoMachinesTitle();
\r
12457 DisplayTitle("");
\r
12461 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12462 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12463 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12464 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12465 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12466 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12467 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12468 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12469 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12470 if (IntFeature(&p, "done", &val, cps)) {
\r
12471 FeatureDone(cps, val);
\r
12474 /* Added by Tord: */
\r
12475 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12476 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12477 /* End of additions by Tord */
\r
12479 /* [HGM] added features: */
\r
12480 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12481 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12482 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12483 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12484 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12485 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12486 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12487 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12488 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12489 cps->nrOptions--;
\r
12490 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12491 DisplayError(buf, 0);
\r
12495 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12496 /* End of additions by HGM */
\r
12498 /* unknown feature: complain and skip */
\r
12500 while (*q && *q != '=') q++;
\r
12501 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12502 SendToProgram(buf, cps);
\r
12506 if (*p == '\"') {
\r
12508 while (*p && *p != '\"') p++;
\r
12509 if (*p == '\"') p++;
\r
12511 while (*p && *p != ' ') p++;
\r
12519 PeriodicUpdatesEvent(newState)
\r
12522 if (newState == appData.periodicUpdates)
\r
12525 appData.periodicUpdates=newState;
\r
12527 /* Display type changes, so update it now */
\r
12528 DisplayAnalysis();
\r
12530 /* Get the ball rolling again... */
\r
12532 AnalysisPeriodicEvent(1);
\r
12533 StartAnalysisClock();
\r
12538 PonderNextMoveEvent(newState)
\r
12541 if (newState == appData.ponderNextMove) return;
\r
12542 if (gameMode == EditPosition) EditPositionDone();
\r
12544 SendToProgram("hard\n", &first);
\r
12545 if (gameMode == TwoMachinesPlay) {
\r
12546 SendToProgram("hard\n", &second);
\r
12549 SendToProgram("easy\n", &first);
\r
12550 thinkOutput[0] = NULLCHAR;
\r
12551 if (gameMode == TwoMachinesPlay) {
\r
12552 SendToProgram("easy\n", &second);
\r
12555 appData.ponderNextMove = newState;
\r
12559 NewSettingEvent(option, command, value)
\r
12561 int option, value;
\r
12563 char buf[MSG_SIZ];
\r
12565 if (gameMode == EditPosition) EditPositionDone();
\r
12566 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12567 SendToProgram(buf, &first);
\r
12568 if (gameMode == TwoMachinesPlay) {
\r
12569 SendToProgram(buf, &second);
\r
12574 ShowThinkingEvent()
\r
12575 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12577 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12578 int newState = appData.showThinking
\r
12579 // [HGM] thinking: other features now need thinking output as well
\r
12580 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12582 if (oldState == newState) return;
\r
12583 oldState = newState;
\r
12584 if (gameMode == EditPosition) EditPositionDone();
\r
12586 SendToProgram("post\n", &first);
\r
12587 if (gameMode == TwoMachinesPlay) {
\r
12588 SendToProgram("post\n", &second);
\r
12591 SendToProgram("nopost\n", &first);
\r
12592 thinkOutput[0] = NULLCHAR;
\r
12593 if (gameMode == TwoMachinesPlay) {
\r
12594 SendToProgram("nopost\n", &second);
\r
12597 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12601 AskQuestionEvent(title, question, replyPrefix, which)
\r
12602 char *title; char *question; char *replyPrefix; char *which;
\r
12604 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12605 if (pr == NoProc) return;
\r
12606 AskQuestion(title, question, replyPrefix, pr);
\r
12610 DisplayMove(moveNumber)
\r
12613 char message[MSG_SIZ];
\r
12614 char res[MSG_SIZ];
\r
12615 char cpThinkOutput[MSG_SIZ];
\r
12617 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12619 if (moveNumber == forwardMostMove - 1 ||
\r
12620 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12622 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12624 if (strchr(cpThinkOutput, '\n')) {
\r
12625 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12628 *cpThinkOutput = NULLCHAR;
\r
12631 /* [AS] Hide thinking from human user */
\r
12632 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12633 *cpThinkOutput = NULLCHAR;
\r
12634 if( thinkOutput[0] != NULLCHAR ) {
\r
12637 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12638 cpThinkOutput[i] = '.';
\r
12640 cpThinkOutput[i] = NULLCHAR;
\r
12641 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12645 if (moveNumber == forwardMostMove - 1 &&
\r
12646 gameInfo.resultDetails != NULL) {
\r
12647 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12648 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12650 sprintf(res, " {%s} %s",
\r
12651 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12654 res[0] = NULLCHAR;
\r
12657 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12658 DisplayMessage(res, cpThinkOutput);
\r
12660 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12661 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12662 parseList[moveNumber], res);
\r
12663 DisplayMessage(message, cpThinkOutput);
\r
12668 DisplayAnalysisText(text)
\r
12671 char buf[MSG_SIZ];
\r
12673 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12674 || appData.icsEngineAnalyze) {
\r
12675 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12676 AnalysisPopUp(buf, text);
\r
12681 only_one_move(str)
\r
12684 while (*str && isspace(*str)) ++str;
\r
12685 while (*str && !isspace(*str)) ++str;
\r
12686 if (!*str) return 1;
\r
12687 while (*str && isspace(*str)) ++str;
\r
12688 if (!*str) return 1;
\r
12693 DisplayAnalysis()
\r
12695 char buf[MSG_SIZ];
\r
12696 char lst[MSG_SIZ / 2];
\r
12698 static char *xtra[] = { "", " (--)", " (++)" };
\r
12701 if (programStats.time == 0) {
\r
12702 programStats.time = 1;
\r
12705 if (programStats.got_only_move) {
\r
12706 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12708 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12710 nps = (u64ToDouble(programStats.nodes) /
\r
12711 ((double)programStats.time /100.0));
\r
12713 cs = programStats.time % 100;
\r
12714 s = programStats.time / 100;
\r
12715 h = (s / (60*60));
\r
12720 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12721 if (programStats.move_name[0] != NULLCHAR) {
\r
12722 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12723 programStats.depth,
\r
12724 programStats.nr_moves-programStats.moves_left,
\r
12725 programStats.nr_moves, programStats.move_name,
\r
12726 ((float)programStats.score)/100.0, lst,
\r
12727 only_one_move(lst)?
\r
12728 xtra[programStats.got_fail] : "",
\r
12729 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12731 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12732 programStats.depth,
\r
12733 programStats.nr_moves-programStats.moves_left,
\r
12734 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12736 only_one_move(lst)?
\r
12737 xtra[programStats.got_fail] : "",
\r
12738 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12741 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12742 programStats.depth,
\r
12743 ((float)programStats.score)/100.0,
\r
12745 only_one_move(lst)?
\r
12746 xtra[programStats.got_fail] : "",
\r
12747 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12750 DisplayAnalysisText(buf);
\r
12754 DisplayComment(moveNumber, text)
\r
12758 char title[MSG_SIZ];
\r
12759 char buf[8000]; // comment can be long!
\r
12760 int score, depth;
\r
12762 if( appData.autoDisplayComment ) {
\r
12763 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12764 strcpy(title, "Comment");
\r
12766 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12767 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12768 parseList[moveNumber]);
\r
12770 } else title[0] = 0;
\r
12772 // [HGM] PV info: display PV info together with (or as) comment
\r
12773 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12774 if(text == NULL) text = "";
\r
12775 score = pvInfoList[moveNumber].score;
\r
12776 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12777 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12778 CommentPopUp(title, buf);
\r
12780 if (text != NULL)
\r
12781 CommentPopUp(title, text);
\r
12784 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12785 * might be busy thinking or pondering. It can be omitted if your
\r
12786 * gnuchess is configured to stop thinking immediately on any user
\r
12787 * input. However, that gnuchess feature depends on the FIONREAD
\r
12788 * ioctl, which does not work properly on some flavors of Unix.
\r
12792 ChessProgramState *cps;
\r
12795 if (!cps->useSigint) return;
\r
12796 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12797 switch (gameMode) {
\r
12798 case MachinePlaysWhite:
\r
12799 case MachinePlaysBlack:
\r
12800 case TwoMachinesPlay:
\r
12801 case IcsPlayingWhite:
\r
12802 case IcsPlayingBlack:
\r
12803 case AnalyzeMode:
\r
12804 case AnalyzeFile:
\r
12805 /* Skip if we know it isn't thinking */
\r
12806 if (!cps->maybeThinking) return;
\r
12807 if (appData.debugMode)
\r
12808 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12809 InterruptChildProcess(cps->pr);
\r
12810 cps->maybeThinking = FALSE;
\r
12815 #endif /*ATTENTION*/
\r
12821 if (whiteTimeRemaining <= 0) {
\r
12822 if (!whiteFlag) {
\r
12823 whiteFlag = TRUE;
\r
12824 if (appData.icsActive) {
\r
12825 if (appData.autoCallFlag &&
\r
12826 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12827 SendToICS(ics_prefix);
\r
12828 SendToICS("flag\n");
\r
12832 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12834 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12835 if (appData.autoCallFlag) {
\r
12836 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12843 if (blackTimeRemaining <= 0) {
\r
12844 if (!blackFlag) {
\r
12845 blackFlag = TRUE;
\r
12846 if (appData.icsActive) {
\r
12847 if (appData.autoCallFlag &&
\r
12848 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12849 SendToICS(ics_prefix);
\r
12850 SendToICS("flag\n");
\r
12854 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12856 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12857 if (appData.autoCallFlag) {
\r
12858 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12869 CheckTimeControl()
\r
12871 if (!appData.clockMode || appData.icsActive ||
\r
12872 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12875 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12877 if ( !WhiteOnMove(forwardMostMove) )
\r
12878 /* White made time control */
\r
12879 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12880 /* [HGM] time odds: correct new time quota for time odds! */
\r
12881 / WhitePlayer()->timeOdds;
\r
12883 /* Black made time control */
\r
12884 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12885 / WhitePlayer()->other->timeOdds;
\r
12889 DisplayBothClocks()
\r
12891 int wom = gameMode == EditPosition ?
\r
12892 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12893 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12894 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12898 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12899 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12900 to use other calls if you don't. Clocks will be less accurate if
\r
12901 you have neither ftime nor gettimeofday.
\r
12904 /* VS 2008 requires the #include outside of the function */
\r
12905 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12906 #include <sys/timeb.h>
\r
12909 /* Get the current time as a TimeMark */
\r
12914 #if HAVE_GETTIMEOFDAY
\r
12916 struct timeval timeVal;
\r
12917 struct timezone timeZone;
\r
12919 gettimeofday(&timeVal, &timeZone);
\r
12920 tm->sec = (long) timeVal.tv_sec;
\r
12921 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12923 #else /*!HAVE_GETTIMEOFDAY*/
\r
12926 // include <sys/timeb.h> / moved to just above start of function
\r
12927 struct timeb timeB;
\r
12930 tm->sec = (long) timeB.time;
\r
12931 tm->ms = (int) timeB.millitm;
\r
12933 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12934 tm->sec = (long) time(NULL);
\r
12940 /* Return the difference in milliseconds between two
\r
12941 time marks. We assume the difference will fit in a long!
\r
12944 SubtractTimeMarks(tm2, tm1)
\r
12945 TimeMark *tm2, *tm1;
\r
12947 return 1000L*(tm2->sec - tm1->sec) +
\r
12948 (long) (tm2->ms - tm1->ms);
\r
12953 * Code to manage the game clocks.
\r
12955 * In tournament play, black starts the clock and then white makes a move.
\r
12956 * We give the human user a slight advantage if he is playing white---the
\r
12957 * clocks don't run until he makes his first move, so it takes zero time.
\r
12958 * Also, we don't account for network lag, so we could get out of sync
\r
12959 * with GNU Chess's clock -- but then, referees are always right.
\r
12962 static TimeMark tickStartTM;
\r
12963 static long intendedTickLength;
\r
12966 NextTickLength(timeRemaining)
\r
12967 long timeRemaining;
\r
12969 long nominalTickLength, nextTickLength;
\r
12971 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12972 nominalTickLength = 100L;
\r
12974 nominalTickLength = 1000L;
\r
12975 nextTickLength = timeRemaining % nominalTickLength;
\r
12976 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12978 return nextTickLength;
\r
12981 /* Adjust clock one minute up or down */
\r
12983 AdjustClock(Boolean which, int dir)
\r
12985 if(which) blackTimeRemaining += 60000*dir;
\r
12986 else whiteTimeRemaining += 60000*dir;
\r
12987 DisplayBothClocks();
\r
12990 /* Stop clocks and reset to a fresh time control */
\r
12994 (void) StopClockTimer();
\r
12995 if (appData.icsActive) {
\r
12996 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12997 } else { /* [HGM] correct new time quote for time odds */
\r
12998 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12999 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
13001 if (whiteFlag || blackFlag) {
\r
13002 DisplayTitle("");
\r
13003 whiteFlag = blackFlag = FALSE;
\r
13005 DisplayBothClocks();
\r
13008 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13010 /* Decrement running clock by amount of time that has passed */
\r
13012 DecrementClocks()
\r
13014 long timeRemaining;
\r
13015 long lastTickLength, fudge;
\r
13018 if (!appData.clockMode) return;
\r
13019 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13021 GetTimeMark(&now);
\r
13023 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13025 /* Fudge if we woke up a little too soon */
\r
13026 fudge = intendedTickLength - lastTickLength;
\r
13027 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13029 if (WhiteOnMove(forwardMostMove)) {
\r
13030 if(whiteNPS >= 0) lastTickLength = 0;
\r
13031 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13032 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13033 WhiteOnMove(currentMove));
\r
13035 if(blackNPS >= 0) lastTickLength = 0;
\r
13036 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13037 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13038 !WhiteOnMove(currentMove));
\r
13041 if (CheckFlags()) return;
\r
13043 tickStartTM = now;
\r
13044 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13045 StartClockTimer(intendedTickLength);
\r
13047 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13048 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13049 * with increment) the time remaining has increased to a level above the
\r
13050 * threshold, reset the alarm so it can sound again.
\r
13053 if (appData.icsActive && appData.icsAlarm) {
\r
13055 /* make sure we are dealing with the user's clock */
\r
13056 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13057 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13060 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13061 alarmSounded = FALSE;
\r
13062 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13063 PlayAlarmSound();
\r
13064 alarmSounded = TRUE;
\r
13070 /* A player has just moved, so stop the previously running
\r
13071 clock and (if in clock mode) start the other one.
\r
13072 We redisplay both clocks in case we're in ICS mode, because
\r
13073 ICS gives us an update to both clocks after every move.
\r
13074 Note that this routine is called *after* forwardMostMove
\r
13075 is updated, so the last fractional tick must be subtracted
\r
13076 from the color that is *not* on move now.
\r
13081 long lastTickLength;
\r
13083 int flagged = FALSE;
\r
13085 GetTimeMark(&now);
\r
13087 if (StopClockTimer() && appData.clockMode) {
\r
13088 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13089 if (WhiteOnMove(forwardMostMove)) {
\r
13090 if(blackNPS >= 0) lastTickLength = 0;
\r
13091 blackTimeRemaining -= lastTickLength;
\r
13092 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13093 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13094 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13095 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13097 if(whiteNPS >= 0) lastTickLength = 0;
\r
13098 whiteTimeRemaining -= lastTickLength;
\r
13099 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13100 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13101 pvInfoList[forwardMostMove-1].time =
\r
13102 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13104 flagged = CheckFlags();
\r
13106 CheckTimeControl();
\r
13108 if (flagged || !appData.clockMode) return;
\r
13110 switch (gameMode) {
\r
13111 case MachinePlaysBlack:
\r
13112 case MachinePlaysWhite:
\r
13113 case BeginningOfGame:
\r
13114 if (pausing) return;
\r
13118 case PlayFromGameFile:
\r
13119 case IcsExamining:
\r
13126 tickStartTM = now;
\r
13127 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13128 whiteTimeRemaining : blackTimeRemaining);
\r
13129 StartClockTimer(intendedTickLength);
\r
13133 /* Stop both clocks */
\r
13137 long lastTickLength;
\r
13140 if (!StopClockTimer()) return;
\r
13141 if (!appData.clockMode) return;
\r
13143 GetTimeMark(&now);
\r
13145 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13146 if (WhiteOnMove(forwardMostMove)) {
\r
13147 if(whiteNPS >= 0) lastTickLength = 0;
\r
13148 whiteTimeRemaining -= lastTickLength;
\r
13149 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13151 if(blackNPS >= 0) lastTickLength = 0;
\r
13152 blackTimeRemaining -= lastTickLength;
\r
13153 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13158 /* Start clock of player on move. Time may have been reset, so
\r
13159 if clock is already running, stop and restart it. */
\r
13163 (void) StopClockTimer(); /* in case it was running already */
\r
13164 DisplayBothClocks();
\r
13165 if (CheckFlags()) return;
\r
13167 if (!appData.clockMode) return;
\r
13168 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13170 GetTimeMark(&tickStartTM);
\r
13171 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13172 whiteTimeRemaining : blackTimeRemaining);
\r
13174 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13175 whiteNPS = blackNPS = -1;
\r
13176 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13177 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13178 whiteNPS = first.nps;
\r
13179 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13180 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13181 blackNPS = first.nps;
\r
13182 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13183 whiteNPS = second.nps;
\r
13184 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13185 blackNPS = second.nps;
\r
13186 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13188 StartClockTimer(intendedTickLength);
\r
13195 long second, minute, hour, day;
\r
13197 static char buf[32];
\r
13199 if (ms > 0 && ms <= 9900) {
\r
13200 /* convert milliseconds to tenths, rounding up */
\r
13201 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13203 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13207 /* convert milliseconds to seconds, rounding up */
\r
13208 /* use floating point to avoid strangeness of integer division
\r
13209 with negative dividends on many machines */
\r
13210 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13212 if (second < 0) {
\r
13214 second = -second;
\r
13217 day = second / (60 * 60 * 24);
\r
13218 second = second % (60 * 60 * 24);
\r
13219 hour = second / (60 * 60);
\r
13220 second = second % (60 * 60);
\r
13221 minute = second / 60;
\r
13222 second = second % 60;
\r
13225 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13226 sign, day, hour, minute, second);
\r
13227 else if (hour > 0)
\r
13228 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13230 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13237 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13240 StrStr(string, match)
\r
13241 char *string, *match;
\r
13245 length = strlen(match);
\r
13247 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13248 if (!strncmp(match, string, length))
\r
13255 StrCaseStr(string, match)
\r
13256 char *string, *match;
\r
13258 int i, j, length;
\r
13260 length = strlen(match);
\r
13262 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13263 for (j = 0; j < length; j++) {
\r
13264 if (ToLower(match[j]) != ToLower(string[j]))
\r
13267 if (j == length) return string;
\r
13273 #ifndef _amigados
\r
13275 StrCaseCmp(s1, s2)
\r
13281 c1 = ToLower(*s1++);
\r
13282 c2 = ToLower(*s2++);
\r
13283 if (c1 > c2) return 1;
\r
13284 if (c1 < c2) return -1;
\r
13285 if (c1 == NULLCHAR) return 0;
\r
13294 return isupper(c) ? tolower(c) : c;
\r
13302 return islower(c) ? toupper(c) : c;
\r
13304 #endif /* !_amigados */
\r
13312 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13319 StrSavePtr(s, savePtr)
\r
13320 char *s, **savePtr;
\r
13325 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13326 strcpy(*savePtr, s);
\r
13328 return(*savePtr);
\r
13336 char buf[MSG_SIZ];
\r
13338 clock = time((time_t *)NULL);
\r
13339 tm = localtime(&clock);
\r
13340 sprintf(buf, "%04d.%02d.%02d",
\r
13341 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13342 return StrSave(buf);
\r
13347 PositionToFEN(move, useFEN960)
\r
13351 int i, j, fromX, fromY, toX, toY;
\r
13356 ChessSquare piece;
\r
13358 whiteToPlay = (gameMode == EditPosition) ?
\r
13359 !blackPlaysFirst : (move % 2 == 0);
\r
13362 /* Piece placement data */
\r
13363 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13365 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13366 if (boards[move][i][j] == EmptySquare) {
\r
13368 } else { ChessSquare piece = boards[move][i][j];
\r
13369 if (emptycount > 0) {
\r
13370 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13371 *p++ = '0' + emptycount;
\r
13372 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13375 if(PieceToChar(piece) == '+') {
\r
13376 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13378 piece = (ChessSquare)(DEMOTED piece);
\r
13380 *p++ = PieceToChar(piece);
\r
13381 if(p[-1] == '~') {
\r
13382 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13383 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13388 if (emptycount > 0) {
\r
13389 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13390 *p++ = '0' + emptycount;
\r
13391 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13398 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13399 if( gameInfo.holdingsWidth ) {
\r
13400 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13402 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13403 piece = boards[move][i][BOARD_WIDTH-1];
\r
13404 if( piece != EmptySquare )
\r
13405 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13406 *p++ = PieceToChar(piece);
\r
13408 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13409 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13410 if( piece != EmptySquare )
\r
13411 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13412 *p++ = PieceToChar(piece);
\r
13415 if( q == p ) *p++ = '-';
\r
13420 /* Active color */
\r
13421 *p++ = whiteToPlay ? 'w' : 'b';
\r
13424 if(nrCastlingRights) {
\r
13426 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13427 /* [HGM] write directly from rights */
\r
13428 if(castlingRights[move][2] >= 0 &&
\r
13429 castlingRights[move][0] >= 0 )
\r
13430 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13431 if(castlingRights[move][2] >= 0 &&
\r
13432 castlingRights[move][1] >= 0 )
\r
13433 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13434 if(castlingRights[move][5] >= 0 &&
\r
13435 castlingRights[move][3] >= 0 )
\r
13436 *p++ = castlingRights[move][3] + AAA;
\r
13437 if(castlingRights[move][5] >= 0 &&
\r
13438 castlingRights[move][4] >= 0 )
\r
13439 *p++ = castlingRights[move][4] + AAA;
\r
13442 /* [HGM] write true castling rights */
\r
13443 if( nrCastlingRights == 6 ) {
\r
13444 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13445 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13446 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13447 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13448 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13449 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13450 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13451 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13454 if (q == p) *p++ = '-'; /* No castling rights */
\r
13458 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13459 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13460 /* En passant target square */
\r
13461 if (move > backwardMostMove) {
\r
13462 fromX = moveList[move - 1][0] - AAA;
\r
13463 fromY = moveList[move - 1][1] - ONE;
\r
13464 toX = moveList[move - 1][2] - AAA;
\r
13465 toY = moveList[move - 1][3] - ONE;
\r
13466 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13467 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13468 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13470 /* 2-square pawn move just happened */
\r
13471 *p++ = toX + AAA;
\r
13472 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13482 /* [HGM] find reversible plies */
\r
13483 { int i = 0, j=move;
\r
13485 if (appData.debugMode) { int k;
\r
13486 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13487 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13488 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13492 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13493 if( j == backwardMostMove ) i += initialRulePlies;
\r
13494 sprintf(p, "%d ", i);
\r
13495 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13497 /* Fullmove number */
\r
13498 sprintf(p, "%d", (move / 2) + 1);
\r
13500 return StrSave(buf);
\r
13504 ParseFEN(board, blackPlaysFirst, fen)
\r
13506 int *blackPlaysFirst;
\r
13512 ChessSquare piece;
\r
13516 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13517 if(gameInfo.holdingsWidth) {
\r
13518 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13519 board[i][0] = EmptySquare; /* black holdings */
\r
13520 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13521 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13522 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13526 /* Piece placement data */
\r
13527 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13530 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13531 if (*p == '/') p++;
\r
13532 emptycount = gameInfo.boardWidth - j;
\r
13533 while (emptycount--)
\r
13534 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13536 #if(BOARD_SIZE >= 10)
\r
13537 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13538 p++; emptycount=10;
\r
13539 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13540 while (emptycount--)
\r
13541 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13543 } else if (isdigit(*p)) {
\r
13544 emptycount = *p++ - '0';
\r
13545 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13546 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13547 while (emptycount--)
\r
13548 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13549 } else if (*p == '+' || isalpha(*p)) {
\r
13550 if (j >= gameInfo.boardWidth) return FALSE;
\r
13552 piece = CharToPiece(*++p);
\r
13553 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13554 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13555 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13556 } else piece = CharToPiece(*p++);
\r
13558 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13559 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13560 piece = (ChessSquare) (PROMOTED piece);
\r
13561 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13564 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13570 while (*p == '/' || *p == ' ') p++;
\r
13572 /* [HGM] look for Crazyhouse holdings here */
\r
13573 while(*p==' ') p++;
\r
13574 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13575 if(*p == '[') p++;
\r
13576 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13577 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13578 /* if we would allow FEN reading to set board size, we would */
\r
13579 /* have to add holdings and shift the board read so far here */
\r
13580 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13582 if((int) piece >= (int) BlackPawn ) {
\r
13583 i = (int)piece - (int)BlackPawn;
\r
13584 i = PieceToNumber((ChessSquare)i);
\r
13585 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13586 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13587 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13589 i = (int)piece - (int)WhitePawn;
\r
13590 i = PieceToNumber((ChessSquare)i);
\r
13591 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13592 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13593 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13597 if(*p == ']') *p++;
\r
13600 while(*p == ' ') p++;
\r
13602 /* Active color */
\r
13605 *blackPlaysFirst = FALSE;
\r
13608 *blackPlaysFirst = TRUE;
\r
13614 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13615 /* return the extra info in global variiables */
\r
13617 /* set defaults in case FEN is incomplete */
\r
13618 FENepStatus = EP_UNKNOWN;
\r
13619 for(i=0; i<nrCastlingRights; i++ ) {
\r
13620 FENcastlingRights[i] =
\r
13621 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13622 } /* assume possible unless obviously impossible */
\r
13623 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13624 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13625 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13626 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13627 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13628 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13629 FENrulePlies = 0;
\r
13631 while(*p==' ') p++;
\r
13632 if(nrCastlingRights) {
\r
13633 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13634 /* castling indicator present, so default becomes no castlings */
\r
13635 for(i=0; i<nrCastlingRights; i++ ) {
\r
13636 FENcastlingRights[i] = -1;
\r
13639 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13640 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13641 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13642 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13643 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13645 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13646 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13647 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13651 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13652 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13653 FENcastlingRights[2] = whiteKingFile;
\r
13656 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13657 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13658 FENcastlingRights[2] = whiteKingFile;
\r
13661 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13662 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13663 FENcastlingRights[5] = blackKingFile;
\r
13666 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13667 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13668 FENcastlingRights[5] = blackKingFile;
\r
13671 default: /* FRC castlings */
\r
13672 if(c >= 'a') { /* black rights */
\r
13673 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13674 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13675 if(i == BOARD_RGHT) break;
\r
13676 FENcastlingRights[5] = i;
\r
13678 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13679 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13681 FENcastlingRights[3] = c;
\r
13683 FENcastlingRights[4] = c;
\r
13684 } else { /* white rights */
\r
13685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13686 if(board[0][i] == WhiteKing) break;
\r
13687 if(i == BOARD_RGHT) break;
\r
13688 FENcastlingRights[2] = i;
\r
13689 c -= AAA - 'a' + 'A';
\r
13690 if(board[0][c] >= WhiteKing) break;
\r
13692 FENcastlingRights[0] = c;
\r
13694 FENcastlingRights[1] = c;
\r
13698 if (appData.debugMode) {
\r
13699 fprintf(debugFP, "FEN castling rights:");
\r
13700 for(i=0; i<nrCastlingRights; i++)
\r
13701 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13702 fprintf(debugFP, "\n");
\r
13705 while(*p==' ') p++;
\r
13708 /* read e.p. field in games that know e.p. capture */
\r
13709 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13710 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13712 p++; FENepStatus = EP_NONE;
\r
13714 char c = *p++ - AAA;
\r
13716 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13717 if(*p >= '0' && *p <='9') *p++;
\r
13723 if(sscanf(p, "%d", &i) == 1) {
\r
13724 FENrulePlies = i; /* 50-move ply counter */
\r
13725 /* (The move number is still ignored) */
\r
13732 EditPositionPasteFEN(char *fen)
\r
13734 if (fen != NULL) {
\r
13735 Board initial_position;
\r
13737 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13738 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13741 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13742 EditPositionEvent();
\r
13743 blackPlaysFirst = savedBlackPlaysFirst;
\r
13744 CopyBoard(boards[0], initial_position);
\r
13745 /* [HGM] copy FEN attributes as well */
\r
13747 initialRulePlies = FENrulePlies;
\r
13748 epStatus[0] = FENepStatus;
\r
13749 for( i=0; i<nrCastlingRights; i++ )
\r
13750 castlingRights[0][i] = FENcastlingRights[i];
\r
13752 EditPositionDone();
\r
13753 DisplayBothClocks();
\r
13754 DrawPosition(FALSE, boards[currentMove]);
\r