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 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5886 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5887 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5888 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5890 /* always flag draws, for judging claims */
\r
5891 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5893 if(appData.materialDraws) {
\r
5894 /* but only adjudicate them if adjudication enabled */
\r
5895 SendToProgram("force\n", cps->other); // suppress reply
\r
5896 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5897 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5898 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5903 /* Shatranj baring rule */
\r
5904 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5907 if(--bare < 0 && appData.checkMates) {
\r
5908 /* but only adjudicate them if adjudication enabled */
\r
5909 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5910 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5911 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5912 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5917 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5918 if(NrPieces == 4 &&
\r
5919 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5920 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5921 || NrWN==2 || NrBN==2 /* KNNK */
\r
5922 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5924 if(--moveCount < 0 && appData.trivialDraws)
\r
5925 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5926 SendToProgram("force\n", cps->other); // suppress reply
\r
5927 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5928 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5929 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5932 } else moveCount = 6;
\r
5936 if (appData.debugMode) { int i;
\r
5937 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5938 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5939 appData.drawRepeats);
\r
5940 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5941 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5945 /* Check for rep-draws */
\r
5947 for(k = forwardMostMove-2;
\r
5948 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5949 epStatus[k] < EP_UNKNOWN &&
\r
5950 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5954 if (appData.debugMode) {
\r
5955 fprintf(debugFP, " loop\n");
\r
5958 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5960 if (appData.debugMode) {
\r
5961 fprintf(debugFP, "match\n");
\r
5964 /* compare castling rights */
\r
5965 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5966 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5967 rights++; /* King lost rights, while rook still had them */
\r
5968 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5969 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5970 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5971 rights++; /* but at least one rook lost them */
\r
5973 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5974 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5976 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5977 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5978 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5982 if (appData.debugMode) {
\r
5983 for(i=0; i<nrCastlingRights; i++)
\r
5984 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5987 if (appData.debugMode) {
\r
5988 fprintf(debugFP, " %d %d\n", rights, k);
\r
5991 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5992 && appData.drawRepeats > 1) {
\r
5993 /* adjudicate after user-specified nr of repeats */
\r
5994 SendToProgram("force\n", cps->other); // suppress reply
\r
5995 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5997 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5998 // [HGM] xiangqi: check for forbidden perpetuals
\r
5999 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
6000 for(m=forwardMostMove; m>k; m-=2) {
\r
6001 if(MateTest(boards[m], PosFlags(m),
\r
6002 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
6003 ourPerpetual = 0; // the current mover did not always check
\r
6004 if(MateTest(boards[m-1], PosFlags(m-1),
\r
6005 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
6006 hisPerpetual = 0; // the opponent did not always check
\r
6008 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
6009 ourPerpetual, hisPerpetual);
\r
6010 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6011 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6012 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6015 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6016 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6017 // Now check for perpetual chases
\r
6018 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
6019 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
6020 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
6021 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6022 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6023 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
6026 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
6027 break; // Abort repetition-checking loop.
\r
6029 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
6031 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6034 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6035 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6039 /* Now we test for 50-move draws. Determine ply count */
\r
6040 count = forwardMostMove;
\r
6041 /* look for last irreversble move */
\r
6042 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6044 /* if we hit starting position, add initial plies */
\r
6045 if( count == backwardMostMove )
\r
6046 count -= initialRulePlies;
\r
6047 count = forwardMostMove - count;
\r
6049 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6050 /* this is used to judge if draw claims are legal */
\r
6051 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6052 SendToProgram("force\n", cps->other); // suppress reply
\r
6053 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6054 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6055 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6059 /* if draw offer is pending, treat it as a draw claim
\r
6060 * when draw condition present, to allow engines a way to
\r
6061 * claim draws before making their move to avoid a race
\r
6062 * condition occurring after their move
\r
6064 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6066 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6067 p = "Draw claim: 50-move rule";
\r
6068 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6069 p = "Draw claim: 3-fold repetition";
\r
6070 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6071 p = "Draw claim: insufficient mating material";
\r
6073 SendToProgram("force\n", cps->other); // suppress reply
\r
6074 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6075 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6076 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6082 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6083 SendToProgram("force\n", cps->other); // suppress reply
\r
6084 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6085 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6087 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6094 if (gameMode == TwoMachinesPlay) {
\r
6095 /* [HGM] relaying draw offers moved to after reception of move */
\r
6096 /* and interpreting offer as claim if it brings draw condition */
\r
6097 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6098 SendToProgram("draw\n", cps->other);
\r
6100 if (cps->other->sendTime) {
\r
6101 SendTimeRemaining(cps->other,
\r
6102 cps->other->twoMachinesColor[0] == 'w');
\r
6104 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6105 if (firstMove && !bookHit) {
\r
6106 firstMove = FALSE;
\r
6107 if (cps->other->useColors) {
\r
6108 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6110 SendToProgram("go\n", cps->other);
\r
6112 cps->other->maybeThinking = TRUE;
\r
6115 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6117 if (!pausing && appData.ringBellAfterMoves) {
\r
6122 * Reenable menu items that were disabled while
\r
6123 * machine was thinking
\r
6125 if (gameMode != TwoMachinesPlay)
\r
6126 SetUserThinkingEnables();
\r
6128 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6129 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6130 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6132 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6134 strcpy(bookMove, "move ");
\r
6135 strcat(bookMove, bookHit);
\r
6136 message = bookMove;
\r
6138 programStats.nodes = programStats.depth = programStats.time =
\r
6139 programStats.score = programStats.got_only_move = 0;
\r
6140 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6142 if(cps->lastPing != cps->lastPong) {
\r
6143 savedMessage = message; // args for deferred call
\r
6145 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6148 goto FakeBookMove;
\r
6154 /* Set special modes for chess engines. Later something general
\r
6155 * could be added here; for now there is just one kludge feature,
\r
6156 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6157 * when "xboard" is given as an interactive command.
\r
6159 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6160 cps->useSigint = FALSE;
\r
6161 cps->useSigterm = FALSE;
\r
6164 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6165 * want this, I was asked to put it in, and obliged.
\r
6167 if (!strncmp(message, "setboard ", 9)) {
\r
6168 Board initial_position; int i;
\r
6170 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6172 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6173 DisplayError(_("Bad FEN received from engine"), 0);
\r
6176 Reset(FALSE, FALSE);
\r
6177 CopyBoard(boards[0], initial_position);
\r
6178 initialRulePlies = FENrulePlies;
\r
6179 epStatus[0] = FENepStatus;
\r
6180 for( i=0; i<nrCastlingRights; i++ )
\r
6181 castlingRights[0][i] = FENcastlingRights[i];
\r
6182 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6183 else gameMode = MachinePlaysBlack;
\r
6184 DrawPosition(FALSE, boards[currentMove]);
\r
6190 * Look for communication commands
\r
6192 if (!strncmp(message, "telluser ", 9)) {
\r
6193 DisplayNote(message + 9);
\r
6196 if (!strncmp(message, "tellusererror ", 14)) {
\r
6197 DisplayError(message + 14, 0);
\r
6200 if (!strncmp(message, "tellopponent ", 13)) {
\r
6201 if (appData.icsActive) {
\r
6203 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6207 DisplayNote(message + 13);
\r
6211 if (!strncmp(message, "tellothers ", 11)) {
\r
6212 if (appData.icsActive) {
\r
6214 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6220 if (!strncmp(message, "tellall ", 8)) {
\r
6221 if (appData.icsActive) {
\r
6223 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6227 DisplayNote(message + 8);
\r
6231 if (strncmp(message, "warning", 7) == 0) {
\r
6232 /* Undocumented feature, use tellusererror in new code */
\r
6233 DisplayError(message, 0);
\r
6236 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6237 strcpy(realname, cps->tidy);
\r
6238 strcat(realname, " query");
\r
6239 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6242 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6243 * sent until we are logged on. Crafty kibitzes have been known to
\r
6244 * interfere with the login process.
\r
6247 if (!strncmp(message, "tellics ", 8)) {
\r
6248 SendToICS(message + 8);
\r
6252 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6253 SendToICS(ics_prefix);
\r
6254 SendToICS(message + 15);
\r
6258 /* The following are for backward compatibility only */
\r
6259 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6260 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6261 SendToICS(ics_prefix);
\r
6262 SendToICS(message);
\r
6267 if (strncmp(message, "feature ", 8) == 0) {
\r
6268 ParseFeatures(message+8, cps);
\r
6270 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6274 * If the move is illegal, cancel it and redraw the board.
\r
6275 * Also deal with other error cases. Matching is rather loose
\r
6276 * here to accommodate engines written before the spec.
\r
6278 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6279 strncmp(message, "Error", 5) == 0) {
\r
6280 if (StrStr(message, "name") ||
\r
6281 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6282 StrStr(message, "result") || StrStr(message, "board") ||
\r
6283 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6284 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6285 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6286 StrStr(message, "accepted")) {
\r
6289 if (StrStr(message, "protover")) {
\r
6290 /* Program is responding to input, so it's apparently done
\r
6291 initializing, and this error message indicates it is
\r
6292 protocol version 1. So we don't need to wait any longer
\r
6293 for it to initialize and send feature commands. */
\r
6294 FeatureDone(cps, 1);
\r
6295 cps->protocolVersion = 1;
\r
6298 cps->maybeThinking = FALSE;
\r
6300 if (StrStr(message, "draw")) {
\r
6301 /* Program doesn't have "draw" command */
\r
6302 cps->sendDrawOffers = 0;
\r
6305 if (cps->sendTime != 1 &&
\r
6306 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6307 /* Program apparently doesn't have "time" or "otim" command */
\r
6308 cps->sendTime = 0;
\r
6311 if (StrStr(message, "analyze")) {
\r
6312 cps->analysisSupport = FALSE;
\r
6313 cps->analyzing = FALSE;
\r
6314 Reset(FALSE, TRUE);
\r
6315 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6316 DisplayError(buf2, 0);
\r
6319 if (StrStr(message, "(no matching move)st")) {
\r
6320 /* Special kludge for GNU Chess 4 only */
\r
6321 cps->stKludge = TRUE;
\r
6322 SendTimeControl(cps, movesPerSession, timeControl,
\r
6323 timeIncrement, appData.searchDepth,
\r
6327 if (StrStr(message, "(no matching move)sd")) {
\r
6328 /* Special kludge for GNU Chess 4 only */
\r
6329 cps->sdKludge = TRUE;
\r
6330 SendTimeControl(cps, movesPerSession, timeControl,
\r
6331 timeIncrement, appData.searchDepth,
\r
6335 if (!StrStr(message, "llegal")) {
\r
6338 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6339 gameMode == IcsIdle) return;
\r
6340 if (forwardMostMove <= backwardMostMove) return;
\r
6342 /* Following removed: it caused a bug where a real illegal move
\r
6343 message in analyze mored would be ignored. */
\r
6344 if (cps == &first && programStats.ok_to_send == 0) {
\r
6345 /* Bogus message from Crafty responding to "." This filtering
\r
6346 can miss some of the bad messages, but fortunately the bug
\r
6347 is fixed in current Crafty versions, so it doesn't matter. */
\r
6351 if (pausing) PauseEvent();
\r
6352 if (gameMode == PlayFromGameFile) {
\r
6353 /* Stop reading this game file */
\r
6354 gameMode = EditGame;
\r
6357 currentMove = --forwardMostMove;
\r
6358 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6360 DisplayBothClocks();
\r
6361 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6362 parseList[currentMove], cps->which);
\r
6363 DisplayMoveError(buf1);
\r
6364 DrawPosition(FALSE, boards[currentMove]);
\r
6366 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6367 /* only passes fully legal moves */
\r
6368 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6369 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6370 "False illegal-move claim", GE_XBOARD );
\r
6374 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6375 /* Program has a broken "time" command that
\r
6376 outputs a string not ending in newline.
\r
6378 cps->sendTime = 0;
\r
6382 * If chess program startup fails, exit with an error message.
\r
6383 * Attempts to recover here are futile.
\r
6385 if ((StrStr(message, "unknown host") != NULL)
\r
6386 || (StrStr(message, "No remote directory") != NULL)
\r
6387 || (StrStr(message, "not found") != NULL)
\r
6388 || (StrStr(message, "No such file") != NULL)
\r
6389 || (StrStr(message, "can't alloc") != NULL)
\r
6390 || (StrStr(message, "Permission denied") != NULL)) {
\r
6392 cps->maybeThinking = FALSE;
\r
6393 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6394 cps->which, cps->program, cps->host, message);
\r
6395 RemoveInputSource(cps->isr);
\r
6396 DisplayFatalError(buf1, 0, 1);
\r
6401 * Look for hint output
\r
6403 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6404 if (cps == &first && hintRequested) {
\r
6405 hintRequested = FALSE;
\r
6406 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6407 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6408 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6409 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6410 fromY, fromX, toY, toX, promoChar, buf1);
\r
6411 sprintf(buf2, _("Hint: %s"), buf1);
\r
6412 DisplayInformation(buf2);
\r
6414 /* Hint move could not be parsed!? */
\r
6416 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6417 buf1, cps->which);
\r
6418 DisplayError(buf2, 0);
\r
6421 strcpy(lastHint, buf1);
\r
6427 * Ignore other messages if game is not in progress
\r
6429 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6430 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6433 * look for win, lose, draw, or draw offer
\r
6435 if (strncmp(message, "1-0", 3) == 0) {
\r
6436 char *p, *q, *r = "";
\r
6437 p = strchr(message, '{');
\r
6439 q = strchr(p, '}');
\r
6445 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6447 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6448 char *p, *q, *r = "";
\r
6449 p = strchr(message, '{');
\r
6451 q = strchr(p, '}');
\r
6457 /* Kludge for Arasan 4.1 bug */
\r
6458 if (strcmp(r, "Black resigns") == 0) {
\r
6459 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6462 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6464 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6465 char *p, *q, *r = "";
\r
6466 p = strchr(message, '{');
\r
6468 q = strchr(p, '}');
\r
6475 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6478 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6479 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6481 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6482 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6484 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6485 strncmp(message, "Black matches", 13) == 0 ) {
\r
6486 /* [HGM] ignore GNUShogi noises */
\r
6488 } else if (strncmp(message, "White", 5) == 0 &&
\r
6489 message[5] != '(' &&
\r
6490 StrStr(message, "Black") == NULL) {
\r
6491 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6493 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6494 message[5] != '(') {
\r
6495 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6497 } else if (strcmp(message, "resign") == 0 ||
\r
6498 strcmp(message, "computer resigns") == 0) {
\r
6499 switch (gameMode) {
\r
6500 case MachinePlaysBlack:
\r
6501 case IcsPlayingBlack:
\r
6502 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6504 case MachinePlaysWhite:
\r
6505 case IcsPlayingWhite:
\r
6506 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6508 case TwoMachinesPlay:
\r
6509 if (cps->twoMachinesColor[0] == 'w')
\r
6510 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6512 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6515 /* can't happen */
\r
6519 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6520 switch (gameMode) {
\r
6521 case MachinePlaysBlack:
\r
6522 case IcsPlayingBlack:
\r
6523 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6525 case MachinePlaysWhite:
\r
6526 case IcsPlayingWhite:
\r
6527 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6529 case TwoMachinesPlay:
\r
6530 if (cps->twoMachinesColor[0] == 'w')
\r
6531 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6533 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6536 /* can't happen */
\r
6540 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6541 switch (gameMode) {
\r
6542 case MachinePlaysBlack:
\r
6543 case IcsPlayingBlack:
\r
6544 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6546 case MachinePlaysWhite:
\r
6547 case IcsPlayingWhite:
\r
6548 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6550 case TwoMachinesPlay:
\r
6551 if (cps->twoMachinesColor[0] == 'w')
\r
6552 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6554 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6557 /* can't happen */
\r
6561 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6562 if (WhiteOnMove(forwardMostMove)) {
\r
6563 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6565 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6568 } else if (strstr(message, "Draw") != NULL ||
\r
6569 strstr(message, "game is a draw") != NULL) {
\r
6570 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6572 } else if (strstr(message, "offer") != NULL &&
\r
6573 strstr(message, "draw") != NULL) {
\r
6575 if (appData.zippyPlay && first.initDone) {
\r
6576 /* Relay offer to ICS */
\r
6577 SendToICS(ics_prefix);
\r
6578 SendToICS("draw\n");
\r
6581 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6582 if (gameMode == TwoMachinesPlay) {
\r
6583 if (cps->other->offeredDraw) {
\r
6584 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6585 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6586 /* until after we also have move, to see if it is really claim */
\r
6590 if (cps->other->sendDrawOffers) {
\r
6591 SendToProgram("draw\n", cps->other);
\r
6595 } else if (gameMode == MachinePlaysWhite ||
\r
6596 gameMode == MachinePlaysBlack) {
\r
6597 if (userOfferedDraw) {
\r
6598 DisplayInformation(_("Machine accepts your draw offer"));
\r
6599 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6601 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6608 * Look for thinking output
\r
6610 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6611 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6613 int plylev, mvleft, mvtot, curscore, time;
\r
6614 char mvname[MOVE_LEN];
\r
6615 u64 nodes; // [DM]
\r
6617 int ignore = FALSE;
\r
6618 int prefixHint = FALSE;
\r
6619 mvname[0] = NULLCHAR;
\r
6621 switch (gameMode) {
\r
6622 case MachinePlaysBlack:
\r
6623 case IcsPlayingBlack:
\r
6624 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6626 case MachinePlaysWhite:
\r
6627 case IcsPlayingWhite:
\r
6628 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6633 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6634 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6636 case TwoMachinesPlay:
\r
6637 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6647 buf1[0] = NULLCHAR;
\r
6648 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6649 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6651 if (plyext != ' ' && plyext != '\t') {
\r
6655 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6656 if( cps->scoreIsAbsolute &&
\r
6657 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6659 curscore = -curscore;
\r
6663 programStats.depth = plylev;
\r
6664 programStats.nodes = nodes;
\r
6665 programStats.time = time;
\r
6666 programStats.score = curscore;
\r
6667 programStats.got_only_move = 0;
\r
6669 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6672 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6673 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6674 if(WhiteOnMove(forwardMostMove))
\r
6675 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6676 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6679 /* Buffer overflow protection */
\r
6680 if (buf1[0] != NULLCHAR) {
\r
6681 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6682 && appData.debugMode) {
\r
6684 "PV is too long; using the first %d bytes.\n",
\r
6685 sizeof(programStats.movelist) - 1);
\r
6688 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6690 sprintf(programStats.movelist, " no PV\n");
\r
6693 if (programStats.seen_stat) {
\r
6694 programStats.ok_to_send = 1;
\r
6697 if (strchr(programStats.movelist, '(') != NULL) {
\r
6698 programStats.line_is_book = 1;
\r
6699 programStats.nr_moves = 0;
\r
6700 programStats.moves_left = 0;
\r
6702 programStats.line_is_book = 0;
\r
6705 SendProgramStatsToFrontend( cps, &programStats );
\r
6708 [AS] Protect the thinkOutput buffer from overflow... this
\r
6709 is only useful if buf1 hasn't overflowed first!
\r
6711 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6713 (gameMode == TwoMachinesPlay ?
\r
6714 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6715 ((double) curscore) / 100.0,
\r
6716 prefixHint ? lastHint : "",
\r
6717 prefixHint ? " " : "" );
\r
6719 if( buf1[0] != NULLCHAR ) {
\r
6720 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6722 if( strlen(buf1) > max_len ) {
\r
6723 if( appData.debugMode) {
\r
6724 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6726 buf1[max_len+1] = '\0';
\r
6729 strcat( thinkOutput, buf1 );
\r
6732 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6733 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6734 DisplayMove(currentMove - 1);
\r
6735 DisplayAnalysis();
\r
6739 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6740 /* crafty (9.25+) says "(only move) <move>"
\r
6741 * if there is only 1 legal move
\r
6743 sscanf(p, "(only move) %s", buf1);
\r
6744 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6745 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6746 programStats.depth = 1;
\r
6747 programStats.nr_moves = 1;
\r
6748 programStats.moves_left = 1;
\r
6749 programStats.nodes = 1;
\r
6750 programStats.time = 1;
\r
6751 programStats.got_only_move = 1;
\r
6753 /* Not really, but we also use this member to
\r
6754 mean "line isn't going to change" (Crafty
\r
6755 isn't searching, so stats won't change) */
\r
6756 programStats.line_is_book = 1;
\r
6758 SendProgramStatsToFrontend( cps, &programStats );
\r
6760 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6761 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6762 DisplayMove(currentMove - 1);
\r
6763 DisplayAnalysis();
\r
6766 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6767 &time, &nodes, &plylev, &mvleft,
\r
6768 &mvtot, mvname) >= 5) {
\r
6769 /* The stat01: line is from Crafty (9.29+) in response
\r
6770 to the "." command */
\r
6771 programStats.seen_stat = 1;
\r
6772 cps->maybeThinking = TRUE;
\r
6774 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6777 programStats.depth = plylev;
\r
6778 programStats.time = time;
\r
6779 programStats.nodes = nodes;
\r
6780 programStats.moves_left = mvleft;
\r
6781 programStats.nr_moves = mvtot;
\r
6782 strcpy(programStats.move_name, mvname);
\r
6783 programStats.ok_to_send = 1;
\r
6784 programStats.movelist[0] = '\0';
\r
6786 SendProgramStatsToFrontend( cps, &programStats );
\r
6788 DisplayAnalysis();
\r
6791 } else if (strncmp(message,"++",2) == 0) {
\r
6792 /* Crafty 9.29+ outputs this */
\r
6793 programStats.got_fail = 2;
\r
6796 } else if (strncmp(message,"--",2) == 0) {
\r
6797 /* Crafty 9.29+ outputs this */
\r
6798 programStats.got_fail = 1;
\r
6801 } else if (thinkOutput[0] != NULLCHAR &&
\r
6802 strncmp(message, " ", 4) == 0) {
\r
6803 unsigned message_len;
\r
6806 while (*p && *p == ' ') p++;
\r
6808 message_len = strlen( p );
\r
6810 /* [AS] Avoid buffer overflow */
\r
6811 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6812 strcat(thinkOutput, " ");
\r
6813 strcat(thinkOutput, p);
\r
6816 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6817 strcat(programStats.movelist, " ");
\r
6818 strcat(programStats.movelist, p);
\r
6821 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6822 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6823 DisplayMove(currentMove - 1);
\r
6824 DisplayAnalysis();
\r
6830 buf1[0] = NULLCHAR;
\r
6832 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6833 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6835 ChessProgramStats cpstats;
\r
6837 if (plyext != ' ' && plyext != '\t') {
\r
6841 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6842 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6843 curscore = -curscore;
\r
6846 cpstats.depth = plylev;
\r
6847 cpstats.nodes = nodes;
\r
6848 cpstats.time = time;
\r
6849 cpstats.score = curscore;
\r
6850 cpstats.got_only_move = 0;
\r
6851 cpstats.movelist[0] = '\0';
\r
6853 if (buf1[0] != NULLCHAR) {
\r
6854 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6857 cpstats.ok_to_send = 0;
\r
6858 cpstats.line_is_book = 0;
\r
6859 cpstats.nr_moves = 0;
\r
6860 cpstats.moves_left = 0;
\r
6862 SendProgramStatsToFrontend( cps, &cpstats );
\r
6869 /* Parse a game score from the character string "game", and
\r
6870 record it as the history of the current game. The game
\r
6871 score is NOT assumed to start from the standard position.
\r
6872 The display is not updated in any way.
\r
6875 ParseGameHistory(game)
\r
6878 ChessMove moveType;
\r
6879 int fromX, fromY, toX, toY, boardIndex;
\r
6882 char buf[MSG_SIZ];
\r
6884 if (appData.debugMode)
\r
6885 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6887 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6888 gameInfo.site = StrSave(appData.icsHost);
\r
6889 gameInfo.date = PGNDate();
\r
6890 gameInfo.round = StrSave("-");
\r
6892 /* Parse out names of players */
\r
6893 while (*game == ' ') game++;
\r
6895 while (*game != ' ') *p++ = *game++;
\r
6897 gameInfo.white = StrSave(buf);
\r
6898 while (*game == ' ') game++;
\r
6900 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6902 gameInfo.black = StrSave(buf);
\r
6905 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6908 yyboardindex = boardIndex;
\r
6909 moveType = (ChessMove) yylex();
\r
6910 switch (moveType) {
\r
6911 case IllegalMove: /* maybe suicide chess, etc. */
\r
6912 if (appData.debugMode) {
\r
6913 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6914 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6915 setbuf(debugFP, NULL);
\r
6917 case WhitePromotionChancellor:
\r
6918 case BlackPromotionChancellor:
\r
6919 case WhitePromotionArchbishop:
\r
6920 case BlackPromotionArchbishop:
\r
6921 case WhitePromotionQueen:
\r
6922 case BlackPromotionQueen:
\r
6923 case WhitePromotionRook:
\r
6924 case BlackPromotionRook:
\r
6925 case WhitePromotionBishop:
\r
6926 case BlackPromotionBishop:
\r
6927 case WhitePromotionKnight:
\r
6928 case BlackPromotionKnight:
\r
6929 case WhitePromotionKing:
\r
6930 case BlackPromotionKing:
\r
6932 case WhiteCapturesEnPassant:
\r
6933 case BlackCapturesEnPassant:
\r
6934 case WhiteKingSideCastle:
\r
6935 case WhiteQueenSideCastle:
\r
6936 case BlackKingSideCastle:
\r
6937 case BlackQueenSideCastle:
\r
6938 case WhiteKingSideCastleWild:
\r
6939 case WhiteQueenSideCastleWild:
\r
6940 case BlackKingSideCastleWild:
\r
6941 case BlackQueenSideCastleWild:
\r
6943 case WhiteHSideCastleFR:
\r
6944 case WhiteASideCastleFR:
\r
6945 case BlackHSideCastleFR:
\r
6946 case BlackASideCastleFR:
\r
6948 fromX = currentMoveString[0] - AAA;
\r
6949 fromY = currentMoveString[1] - ONE;
\r
6950 toX = currentMoveString[2] - AAA;
\r
6951 toY = currentMoveString[3] - ONE;
\r
6952 promoChar = currentMoveString[4];
\r
6956 fromX = moveType == WhiteDrop ?
\r
6957 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6958 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6959 fromY = DROP_RANK;
\r
6960 toX = currentMoveString[2] - AAA;
\r
6961 toY = currentMoveString[3] - ONE;
\r
6962 promoChar = NULLCHAR;
\r
6964 case AmbiguousMove:
\r
6966 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6967 if (appData.debugMode) {
\r
6968 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6969 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6970 setbuf(debugFP, NULL);
\r
6972 DisplayError(buf, 0);
\r
6974 case ImpossibleMove:
\r
6976 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6977 if (appData.debugMode) {
\r
6978 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6979 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6980 setbuf(debugFP, NULL);
\r
6982 DisplayError(buf, 0);
\r
6984 case (ChessMove) 0: /* end of file */
\r
6985 if (boardIndex < backwardMostMove) {
\r
6986 /* Oops, gap. How did that happen? */
\r
6987 DisplayError(_("Gap in move list"), 0);
\r
6990 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6991 if (boardIndex > forwardMostMove) {
\r
6992 forwardMostMove = boardIndex;
\r
6996 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6997 strcat(parseList[boardIndex-1], " ");
\r
6998 strcat(parseList[boardIndex-1], yy_text);
\r
7010 case GameUnfinished:
\r
7011 if (gameMode == IcsExamining) {
\r
7012 if (boardIndex < backwardMostMove) {
\r
7013 /* Oops, gap. How did that happen? */
\r
7016 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7019 gameInfo.result = moveType;
\r
7020 p = strchr(yy_text, '{');
\r
7021 if (p == NULL) p = strchr(yy_text, '(');
\r
7024 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7026 q = strchr(p, *p == '{' ? '}' : ')');
\r
7027 if (q != NULL) *q = NULLCHAR;
\r
7030 gameInfo.resultDetails = StrSave(p);
\r
7033 if (boardIndex >= forwardMostMove &&
\r
7034 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7035 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7038 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7039 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7040 parseList[boardIndex]);
\r
7041 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7042 /* currentMoveString is set as a side-effect of yylex */
\r
7043 strcpy(moveList[boardIndex], currentMoveString);
\r
7044 strcat(moveList[boardIndex], "\n");
\r
7046 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7047 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7048 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7050 case MT_STALEMATE:
\r
7054 if(gameInfo.variant != VariantShogi)
\r
7055 strcat(parseList[boardIndex - 1], "+");
\r
7057 case MT_CHECKMATE:
\r
7058 strcat(parseList[boardIndex - 1], "#");
\r
7065 /* Apply a move to the given board */
\r
7067 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7068 int fromX, fromY, toX, toY;
\r
7072 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7074 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7075 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7076 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7079 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7080 oldEP = epStatus[p-1];
\r
7081 epStatus[p] = EP_NONE;
\r
7083 if( board[toY][toX] != EmptySquare )
\r
7084 epStatus[p] = EP_CAPTURE;
\r
7086 if( board[fromY][fromX] == WhitePawn ) {
\r
7087 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7088 epStatus[p] = EP_PAWN_MOVE;
\r
7089 if( toY-fromY==2) {
\r
7090 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7091 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7092 epStatus[p] = toX | berolina;
\r
7093 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7094 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7095 epStatus[p] = toX;
\r
7098 if( board[fromY][fromX] == BlackPawn ) {
\r
7099 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7100 epStatus[p] = EP_PAWN_MOVE;
\r
7101 if( toY-fromY== -2) {
\r
7102 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7103 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7104 epStatus[p] = toX | berolina;
\r
7105 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7106 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7107 epStatus[p] = toX;
\r
7111 for(i=0; i<nrCastlingRights; i++) {
\r
7112 castlingRights[p][i] = castlingRights[p-1][i];
\r
7113 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7114 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7115 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7120 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7121 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7122 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7124 if (fromX == toX && fromY == toY) return;
\r
7126 if (fromY == DROP_RANK) {
\r
7127 /* must be first */
\r
7128 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7130 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7131 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7132 if(gameInfo.variant == VariantKnightmate)
\r
7133 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7135 /* Code added by Tord: */
\r
7136 /* FRC castling assumed when king captures friendly rook. */
\r
7137 if (board[fromY][fromX] == WhiteKing &&
\r
7138 board[toY][toX] == WhiteRook) {
\r
7139 board[fromY][fromX] = EmptySquare;
\r
7140 board[toY][toX] = EmptySquare;
\r
7142 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7144 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7146 } else if (board[fromY][fromX] == BlackKing &&
\r
7147 board[toY][toX] == BlackRook) {
\r
7148 board[fromY][fromX] = EmptySquare;
\r
7149 board[toY][toX] = EmptySquare;
\r
7151 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7153 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7155 /* End of code added by Tord */
\r
7157 } else if (board[fromY][fromX] == king
\r
7158 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7159 && toY == fromY && toX > fromX+1) {
\r
7160 board[fromY][fromX] = EmptySquare;
\r
7161 board[toY][toX] = king;
\r
7162 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7163 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7164 } else if (board[fromY][fromX] == king
\r
7165 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7166 && toY == fromY && toX < fromX-1) {
\r
7167 board[fromY][fromX] = EmptySquare;
\r
7168 board[toY][toX] = king;
\r
7169 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7170 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7171 } else if (board[fromY][fromX] == WhitePawn
\r
7172 && toY == BOARD_HEIGHT-1
\r
7173 && gameInfo.variant != VariantXiangqi
\r
7175 /* white pawn promotion */
\r
7176 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7177 if (board[toY][toX] == EmptySquare) {
\r
7178 board[toY][toX] = WhiteQueen;
\r
7180 if(gameInfo.variant==VariantBughouse ||
\r
7181 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7182 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7183 board[fromY][fromX] = EmptySquare;
\r
7184 } else if ((fromY == BOARD_HEIGHT-4)
\r
7186 && gameInfo.variant != VariantXiangqi
\r
7187 && gameInfo.variant != VariantBerolina
\r
7188 && (board[fromY][fromX] == WhitePawn)
\r
7189 && (board[toY][toX] == EmptySquare)) {
\r
7190 board[fromY][fromX] = EmptySquare;
\r
7191 board[toY][toX] = WhitePawn;
\r
7192 captured = board[toY - 1][toX];
\r
7193 board[toY - 1][toX] = EmptySquare;
\r
7194 } else if ((fromY == BOARD_HEIGHT-4)
\r
7196 && gameInfo.variant == VariantBerolina
\r
7197 && (board[fromY][fromX] == WhitePawn)
\r
7198 && (board[toY][toX] == EmptySquare)) {
\r
7199 board[fromY][fromX] = EmptySquare;
\r
7200 board[toY][toX] = WhitePawn;
\r
7201 if(oldEP & EP_BEROLIN_A) {
\r
7202 captured = board[fromY][fromX-1];
\r
7203 board[fromY][fromX-1] = EmptySquare;
\r
7204 }else{ captured = board[fromY][fromX+1];
\r
7205 board[fromY][fromX+1] = EmptySquare;
\r
7207 } else if (board[fromY][fromX] == king
\r
7208 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7209 && toY == fromY && toX > fromX+1) {
\r
7210 board[fromY][fromX] = EmptySquare;
\r
7211 board[toY][toX] = king;
\r
7212 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7213 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7214 } else if (board[fromY][fromX] == king
\r
7215 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7216 && toY == fromY && toX < fromX-1) {
\r
7217 board[fromY][fromX] = EmptySquare;
\r
7218 board[toY][toX] = king;
\r
7219 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7220 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7221 } else if (fromY == 7 && fromX == 3
\r
7222 && board[fromY][fromX] == BlackKing
\r
7223 && toY == 7 && toX == 5) {
\r
7224 board[fromY][fromX] = EmptySquare;
\r
7225 board[toY][toX] = BlackKing;
\r
7226 board[fromY][7] = EmptySquare;
\r
7227 board[toY][4] = BlackRook;
\r
7228 } else if (fromY == 7 && fromX == 3
\r
7229 && board[fromY][fromX] == BlackKing
\r
7230 && toY == 7 && toX == 1) {
\r
7231 board[fromY][fromX] = EmptySquare;
\r
7232 board[toY][toX] = BlackKing;
\r
7233 board[fromY][0] = EmptySquare;
\r
7234 board[toY][2] = BlackRook;
\r
7235 } else if (board[fromY][fromX] == BlackPawn
\r
7237 && gameInfo.variant != VariantXiangqi
\r
7239 /* black pawn promotion */
\r
7240 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7241 if (board[0][toX] == EmptySquare) {
\r
7242 board[0][toX] = BlackQueen;
\r
7244 if(gameInfo.variant==VariantBughouse ||
\r
7245 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7246 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7247 board[fromY][fromX] = EmptySquare;
\r
7248 } else if ((fromY == 3)
\r
7250 && gameInfo.variant != VariantXiangqi
\r
7251 && gameInfo.variant != VariantBerolina
\r
7252 && (board[fromY][fromX] == BlackPawn)
\r
7253 && (board[toY][toX] == EmptySquare)) {
\r
7254 board[fromY][fromX] = EmptySquare;
\r
7255 board[toY][toX] = BlackPawn;
\r
7256 captured = board[toY + 1][toX];
\r
7257 board[toY + 1][toX] = EmptySquare;
\r
7258 } else if ((fromY == 3)
\r
7260 && gameInfo.variant == VariantBerolina
\r
7261 && (board[fromY][fromX] == BlackPawn)
\r
7262 && (board[toY][toX] == EmptySquare)) {
\r
7263 board[fromY][fromX] = EmptySquare;
\r
7264 board[toY][toX] = BlackPawn;
\r
7265 if(oldEP & EP_BEROLIN_A) {
\r
7266 captured = board[fromY][fromX-1];
\r
7267 board[fromY][fromX-1] = EmptySquare;
\r
7268 }else{ captured = board[fromY][fromX+1];
\r
7269 board[fromY][fromX+1] = EmptySquare;
\r
7272 board[toY][toX] = board[fromY][fromX];
\r
7273 board[fromY][fromX] = EmptySquare;
\r
7276 /* [HGM] now we promote for Shogi, if needed */
\r
7277 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7278 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7281 if (gameInfo.holdingsWidth != 0) {
\r
7283 /* !!A lot more code needs to be written to support holdings */
\r
7284 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7285 /* penultimate board files, so they are automaticlly stored */
\r
7286 /* in the game history. */
\r
7287 if (fromY == DROP_RANK) {
\r
7288 /* Delete from holdings, by decreasing count */
\r
7289 /* and erasing image if necessary */
\r
7291 if(p < (int) BlackPawn) { /* white drop */
\r
7292 p -= (int)WhitePawn;
\r
7293 if(p >= gameInfo.holdingsSize) p = 0;
\r
7294 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7295 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7296 } else { /* black drop */
\r
7297 p -= (int)BlackPawn;
\r
7298 if(p >= gameInfo.holdingsSize) p = 0;
\r
7299 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7300 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7303 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7304 && gameInfo.variant != VariantBughouse ) {
\r
7305 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7306 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7307 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7308 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7310 p = (int) captured;
\r
7311 if (p >= (int) BlackPawn) {
\r
7312 p -= (int)BlackPawn;
\r
7313 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7314 /* in Shogi restore piece to its original first */
\r
7315 captured = (ChessSquare) (DEMOTED captured);
\r
7318 p = PieceToNumber((ChessSquare)p);
\r
7319 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7320 board[p][BOARD_WIDTH-2]++;
\r
7321 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7323 p -= (int)WhitePawn;
\r
7324 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7325 captured = (ChessSquare) (DEMOTED captured);
\r
7328 p = PieceToNumber((ChessSquare)p);
\r
7329 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7330 board[BOARD_HEIGHT-1-p][1]++;
\r
7331 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7335 } else if (gameInfo.variant == VariantAtomic) {
\r
7336 if (captured != EmptySquare) {
\r
7338 for (y = toY-1; y <= toY+1; y++) {
\r
7339 for (x = toX-1; x <= toX+1; x++) {
\r
7340 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7341 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7342 board[y][x] = EmptySquare;
\r
7346 board[toY][toX] = EmptySquare;
\r
7349 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7350 /* [HGM] Shogi promotions */
\r
7351 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7356 /* Updates forwardMostMove */
\r
7358 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7359 int fromX, fromY, toX, toY;
\r
7362 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7364 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7365 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7366 piece = boards[forwardMostMove][fromY][fromX];
\r
7367 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7368 if(gameInfo.variant == VariantKnightmate)
\r
7369 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7370 if(forwardMostMove == 0) {
\r
7371 if(blackPlaysFirst)
\r
7372 fprintf(serverMoves, "%s;", second.tidy);
\r
7373 fprintf(serverMoves, "%s;", first.tidy);
\r
7374 if(!blackPlaysFirst)
\r
7375 fprintf(serverMoves, "%s;", second.tidy);
\r
7376 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7377 lastLoadFlag = loadFlag;
\r
7378 // print base move
\r
7379 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7380 // print castling suffix
\r
7381 if( toY == fromY && piece == king ) {
\r
7383 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7385 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7388 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7389 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7390 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7392 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7393 // promotion suffix
\r
7394 if(promoChar != NULLCHAR)
\r
7395 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7397 fprintf(serverMoves, "/%d/%d",
\r
7398 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7399 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7400 else timeLeft = blackTimeRemaining/1000;
\r
7401 fprintf(serverMoves, "/%d", timeLeft);
\r
7403 fflush(serverMoves);
\r
7406 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7407 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7412 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7413 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7414 if (commentList[forwardMostMove+1] != NULL) {
\r
7415 free(commentList[forwardMostMove+1]);
\r
7416 commentList[forwardMostMove+1] = NULL;
\r
7418 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7419 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7420 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7421 gameInfo.result = GameUnfinished;
\r
7422 if (gameInfo.resultDetails != NULL) {
\r
7423 free(gameInfo.resultDetails);
\r
7424 gameInfo.resultDetails = NULL;
\r
7426 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7427 moveList[forwardMostMove - 1]);
\r
7428 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7429 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7430 fromY, fromX, toY, toX, promoChar,
\r
7431 parseList[forwardMostMove - 1]);
\r
7432 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7433 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7434 castlingRights[forwardMostMove]) ) {
\r
7436 case MT_STALEMATE:
\r
7440 if(gameInfo.variant != VariantShogi)
\r
7441 strcat(parseList[forwardMostMove - 1], "+");
\r
7443 case MT_CHECKMATE:
\r
7444 strcat(parseList[forwardMostMove - 1], "#");
\r
7447 if (appData.debugMode) {
\r
7448 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7453 /* Updates currentMove if not pausing */
\r
7455 ShowMove(fromX, fromY, toX, toY)
\r
7457 int instant = (gameMode == PlayFromGameFile) ?
\r
7458 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7459 if(appData.noGUI) return;
\r
7460 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7462 if (forwardMostMove == currentMove + 1) {
\r
7463 AnimateMove(boards[forwardMostMove - 1],
\r
7464 fromX, fromY, toX, toY);
\r
7466 if (appData.highlightLastMove) {
\r
7467 SetHighlights(fromX, fromY, toX, toY);
\r
7470 currentMove = forwardMostMove;
\r
7473 if (instant) return;
\r
7475 DisplayMove(currentMove - 1);
\r
7476 DrawPosition(FALSE, boards[currentMove]);
\r
7477 DisplayBothClocks();
\r
7478 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7481 void SendEgtPath(ChessProgramState *cps)
\r
7482 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7483 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7485 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7488 char c, *q = name+1, *r, *s;
\r
7490 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7491 while(*p && *p != ',') *q++ = *p++;
\r
7492 *q++ = ':'; *q = 0;
\r
7493 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7494 strcmp(name, ",nalimov:") == 0 ) {
\r
7495 // take nalimov path from the menu-changeable option first, if it is defined
\r
7496 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7497 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7499 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7500 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7501 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7502 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7503 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7504 c = *r; *r = 0; // temporarily null-terminate path info
\r
7505 *--q = 0; // strip of trailig ':' from name
\r
7506 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7508 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7510 if(*p == ',') p++; // read away comma to position for next format name
\r
7515 InitChessProgram(cps, setup)
\r
7516 ChessProgramState *cps;
\r
7517 int setup; /* [HGM] needed to setup FRC opening position */
\r
7519 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7520 if (appData.noChessProgram) return;
\r
7521 hintRequested = FALSE;
\r
7522 bookRequested = FALSE;
\r
7524 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7525 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7526 if(cps->memSize) { /* [HGM] memory */
\r
7527 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7528 SendToProgram(buf, cps);
\r
7530 SendEgtPath(cps); /* [HGM] EGT */
\r
7531 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7532 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7533 SendToProgram(buf, cps);
\r
7536 SendToProgram(cps->initString, cps);
\r
7537 if (gameInfo.variant != VariantNormal &&
\r
7538 gameInfo.variant != VariantLoadable
\r
7539 /* [HGM] also send variant if board size non-standard */
\r
7540 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7542 char *v = VariantName(gameInfo.variant);
\r
7543 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7544 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7545 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7546 DisplayFatalError(buf, 0, 1);
\r
7550 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7551 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7552 if( gameInfo.variant == VariantXiangqi )
\r
7553 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7554 if( gameInfo.variant == VariantShogi )
\r
7555 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7556 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7557 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7558 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7559 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7560 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7561 if( gameInfo.variant == VariantCourier )
\r
7562 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7563 if( gameInfo.variant == VariantSuper )
\r
7564 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7565 if( gameInfo.variant == VariantGreat )
\r
7566 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7569 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7570 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7571 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7572 if(StrStr(cps->variants, b) == NULL) {
\r
7573 // specific sized variant not known, check if general sizing allowed
\r
7574 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7575 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7576 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7577 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7578 DisplayFatalError(buf, 0, 1);
\r
7581 /* [HGM] here we really should compare with the maximum supported board size */
\r
7584 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7585 sprintf(buf, "variant %s\n", b);
\r
7586 SendToProgram(buf, cps);
\r
7588 currentlyInitializedVariant = gameInfo.variant;
\r
7590 /* [HGM] send opening position in FRC to first engine */
\r
7592 SendToProgram("force\n", cps);
\r
7593 SendBoard(cps, 0);
\r
7594 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7595 setboardSpoiledMachineBlack = 1;
\r
7598 if (cps->sendICS) {
\r
7599 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7600 SendToProgram(buf, cps);
\r
7602 cps->maybeThinking = FALSE;
\r
7603 cps->offeredDraw = 0;
\r
7604 if (!appData.icsActive) {
\r
7605 SendTimeControl(cps, movesPerSession, timeControl,
\r
7606 timeIncrement, appData.searchDepth,
\r
7609 if (appData.showThinking
\r
7610 // [HGM] thinking: four options require thinking output to be sent
\r
7611 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7613 SendToProgram("post\n", cps);
\r
7615 SendToProgram("hard\n", cps);
\r
7616 if (!appData.ponderNextMove) {
\r
7617 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7618 it without being sure what state we are in first. "hard"
\r
7619 is not a toggle, so that one is OK.
\r
7621 SendToProgram("easy\n", cps);
\r
7623 if (cps->usePing) {
\r
7624 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7625 SendToProgram(buf, cps);
\r
7627 cps->initDone = TRUE;
\r
7632 StartChessProgram(cps)
\r
7633 ChessProgramState *cps;
\r
7635 char buf[MSG_SIZ];
\r
7638 if (appData.noChessProgram) return;
\r
7639 cps->initDone = FALSE;
\r
7641 if (strcmp(cps->host, "localhost") == 0) {
\r
7642 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7643 } else if (*appData.remoteShell == NULLCHAR) {
\r
7644 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7646 if (*appData.remoteUser == NULLCHAR) {
\r
7647 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7650 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7651 cps->host, appData.remoteUser, cps->program);
\r
7653 err = StartChildProcess(buf, "", &cps->pr);
\r
7657 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7658 DisplayFatalError(buf, err, 1);
\r
7664 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7665 if (cps->protocolVersion > 1) {
\r
7666 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7667 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7668 cps->comboCnt = 0; // and values of combo boxes
\r
7669 SendToProgram(buf, cps);
\r
7671 SendToProgram("xboard\n", cps);
\r
7677 TwoMachinesEventIfReady P((void))
\r
7679 if (first.lastPing != first.lastPong) {
\r
7680 DisplayMessage("", _("Waiting for first chess program"));
\r
7681 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7684 if (second.lastPing != second.lastPong) {
\r
7685 DisplayMessage("", _("Waiting for second chess program"));
\r
7686 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7690 TwoMachinesEvent();
\r
7694 NextMatchGame P((void))
\r
7696 int index; /* [HGM] autoinc: step lod index during match */
\r
7697 Reset(FALSE, TRUE);
\r
7698 if (*appData.loadGameFile != NULLCHAR) {
\r
7699 index = appData.loadGameIndex;
\r
7700 if(index < 0) { // [HGM] autoinc
\r
7701 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7702 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7704 LoadGameFromFile(appData.loadGameFile,
\r
7706 appData.loadGameFile, FALSE);
\r
7707 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7708 index = appData.loadPositionIndex;
\r
7709 if(index < 0) { // [HGM] autoinc
\r
7710 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7711 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7713 LoadPositionFromFile(appData.loadPositionFile,
\r
7715 appData.loadPositionFile);
\r
7717 TwoMachinesEventIfReady();
\r
7720 void UserAdjudicationEvent( int result )
\r
7722 ChessMove gameResult = GameIsDrawn;
\r
7724 if( result > 0 ) {
\r
7725 gameResult = WhiteWins;
\r
7727 else if( result < 0 ) {
\r
7728 gameResult = BlackWins;
\r
7731 if( gameMode == TwoMachinesPlay ) {
\r
7732 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7738 GameEnds(result, resultDetails, whosays)
\r
7740 char *resultDetails;
\r
7743 GameMode nextGameMode;
\r
7745 char buf[MSG_SIZ];
\r
7747 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7750 if (appData.debugMode) {
\r
7751 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7752 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7755 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7756 /* If we are playing on ICS, the server decides when the
\r
7757 game is over, but the engine can offer to draw, claim
\r
7758 a draw, or resign.
\r
7761 if (appData.zippyPlay && first.initDone) {
\r
7762 if (result == GameIsDrawn) {
\r
7763 /* In case draw still needs to be claimed */
\r
7764 SendToICS(ics_prefix);
\r
7765 SendToICS("draw\n");
\r
7766 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7767 SendToICS(ics_prefix);
\r
7768 SendToICS("resign\n");
\r
7772 endingGame = 0; /* [HGM] crash */
\r
7776 /* If we're loading the game from a file, stop */
\r
7777 if (whosays == GE_FILE) {
\r
7778 (void) StopLoadGameTimer();
\r
7779 gameFileFP = NULL;
\r
7782 /* Cancel draw offers */
\r
7783 first.offeredDraw = second.offeredDraw = 0;
\r
7785 /* If this is an ICS game, only ICS can really say it's done;
\r
7786 if not, anyone can. */
\r
7787 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7788 gameMode == IcsPlayingBlack ||
\r
7789 gameMode == IcsObserving ||
\r
7790 gameMode == IcsExamining);
\r
7792 if (!isIcsGame || whosays == GE_ICS) {
\r
7793 /* OK -- not an ICS game, or ICS said it was done */
\r
7795 if (!isIcsGame && !appData.noChessProgram)
\r
7796 SetUserThinkingEnables();
\r
7798 /* [HGM] if a machine claims the game end we verify this claim */
\r
7799 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7800 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7803 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7804 first.twoMachinesColor[0] :
\r
7805 second.twoMachinesColor[0] ;
\r
7806 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7807 (result == WhiteWins && claimer == 'w' ||
\r
7808 result == BlackWins && claimer == 'b' ) ) {
\r
7809 if (appData.debugMode) {
\r
7810 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7811 result, epStatus[forwardMostMove], forwardMostMove);
\r
7813 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7814 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7815 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7816 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7817 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7818 resultDetails = buf;
\r
7821 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7822 && (forwardMostMove <= backwardMostMove ||
\r
7823 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7824 (claimer=='b')==(forwardMostMove&1))
\r
7826 /* [HGM] verify: draws that were not flagged are false claims */
\r
7827 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7828 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7829 resultDetails = buf;
\r
7831 /* (Claiming a loss is accepted no questions asked!) */
\r
7833 /* [HGM] bare: don't allow bare King to win */
\r
7834 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7835 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7836 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7837 && result != GameIsDrawn)
\r
7838 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7839 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7840 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7841 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7843 if (appData.debugMode) {
\r
7844 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7845 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7848 result = GameIsDrawn;
\r
7849 sprintf(buf, "%s but bare king", resultDetails);
\r
7850 resultDetails = buf;
\r
7856 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7857 if(result==WhiteWins) c = '+';
\r
7858 if(result==BlackWins) c = '-';
\r
7859 if(resultDetails != NULL)
\r
7860 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7862 if (resultDetails != NULL) {
\r
7863 gameInfo.result = result;
\r
7864 gameInfo.resultDetails = StrSave(resultDetails);
\r
7866 /* display last move only if game was not loaded from file */
\r
7867 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7868 DisplayMove(currentMove - 1);
\r
7870 if (forwardMostMove != 0) {
\r
7871 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7872 if (*appData.saveGameFile != NULLCHAR) {
\r
7873 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7874 } else if (appData.autoSaveGames) {
\r
7877 if (*appData.savePositionFile != NULLCHAR) {
\r
7878 SavePositionToFile(appData.savePositionFile);
\r
7883 /* Tell program how game ended in case it is learning */
\r
7884 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7885 /* engine died and we got here through time loss. In that */
\r
7886 /* case we will get a fatal error writing the pipe, which */
\r
7887 /* would otherwise lose us the PGN. */
\r
7888 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7889 /* output during GameEnds should never be fatal anymore */
\r
7890 if (gameMode == MachinePlaysWhite ||
\r
7891 gameMode == MachinePlaysBlack ||
\r
7892 gameMode == TwoMachinesPlay ||
\r
7893 gameMode == IcsPlayingWhite ||
\r
7894 gameMode == IcsPlayingBlack ||
\r
7895 gameMode == BeginningOfGame) {
\r
7896 char buf[MSG_SIZ];
\r
7897 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7899 if (first.pr != NoProc) {
\r
7900 SendToProgram(buf, &first);
\r
7902 if (second.pr != NoProc &&
\r
7903 gameMode == TwoMachinesPlay) {
\r
7904 SendToProgram(buf, &second);
\r
7909 if (appData.icsActive) {
\r
7910 if (appData.quietPlay &&
\r
7911 (gameMode == IcsPlayingWhite ||
\r
7912 gameMode == IcsPlayingBlack)) {
\r
7913 SendToICS(ics_prefix);
\r
7914 SendToICS("set shout 1\n");
\r
7916 nextGameMode = IcsIdle;
\r
7917 ics_user_moved = FALSE;
\r
7918 /* clean up premove. It's ugly when the game has ended and the
\r
7919 * premove highlights are still on the board.
\r
7922 gotPremove = FALSE;
\r
7923 ClearPremoveHighlights();
\r
7924 DrawPosition(FALSE, boards[currentMove]);
\r
7926 if (whosays == GE_ICS) {
\r
7929 if (gameMode == IcsPlayingWhite)
\r
7930 PlayIcsWinSound();
\r
7931 else if(gameMode == IcsPlayingBlack)
\r
7932 PlayIcsLossSound();
\r
7935 if (gameMode == IcsPlayingBlack)
\r
7936 PlayIcsWinSound();
\r
7937 else if(gameMode == IcsPlayingWhite)
\r
7938 PlayIcsLossSound();
\r
7941 PlayIcsDrawSound();
\r
7944 PlayIcsUnfinishedSound();
\r
7947 } else if (gameMode == EditGame ||
\r
7948 gameMode == PlayFromGameFile ||
\r
7949 gameMode == AnalyzeMode ||
\r
7950 gameMode == AnalyzeFile) {
\r
7951 nextGameMode = gameMode;
\r
7953 nextGameMode = EndOfGame;
\r
7958 nextGameMode = gameMode;
\r
7961 if (appData.noChessProgram) {
\r
7962 gameMode = nextGameMode;
\r
7964 endingGame = 0; /* [HGM] crash */
\r
7968 if (first.reuse) {
\r
7969 /* Put first chess program into idle state */
\r
7970 if (first.pr != NoProc &&
\r
7971 (gameMode == MachinePlaysWhite ||
\r
7972 gameMode == MachinePlaysBlack ||
\r
7973 gameMode == TwoMachinesPlay ||
\r
7974 gameMode == IcsPlayingWhite ||
\r
7975 gameMode == IcsPlayingBlack ||
\r
7976 gameMode == BeginningOfGame)) {
\r
7977 SendToProgram("force\n", &first);
\r
7978 if (first.usePing) {
\r
7979 char buf[MSG_SIZ];
\r
7980 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7981 SendToProgram(buf, &first);
\r
7984 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7985 /* Kill off first chess program */
\r
7986 if (first.isr != NULL)
\r
7987 RemoveInputSource(first.isr);
\r
7990 if (first.pr != NoProc) {
\r
7991 ExitAnalyzeMode();
\r
7992 DoSleep( appData.delayBeforeQuit );
\r
7993 SendToProgram("quit\n", &first);
\r
7994 DoSleep( appData.delayAfterQuit );
\r
7995 DestroyChildProcess(first.pr, first.useSigterm);
\r
7997 first.pr = NoProc;
\r
7999 if (second.reuse) {
\r
8000 /* Put second chess program into idle state */
\r
8001 if (second.pr != NoProc &&
\r
8002 gameMode == TwoMachinesPlay) {
\r
8003 SendToProgram("force\n", &second);
\r
8004 if (second.usePing) {
\r
8005 char buf[MSG_SIZ];
\r
8006 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
8007 SendToProgram(buf, &second);
\r
8010 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8011 /* Kill off second chess program */
\r
8012 if (second.isr != NULL)
\r
8013 RemoveInputSource(second.isr);
\r
8014 second.isr = NULL;
\r
8016 if (second.pr != NoProc) {
\r
8017 DoSleep( appData.delayBeforeQuit );
\r
8018 SendToProgram("quit\n", &second);
\r
8019 DoSleep( appData.delayAfterQuit );
\r
8020 DestroyChildProcess(second.pr, second.useSigterm);
\r
8022 second.pr = NoProc;
\r
8025 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8028 if (first.twoMachinesColor[0] == 'w') {
\r
8029 first.matchWins++;
\r
8031 second.matchWins++;
\r
8035 if (first.twoMachinesColor[0] == 'b') {
\r
8036 first.matchWins++;
\r
8038 second.matchWins++;
\r
8044 if (matchGame < appData.matchGames) {
\r
8046 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8047 tmp = first.twoMachinesColor;
\r
8048 first.twoMachinesColor = second.twoMachinesColor;
\r
8049 second.twoMachinesColor = tmp;
\r
8051 gameMode = nextGameMode;
\r
8053 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8054 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8055 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8056 endingGame = 0; /* [HGM] crash */
\r
8059 char buf[MSG_SIZ];
\r
8060 gameMode = nextGameMode;
\r
8061 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8062 first.tidy, second.tidy,
\r
8063 first.matchWins, second.matchWins,
\r
8064 appData.matchGames - (first.matchWins + second.matchWins));
\r
8065 DisplayFatalError(buf, 0, 0);
\r
8068 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8069 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8070 ExitAnalyzeMode();
\r
8071 gameMode = nextGameMode;
\r
8073 endingGame = 0; /* [HGM] crash */
\r
8076 /* Assumes program was just initialized (initString sent).
\r
8077 Leaves program in force mode. */
\r
8079 FeedMovesToProgram(cps, upto)
\r
8080 ChessProgramState *cps;
\r
8085 if (appData.debugMode)
\r
8086 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8087 startedFromSetupPosition ? "position and " : "",
\r
8088 backwardMostMove, upto, cps->which);
\r
8089 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8090 // [HGM] variantswitch: make engine aware of new variant
\r
8091 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8092 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8093 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8094 SendToProgram(buf, cps);
\r
8095 currentlyInitializedVariant = gameInfo.variant;
\r
8097 SendToProgram("force\n", cps);
\r
8098 if (startedFromSetupPosition) {
\r
8099 SendBoard(cps, backwardMostMove);
\r
8100 if (appData.debugMode) {
\r
8101 fprintf(debugFP, "feedMoves\n");
\r
8104 for (i = backwardMostMove; i < upto; i++) {
\r
8105 SendMoveToProgram(i, cps);
\r
8111 ResurrectChessProgram()
\r
8113 /* The chess program may have exited.
\r
8114 If so, restart it and feed it all the moves made so far. */
\r
8116 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8118 StartChessProgram(&first);
\r
8119 InitChessProgram(&first, FALSE);
\r
8120 FeedMovesToProgram(&first, currentMove);
\r
8122 if (!first.sendTime) {
\r
8123 /* can't tell gnuchess what its clock should read,
\r
8124 so we bow to its notion. */
\r
8126 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8127 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8130 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8131 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8132 SendToProgram("analyze\n", &first);
\r
8133 first.analyzing = TRUE;
\r
8138 * Button procedures
\r
8141 Reset(redraw, init)
\r
8146 if (appData.debugMode) {
\r
8147 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8148 redraw, init, gameMode);
\r
8150 pausing = pauseExamInvalid = FALSE;
\r
8151 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8153 whiteFlag = blackFlag = FALSE;
\r
8154 userOfferedDraw = FALSE;
\r
8155 hintRequested = bookRequested = FALSE;
\r
8156 first.maybeThinking = FALSE;
\r
8157 second.maybeThinking = FALSE;
\r
8158 first.bookSuspend = FALSE; // [HGM] book
\r
8159 second.bookSuspend = FALSE;
\r
8160 thinkOutput[0] = NULLCHAR;
\r
8161 lastHint[0] = NULLCHAR;
\r
8162 ClearGameInfo(&gameInfo);
\r
8163 gameInfo.variant = StringToVariant(appData.variant);
\r
8164 ics_user_moved = ics_clock_paused = FALSE;
\r
8165 ics_getting_history = H_FALSE;
\r
8167 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8168 ClearProgramStats();
\r
8169 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8172 ClearHighlights();
\r
8173 flipView = appData.flipView;
\r
8174 ClearPremoveHighlights();
\r
8175 gotPremove = FALSE;
\r
8176 alarmSounded = FALSE;
\r
8178 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8179 if(appData.serverMovesName != NULL) {
\r
8180 /* [HGM] prepare to make moves file for broadcasting */
\r
8181 clock_t t = clock();
\r
8182 if(serverMoves != NULL) fclose(serverMoves);
\r
8183 serverMoves = fopen(appData.serverMovesName, "r");
\r
8184 if(serverMoves != NULL) {
\r
8185 fclose(serverMoves);
\r
8186 /* delay 15 sec before overwriting, so all clients can see end */
\r
8187 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8189 serverMoves = fopen(appData.serverMovesName, "w");
\r
8192 ExitAnalyzeMode();
\r
8193 gameMode = BeginningOfGame;
\r
8195 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8196 InitPosition(redraw);
\r
8197 for (i = 0; i < MAX_MOVES; i++) {
\r
8198 if (commentList[i] != NULL) {
\r
8199 free(commentList[i]);
\r
8200 commentList[i] = NULL;
\r
8204 timeRemaining[0][0] = whiteTimeRemaining;
\r
8205 timeRemaining[1][0] = blackTimeRemaining;
\r
8206 if (first.pr == NULL) {
\r
8207 StartChessProgram(&first);
\r
8210 InitChessProgram(&first, startedFromSetupPosition);
\r
8213 DisplayMessage("", "");
\r
8214 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8218 AutoPlayGameLoop()
\r
8221 if (!AutoPlayOneMove())
\r
8223 if (matchMode || appData.timeDelay == 0)
\r
8225 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8227 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8236 int fromX, fromY, toX, toY;
\r
8238 if (appData.debugMode) {
\r
8239 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8242 if (gameMode != PlayFromGameFile)
\r
8245 if (currentMove >= forwardMostMove) {
\r
8246 gameMode = EditGame;
\r
8249 /* [AS] Clear current move marker at the end of a game */
\r
8250 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8255 toX = moveList[currentMove][2] - AAA;
\r
8256 toY = moveList[currentMove][3] - ONE;
\r
8258 if (moveList[currentMove][1] == '@') {
\r
8259 if (appData.highlightLastMove) {
\r
8260 SetHighlights(-1, -1, toX, toY);
\r
8263 fromX = moveList[currentMove][0] - AAA;
\r
8264 fromY = moveList[currentMove][1] - ONE;
\r
8266 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8268 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8270 if (appData.highlightLastMove) {
\r
8271 SetHighlights(fromX, fromY, toX, toY);
\r
8274 DisplayMove(currentMove);
\r
8275 SendMoveToProgram(currentMove++, &first);
\r
8276 DisplayBothClocks();
\r
8277 DrawPosition(FALSE, boards[currentMove]);
\r
8278 // [HGM] PV info: always display, routine tests if empty
\r
8279 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8285 LoadGameOneMove(readAhead)
\r
8286 ChessMove readAhead;
\r
8288 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8289 char promoChar = NULLCHAR;
\r
8290 ChessMove moveType;
\r
8291 char move[MSG_SIZ];
\r
8294 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8295 gameMode != AnalyzeMode && gameMode != Training) {
\r
8296 gameFileFP = NULL;
\r
8300 yyboardindex = forwardMostMove;
\r
8301 if (readAhead != (ChessMove)0) {
\r
8302 moveType = readAhead;
\r
8304 if (gameFileFP == NULL)
\r
8306 moveType = (ChessMove) yylex();
\r
8310 switch (moveType) {
\r
8312 if (appData.debugMode)
\r
8313 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8315 if (*p == '{' || *p == '[' || *p == '(') {
\r
8316 p[strlen(p) - 1] = NULLCHAR;
\r
8320 /* append the comment but don't display it */
\r
8321 while (*p == '\n') p++;
\r
8322 AppendComment(currentMove, p);
\r
8325 case WhiteCapturesEnPassant:
\r
8326 case BlackCapturesEnPassant:
\r
8327 case WhitePromotionChancellor:
\r
8328 case BlackPromotionChancellor:
\r
8329 case WhitePromotionArchbishop:
\r
8330 case BlackPromotionArchbishop:
\r
8331 case WhitePromotionCentaur:
\r
8332 case BlackPromotionCentaur:
\r
8333 case WhitePromotionQueen:
\r
8334 case BlackPromotionQueen:
\r
8335 case WhitePromotionRook:
\r
8336 case BlackPromotionRook:
\r
8337 case WhitePromotionBishop:
\r
8338 case BlackPromotionBishop:
\r
8339 case WhitePromotionKnight:
\r
8340 case BlackPromotionKnight:
\r
8341 case WhitePromotionKing:
\r
8342 case BlackPromotionKing:
\r
8344 case WhiteKingSideCastle:
\r
8345 case WhiteQueenSideCastle:
\r
8346 case BlackKingSideCastle:
\r
8347 case BlackQueenSideCastle:
\r
8348 case WhiteKingSideCastleWild:
\r
8349 case WhiteQueenSideCastleWild:
\r
8350 case BlackKingSideCastleWild:
\r
8351 case BlackQueenSideCastleWild:
\r
8353 case WhiteHSideCastleFR:
\r
8354 case WhiteASideCastleFR:
\r
8355 case BlackHSideCastleFR:
\r
8356 case BlackASideCastleFR:
\r
8358 if (appData.debugMode)
\r
8359 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8360 fromX = currentMoveString[0] - AAA;
\r
8361 fromY = currentMoveString[1] - ONE;
\r
8362 toX = currentMoveString[2] - AAA;
\r
8363 toY = currentMoveString[3] - ONE;
\r
8364 promoChar = currentMoveString[4];
\r
8369 if (appData.debugMode)
\r
8370 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8371 fromX = moveType == WhiteDrop ?
\r
8372 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8373 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8374 fromY = DROP_RANK;
\r
8375 toX = currentMoveString[2] - AAA;
\r
8376 toY = currentMoveString[3] - ONE;
\r
8382 case GameUnfinished:
\r
8383 if (appData.debugMode)
\r
8384 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8385 p = strchr(yy_text, '{');
\r
8386 if (p == NULL) p = strchr(yy_text, '(');
\r
8389 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8391 q = strchr(p, *p == '{' ? '}' : ')');
\r
8392 if (q != NULL) *q = NULLCHAR;
\r
8395 GameEnds(moveType, p, GE_FILE);
\r
8397 if (cmailMsgLoaded) {
\r
8398 ClearHighlights();
\r
8399 flipView = WhiteOnMove(currentMove);
\r
8400 if (moveType == GameUnfinished) flipView = !flipView;
\r
8401 if (appData.debugMode)
\r
8402 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8406 case (ChessMove) 0: /* end of file */
\r
8407 if (appData.debugMode)
\r
8408 fprintf(debugFP, "Parser hit end of file\n");
\r
8409 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8410 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8414 case MT_CHECKMATE:
\r
8415 if (WhiteOnMove(currentMove)) {
\r
8416 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8418 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8421 case MT_STALEMATE:
\r
8422 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8428 case MoveNumberOne:
\r
8429 if (lastLoadGameStart == GNUChessGame) {
\r
8430 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8431 if (appData.debugMode)
\r
8432 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8433 yy_text, (int) moveType);
\r
8434 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8436 /* else fall thru */
\r
8439 case GNUChessGame:
\r
8441 /* Reached start of next game in file */
\r
8442 if (appData.debugMode)
\r
8443 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8444 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8445 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8449 case MT_CHECKMATE:
\r
8450 if (WhiteOnMove(currentMove)) {
\r
8451 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8453 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8456 case MT_STALEMATE:
\r
8457 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8463 case PositionDiagram: /* should not happen; ignore */
\r
8464 case ElapsedTime: /* ignore */
\r
8465 case NAG: /* ignore */
\r
8466 if (appData.debugMode)
\r
8467 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8468 yy_text, (int) moveType);
\r
8469 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8472 if (appData.testLegality) {
\r
8473 if (appData.debugMode)
\r
8474 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8475 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8476 (forwardMostMove / 2) + 1,
\r
8477 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8478 DisplayError(move, 0);
\r
8481 if (appData.debugMode)
\r
8482 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8483 yy_text, currentMoveString);
\r
8484 fromX = currentMoveString[0] - AAA;
\r
8485 fromY = currentMoveString[1] - ONE;
\r
8486 toX = currentMoveString[2] - AAA;
\r
8487 toY = currentMoveString[3] - ONE;
\r
8488 promoChar = currentMoveString[4];
\r
8492 case AmbiguousMove:
\r
8493 if (appData.debugMode)
\r
8494 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8495 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8496 (forwardMostMove / 2) + 1,
\r
8497 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8498 DisplayError(move, 0);
\r
8503 case ImpossibleMove:
\r
8504 if (appData.debugMode)
\r
8505 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8506 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8507 (forwardMostMove / 2) + 1,
\r
8508 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8509 DisplayError(move, 0);
\r
8515 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8516 DrawPosition(FALSE, boards[currentMove]);
\r
8517 DisplayBothClocks();
\r
8518 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8519 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8521 (void) StopLoadGameTimer();
\r
8522 gameFileFP = NULL;
\r
8523 cmailOldMove = forwardMostMove;
\r
8526 /* currentMoveString is set as a side-effect of yylex */
\r
8527 strcat(currentMoveString, "\n");
\r
8528 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8530 thinkOutput[0] = NULLCHAR;
\r
8531 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8532 currentMove = forwardMostMove;
\r
8537 /* Load the nth game from the given file */
\r
8539 LoadGameFromFile(filename, n, title, useList)
\r
8543 /*Boolean*/ int useList;
\r
8546 char buf[MSG_SIZ];
\r
8548 if (strcmp(filename, "-") == 0) {
\r
8552 f = fopen(filename, "rb");
\r
8554 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8555 DisplayError(buf, errno);
\r
8559 if (fseek(f, 0, 0) == -1) {
\r
8560 /* f is not seekable; probably a pipe */
\r
8563 if (useList && n == 0) {
\r
8564 int error = GameListBuild(f);
\r
8566 DisplayError(_("Cannot build game list"), error);
\r
8567 } else if (!ListEmpty(&gameList) &&
\r
8568 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8569 GameListPopUp(f, title);
\r
8572 GameListDestroy();
\r
8575 if (n == 0) n = 1;
\r
8576 return LoadGame(f, n, title, FALSE);
\r
8581 MakeRegisteredMove()
\r
8583 int fromX, fromY, toX, toY;
\r
8585 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8586 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8589 if (appData.debugMode)
\r
8590 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8591 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8593 thinkOutput[0] = NULLCHAR;
\r
8594 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8595 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8596 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8597 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8598 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8599 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8600 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8601 ShowMove(fromX, fromY, toX, toY);
\r
8603 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8604 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8609 case MT_CHECKMATE:
\r
8610 if (WhiteOnMove(currentMove)) {
\r
8611 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8613 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8617 case MT_STALEMATE:
\r
8618 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8624 case CMAIL_RESIGN:
\r
8625 if (WhiteOnMove(currentMove)) {
\r
8626 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8628 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8632 case CMAIL_ACCEPT:
\r
8633 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8644 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8646 CmailLoadGame(f, gameNumber, title, useList)
\r
8654 if (gameNumber > nCmailGames) {
\r
8655 DisplayError(_("No more games in this message"), 0);
\r
8658 if (f == lastLoadGameFP) {
\r
8659 int offset = gameNumber - lastLoadGameNumber;
\r
8660 if (offset == 0) {
\r
8661 cmailMsg[0] = NULLCHAR;
\r
8662 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8663 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8664 nCmailMovesRegistered--;
\r
8666 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8667 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8668 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8671 if (! RegisterMove()) return FALSE;
\r
8675 retVal = LoadGame(f, gameNumber, title, useList);
\r
8677 /* Make move registered during previous look at this game, if any */
\r
8678 MakeRegisteredMove();
\r
8680 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8681 commentList[currentMove]
\r
8682 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8683 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8689 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8691 ReloadGame(offset)
\r
8694 int gameNumber = lastLoadGameNumber + offset;
\r
8695 if (lastLoadGameFP == NULL) {
\r
8696 DisplayError(_("No game has been loaded yet"), 0);
\r
8699 if (gameNumber <= 0) {
\r
8700 DisplayError(_("Can't back up any further"), 0);
\r
8703 if (cmailMsgLoaded) {
\r
8704 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8705 lastLoadGameTitle, lastLoadGameUseList);
\r
8707 return LoadGame(lastLoadGameFP, gameNumber,
\r
8708 lastLoadGameTitle, lastLoadGameUseList);
\r
8714 /* Load the nth game from open file f */
\r
8716 LoadGame(f, gameNumber, title, useList)
\r
8723 char buf[MSG_SIZ];
\r
8724 int gn = gameNumber;
\r
8725 ListGame *lg = NULL;
\r
8726 int numPGNTags = 0;
\r
8728 GameMode oldGameMode;
\r
8729 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8731 if (appData.debugMode)
\r
8732 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8734 if (gameMode == Training )
\r
8735 SetTrainingModeOff();
\r
8737 oldGameMode = gameMode;
\r
8738 if (gameMode != BeginningOfGame) {
\r
8739 Reset(FALSE, TRUE);
\r
8743 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8744 fclose(lastLoadGameFP);
\r
8748 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8751 fseek(f, lg->offset, 0);
\r
8752 GameListHighlight(gameNumber);
\r
8756 DisplayError(_("Game number out of range"), 0);
\r
8760 GameListDestroy();
\r
8761 if (fseek(f, 0, 0) == -1) {
\r
8762 if (f == lastLoadGameFP ?
\r
8763 gameNumber == lastLoadGameNumber + 1 :
\r
8764 gameNumber == 1) {
\r
8767 DisplayError(_("Can't seek on game file"), 0);
\r
8772 lastLoadGameFP = f;
\r
8773 lastLoadGameNumber = gameNumber;
\r
8774 strcpy(lastLoadGameTitle, title);
\r
8775 lastLoadGameUseList = useList;
\r
8779 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8780 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8781 lg->gameInfo.black);
\r
8782 DisplayTitle(buf);
\r
8783 } else if (*title != NULLCHAR) {
\r
8784 if (gameNumber > 1) {
\r
8785 sprintf(buf, "%s %d", title, gameNumber);
\r
8786 DisplayTitle(buf);
\r
8788 DisplayTitle(title);
\r
8792 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8793 gameMode = PlayFromGameFile;
\r
8797 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8798 CopyBoard(boards[0], initialPosition);
\r
8802 * Skip the first gn-1 games in the file.
\r
8803 * Also skip over anything that precedes an identifiable
\r
8804 * start of game marker, to avoid being confused by
\r
8805 * garbage at the start of the file. Currently
\r
8806 * recognized start of game markers are the move number "1",
\r
8807 * the pattern "gnuchess .* game", the pattern
\r
8808 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8809 * A game that starts with one of the latter two patterns
\r
8810 * will also have a move number 1, possibly
\r
8811 * following a position diagram.
\r
8812 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8813 * start with an unnumbered move. Does that break anything?
\r
8815 cm = lastLoadGameStart = (ChessMove) 0;
\r
8817 yyboardindex = forwardMostMove;
\r
8818 cm = (ChessMove) yylex();
\r
8820 case (ChessMove) 0:
\r
8821 if (cmailMsgLoaded) {
\r
8822 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8824 Reset(TRUE, TRUE);
\r
8825 DisplayError(_("Game not found in file"), 0);
\r
8829 case GNUChessGame:
\r
8832 lastLoadGameStart = cm;
\r
8835 case MoveNumberOne:
\r
8836 switch (lastLoadGameStart) {
\r
8837 case GNUChessGame:
\r
8841 case MoveNumberOne:
\r
8842 case (ChessMove) 0:
\r
8843 gn--; /* count this game */
\r
8844 lastLoadGameStart = cm;
\r
8853 switch (lastLoadGameStart) {
\r
8854 case GNUChessGame:
\r
8856 case MoveNumberOne:
\r
8857 case (ChessMove) 0:
\r
8858 gn--; /* count this game */
\r
8859 lastLoadGameStart = cm;
\r
8862 lastLoadGameStart = cm; /* game counted already */
\r
8870 yyboardindex = forwardMostMove;
\r
8871 cm = (ChessMove) yylex();
\r
8872 } while (cm == PGNTag || cm == Comment);
\r
8879 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8880 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8881 != CMAIL_OLD_RESULT) {
\r
8882 nCmailResults ++ ;
\r
8883 cmailResult[ CMAIL_MAX_GAMES
\r
8884 - gn - 1] = CMAIL_OLD_RESULT;
\r
8890 /* Only a NormalMove can be at the start of a game
\r
8891 * without a position diagram. */
\r
8892 if (lastLoadGameStart == (ChessMove) 0) {
\r
8894 lastLoadGameStart = MoveNumberOne;
\r
8903 if (appData.debugMode)
\r
8904 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8906 if (cm == XBoardGame) {
\r
8907 /* Skip any header junk before position diagram and/or move 1 */
\r
8909 yyboardindex = forwardMostMove;
\r
8910 cm = (ChessMove) yylex();
\r
8912 if (cm == (ChessMove) 0 ||
\r
8913 cm == GNUChessGame || cm == XBoardGame) {
\r
8914 /* Empty game; pretend end-of-file and handle later */
\r
8915 cm = (ChessMove) 0;
\r
8919 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8920 cm == PGNTag || cm == Comment)
\r
8923 } else if (cm == GNUChessGame) {
\r
8924 if (gameInfo.event != NULL) {
\r
8925 free(gameInfo.event);
\r
8927 gameInfo.event = StrSave(yy_text);
\r
8930 startedFromSetupPosition = FALSE;
\r
8931 while (cm == PGNTag) {
\r
8932 if (appData.debugMode)
\r
8933 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8934 err = ParsePGNTag(yy_text, &gameInfo);
\r
8935 if (!err) numPGNTags++;
\r
8937 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8938 if(gameInfo.variant != oldVariant) {
\r
8939 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8940 InitPosition(TRUE);
\r
8941 oldVariant = gameInfo.variant;
\r
8942 if (appData.debugMode)
\r
8943 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8947 if (gameInfo.fen != NULL) {
\r
8948 Board initial_position;
\r
8949 startedFromSetupPosition = TRUE;
\r
8950 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8951 Reset(TRUE, TRUE);
\r
8952 DisplayError(_("Bad FEN position in file"), 0);
\r
8955 CopyBoard(boards[0], initial_position);
\r
8956 if (blackPlaysFirst) {
\r
8957 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8958 CopyBoard(boards[1], initial_position);
\r
8959 strcpy(moveList[0], "");
\r
8960 strcpy(parseList[0], "");
\r
8961 timeRemaining[0][1] = whiteTimeRemaining;
\r
8962 timeRemaining[1][1] = blackTimeRemaining;
\r
8963 if (commentList[0] != NULL) {
\r
8964 commentList[1] = commentList[0];
\r
8965 commentList[0] = NULL;
\r
8968 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8970 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8972 initialRulePlies = FENrulePlies;
\r
8973 epStatus[forwardMostMove] = FENepStatus;
\r
8974 for( i=0; i< nrCastlingRights; i++ )
\r
8975 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8977 yyboardindex = forwardMostMove;
\r
8978 free(gameInfo.fen);
\r
8979 gameInfo.fen = NULL;
\r
8982 yyboardindex = forwardMostMove;
\r
8983 cm = (ChessMove) yylex();
\r
8985 /* Handle comments interspersed among the tags */
\r
8986 while (cm == Comment) {
\r
8988 if (appData.debugMode)
\r
8989 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8991 if (*p == '{' || *p == '[' || *p == '(') {
\r
8992 p[strlen(p) - 1] = NULLCHAR;
\r
8995 while (*p == '\n') p++;
\r
8996 AppendComment(currentMove, p);
\r
8997 yyboardindex = forwardMostMove;
\r
8998 cm = (ChessMove) yylex();
\r
9002 /* don't rely on existence of Event tag since if game was
\r
9003 * pasted from clipboard the Event tag may not exist
\r
9005 if (numPGNTags > 0){
\r
9007 if (gameInfo.variant == VariantNormal) {
\r
9008 gameInfo.variant = StringToVariant(gameInfo.event);
\r
9011 if( appData.autoDisplayTags ) {
\r
9012 tags = PGNTags(&gameInfo);
\r
9013 TagsPopUp(tags, CmailMsg());
\r
9018 /* Make something up, but don't display it now */
\r
9023 if (cm == PositionDiagram) {
\r
9026 Board initial_position;
\r
9028 if (appData.debugMode)
\r
9029 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9031 if (!startedFromSetupPosition) {
\r
9033 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9034 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9044 initial_position[i][j++] = CharToPiece(*p);
\r
9047 while (*p == ' ' || *p == '\t' ||
\r
9048 *p == '\n' || *p == '\r') p++;
\r
9050 if (strncmp(p, "black", strlen("black"))==0)
\r
9051 blackPlaysFirst = TRUE;
\r
9053 blackPlaysFirst = FALSE;
\r
9054 startedFromSetupPosition = TRUE;
\r
9056 CopyBoard(boards[0], initial_position);
\r
9057 if (blackPlaysFirst) {
\r
9058 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9059 CopyBoard(boards[1], initial_position);
\r
9060 strcpy(moveList[0], "");
\r
9061 strcpy(parseList[0], "");
\r
9062 timeRemaining[0][1] = whiteTimeRemaining;
\r
9063 timeRemaining[1][1] = blackTimeRemaining;
\r
9064 if (commentList[0] != NULL) {
\r
9065 commentList[1] = commentList[0];
\r
9066 commentList[0] = NULL;
\r
9069 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9072 yyboardindex = forwardMostMove;
\r
9073 cm = (ChessMove) yylex();
\r
9076 if (first.pr == NoProc) {
\r
9077 StartChessProgram(&first);
\r
9079 InitChessProgram(&first, FALSE);
\r
9080 SendToProgram("force\n", &first);
\r
9081 if (startedFromSetupPosition) {
\r
9082 SendBoard(&first, forwardMostMove);
\r
9083 if (appData.debugMode) {
\r
9084 fprintf(debugFP, "Load Game\n");
\r
9086 DisplayBothClocks();
\r
9089 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9090 loadFlag = appData.suppressLoadMoves;
\r
9092 while (cm == Comment) {
\r
9094 if (appData.debugMode)
\r
9095 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9097 if (*p == '{' || *p == '[' || *p == '(') {
\r
9098 p[strlen(p) - 1] = NULLCHAR;
\r
9101 while (*p == '\n') p++;
\r
9102 AppendComment(currentMove, p);
\r
9103 yyboardindex = forwardMostMove;
\r
9104 cm = (ChessMove) yylex();
\r
9107 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9108 cm == WhiteWins || cm == BlackWins ||
\r
9109 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9110 DisplayMessage("", _("No moves in game"));
\r
9111 if (cmailMsgLoaded) {
\r
9112 if (appData.debugMode)
\r
9113 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9114 ClearHighlights();
\r
9117 DrawPosition(FALSE, boards[currentMove]);
\r
9118 DisplayBothClocks();
\r
9119 gameMode = EditGame;
\r
9121 gameFileFP = NULL;
\r
9126 // [HGM] PV info: routine tests if comment empty
\r
9127 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9128 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9130 if (!matchMode && appData.timeDelay != 0)
\r
9131 DrawPosition(FALSE, boards[currentMove]);
\r
9133 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9134 programStats.ok_to_send = 1;
\r
9137 /* if the first token after the PGN tags is a move
\r
9138 * and not move number 1, retrieve it from the parser
\r
9140 if (cm != MoveNumberOne)
\r
9141 LoadGameOneMove(cm);
\r
9143 /* load the remaining moves from the file */
\r
9144 while (LoadGameOneMove((ChessMove)0)) {
\r
9145 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9146 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9149 /* rewind to the start of the game */
\r
9150 currentMove = backwardMostMove;
\r
9152 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9154 if (oldGameMode == AnalyzeFile ||
\r
9155 oldGameMode == AnalyzeMode) {
\r
9156 AnalyzeFileEvent();
\r
9159 if (matchMode || appData.timeDelay == 0) {
\r
9161 gameMode = EditGame;
\r
9163 } else if (appData.timeDelay > 0) {
\r
9164 AutoPlayGameLoop();
\r
9167 if (appData.debugMode)
\r
9168 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9170 loadFlag = 0; /* [HGM] true game starts */
\r
9174 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9176 ReloadPosition(offset)
\r
9179 int positionNumber = lastLoadPositionNumber + offset;
\r
9180 if (lastLoadPositionFP == NULL) {
\r
9181 DisplayError(_("No position has been loaded yet"), 0);
\r
9184 if (positionNumber <= 0) {
\r
9185 DisplayError(_("Can't back up any further"), 0);
\r
9188 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9189 lastLoadPositionTitle);
\r
9192 /* Load the nth position from the given file */
\r
9194 LoadPositionFromFile(filename, n, title)
\r
9200 char buf[MSG_SIZ];
\r
9202 if (strcmp(filename, "-") == 0) {
\r
9203 return LoadPosition(stdin, n, "stdin");
\r
9205 f = fopen(filename, "rb");
\r
9207 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9208 DisplayError(buf, errno);
\r
9211 return LoadPosition(f, n, title);
\r
9216 /* Load the nth position from the given open file, and close it */
\r
9218 LoadPosition(f, positionNumber, title)
\r
9220 int positionNumber;
\r
9223 char *p, line[MSG_SIZ];
\r
9224 Board initial_position;
\r
9225 int i, j, fenMode, pn;
\r
9227 if (gameMode == Training )
\r
9228 SetTrainingModeOff();
\r
9230 if (gameMode != BeginningOfGame) {
\r
9231 Reset(FALSE, TRUE);
\r
9233 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9234 fclose(lastLoadPositionFP);
\r
9236 if (positionNumber == 0) positionNumber = 1;
\r
9237 lastLoadPositionFP = f;
\r
9238 lastLoadPositionNumber = positionNumber;
\r
9239 strcpy(lastLoadPositionTitle, title);
\r
9240 if (first.pr == NoProc) {
\r
9241 StartChessProgram(&first);
\r
9242 InitChessProgram(&first, FALSE);
\r
9244 pn = positionNumber;
\r
9245 if (positionNumber < 0) {
\r
9246 /* Negative position number means to seek to that byte offset */
\r
9247 if (fseek(f, -positionNumber, 0) == -1) {
\r
9248 DisplayError(_("Can't seek on position file"), 0);
\r
9253 if (fseek(f, 0, 0) == -1) {
\r
9254 if (f == lastLoadPositionFP ?
\r
9255 positionNumber == lastLoadPositionNumber + 1 :
\r
9256 positionNumber == 1) {
\r
9259 DisplayError(_("Can't seek on position file"), 0);
\r
9264 /* See if this file is FEN or old-style xboard */
\r
9265 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9266 DisplayError(_("Position not found in file"), 0);
\r
9270 switch (line[0]) {
\r
9271 case '#': case 'x':
\r
9275 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9276 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9277 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9278 case '7': case '8': case '9':
\r
9279 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9280 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9281 case 'C': case 'W': case 'c': case 'w':
\r
9286 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9287 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9291 if (fenMode || line[0] == '#') pn--;
\r
9293 /* skip positions before number pn */
\r
9294 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9295 Reset(TRUE, TRUE);
\r
9296 DisplayError(_("Position not found in file"), 0);
\r
9299 if (fenMode || line[0] == '#') pn--;
\r
9304 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9305 DisplayError(_("Bad FEN position in file"), 0);
\r
9309 (void) fgets(line, MSG_SIZ, f);
\r
9310 (void) fgets(line, MSG_SIZ, f);
\r
9312 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9313 (void) fgets(line, MSG_SIZ, f);
\r
9314 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9317 initial_position[i][j++] = CharToPiece(*p);
\r
9321 blackPlaysFirst = FALSE;
\r
9323 (void) fgets(line, MSG_SIZ, f);
\r
9324 if (strncmp(line, "black", strlen("black"))==0)
\r
9325 blackPlaysFirst = TRUE;
\r
9328 startedFromSetupPosition = TRUE;
\r
9330 SendToProgram("force\n", &first);
\r
9331 CopyBoard(boards[0], initial_position);
\r
9332 if (blackPlaysFirst) {
\r
9333 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9334 strcpy(moveList[0], "");
\r
9335 strcpy(parseList[0], "");
\r
9336 CopyBoard(boards[1], initial_position);
\r
9337 DisplayMessage("", _("Black to play"));
\r
9339 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9340 DisplayMessage("", _("White to play"));
\r
9342 /* [HGM] copy FEN attributes as well */
\r
9344 initialRulePlies = FENrulePlies;
\r
9345 epStatus[forwardMostMove] = FENepStatus;
\r
9346 for( i=0; i< nrCastlingRights; i++ )
\r
9347 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9349 SendBoard(&first, forwardMostMove);
\r
9350 if (appData.debugMode) {
\r
9352 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9353 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9354 fprintf(debugFP, "Load Position\n");
\r
9357 if (positionNumber > 1) {
\r
9358 sprintf(line, "%s %d", title, positionNumber);
\r
9359 DisplayTitle(line);
\r
9361 DisplayTitle(title);
\r
9363 gameMode = EditGame;
\r
9366 timeRemaining[0][1] = whiteTimeRemaining;
\r
9367 timeRemaining[1][1] = blackTimeRemaining;
\r
9368 DrawPosition(FALSE, boards[currentMove]);
\r
9375 CopyPlayerNameIntoFileName(dest, src)
\r
9376 char **dest, *src;
\r
9378 while (*src != NULLCHAR && *src != ',') {
\r
9379 if (*src == ' ') {
\r
9383 *(*dest)++ = *src++;
\r
9388 char *DefaultFileName(ext)
\r
9391 static char def[MSG_SIZ];
\r
9394 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9396 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9398 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9402 def[0] = NULLCHAR;
\r
9407 /* Save the current game to the given file */
\r
9409 SaveGameToFile(filename, append)
\r
9414 char buf[MSG_SIZ];
\r
9416 if (strcmp(filename, "-") == 0) {
\r
9417 return SaveGame(stdout, 0, NULL);
\r
9419 f = fopen(filename, append ? "a" : "w");
\r
9421 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9422 DisplayError(buf, errno);
\r
9425 return SaveGame(f, 0, NULL);
\r
9434 static char buf[MSG_SIZ];
\r
9437 p = strchr(str, ' ');
\r
9438 if (p == NULL) return str;
\r
9439 strncpy(buf, str, p - str);
\r
9440 buf[p - str] = NULLCHAR;
\r
9444 #define PGN_MAX_LINE 75
\r
9446 #define PGN_SIDE_WHITE 0
\r
9447 #define PGN_SIDE_BLACK 1
\r
9450 static int FindFirstMoveOutOfBook( int side )
\r
9454 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9455 int index = backwardMostMove;
\r
9456 int has_book_hit = 0;
\r
9458 if( (index % 2) != side ) {
\r
9462 while( index < forwardMostMove ) {
\r
9463 /* Check to see if engine is in book */
\r
9464 int depth = pvInfoList[index].depth;
\r
9465 int score = pvInfoList[index].score;
\r
9468 if( depth <= 2 ) {
\r
9471 else if( score == 0 && depth == 63 ) {
\r
9472 in_book = 1; /* Zappa */
\r
9474 else if( score == 2 && depth == 99 ) {
\r
9475 in_book = 1; /* Abrok */
\r
9478 has_book_hit += in_book;
\r
9494 void GetOutOfBookInfo( char * buf )
\r
9498 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9500 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9501 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9505 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9506 for( i=0; i<2; i++ ) {
\r
9510 if( i > 0 && oob[0] >= 0 ) {
\r
9511 strcat( buf, " " );
\r
9514 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9515 sprintf( buf+strlen(buf), "%s%.2f",
\r
9516 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9517 pvInfoList[idx].score / 100.0 );
\r
9523 /* Save game in PGN style and close the file */
\r
9528 int i, offset, linelen, newblock;
\r
9530 // char *movetext;
\r
9532 int movelen, numlen, blank;
\r
9533 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9535 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9537 tm = time((time_t *) NULL);
\r
9539 PrintPGNTags(f, &gameInfo);
\r
9541 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9542 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9543 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9544 fprintf(f, "\n{--------------\n");
\r
9545 PrintPosition(f, backwardMostMove);
\r
9546 fprintf(f, "--------------}\n");
\r
9550 /* [AS] Out of book annotation */
\r
9551 if( appData.saveOutOfBookInfo ) {
\r
9554 GetOutOfBookInfo( buf );
\r
9556 if( buf[0] != '\0' ) {
\r
9557 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9564 i = backwardMostMove;
\r
9568 while (i < forwardMostMove) {
\r
9569 /* Print comments preceding this move */
\r
9570 if (commentList[i] != NULL) {
\r
9571 if (linelen > 0) fprintf(f, "\n");
\r
9572 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9577 /* Format move number */
\r
9578 if ((i % 2) == 0) {
\r
9579 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9582 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9584 numtext[0] = NULLCHAR;
\r
9587 numlen = strlen(numtext);
\r
9590 /* Print move number */
\r
9591 blank = linelen > 0 && numlen > 0;
\r
9592 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9601 fprintf(f, numtext);
\r
9602 linelen += numlen;
\r
9605 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9608 blank = linelen > 0 && movelen > 0;
\r
9609 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9618 fprintf(f, parseList[i]);
\r
9619 linelen += movelen;
\r
9621 /* [AS] Add PV info if present */
\r
9622 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9623 /* [HGM] add time */
\r
9624 char buf[MSG_SIZ]; int seconds = 0;
\r
9627 if(i >= backwardMostMove) {
\r
9628 if(WhiteOnMove(i))
\r
9629 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9630 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9632 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9633 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9635 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9637 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9640 if( seconds <= 0) buf[0] = 0; else
\r
9641 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9642 seconds = (seconds + 4)/10; // round to full seconds
\r
9643 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9644 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9647 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9648 pvInfoList[i].score >= 0 ? "+" : "",
\r
9649 pvInfoList[i].score / 100.0,
\r
9650 pvInfoList[i].depth,
\r
9653 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9655 /* Print score/depth */
\r
9656 blank = linelen > 0 && movelen > 0;
\r
9657 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9666 fprintf(f, move_buffer);
\r
9667 linelen += movelen;
\r
9673 /* Start a new line */
\r
9674 if (linelen > 0) fprintf(f, "\n");
\r
9676 /* Print comments after last move */
\r
9677 if (commentList[i] != NULL) {
\r
9678 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9681 /* Print result */
\r
9682 if (gameInfo.resultDetails != NULL &&
\r
9683 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9684 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9685 PGNResult(gameInfo.result));
\r
9687 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9694 /* Save game in old style and close the file */
\r
9696 SaveGameOldStyle(f)
\r
9702 tm = time((time_t *) NULL);
\r
9704 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9705 PrintOpponents(f);
\r
9707 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9708 fprintf(f, "\n[--------------\n");
\r
9709 PrintPosition(f, backwardMostMove);
\r
9710 fprintf(f, "--------------]\n");
\r
9715 i = backwardMostMove;
\r
9716 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9718 while (i < forwardMostMove) {
\r
9719 if (commentList[i] != NULL) {
\r
9720 fprintf(f, "[%s]\n", commentList[i]);
\r
9723 if ((i % 2) == 1) {
\r
9724 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9727 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9729 if (commentList[i] != NULL) {
\r
9733 if (i >= forwardMostMove) {
\r
9737 fprintf(f, "%s\n", parseList[i]);
\r
9742 if (commentList[i] != NULL) {
\r
9743 fprintf(f, "[%s]\n", commentList[i]);
\r
9746 /* This isn't really the old style, but it's close enough */
\r
9747 if (gameInfo.resultDetails != NULL &&
\r
9748 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9749 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9750 gameInfo.resultDetails);
\r
9752 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9759 /* Save the current game to open file f and close the file */
\r
9761 SaveGame(f, dummy, dummy2)
\r
9766 if (gameMode == EditPosition) EditPositionDone();
\r
9767 if (appData.oldSaveStyle)
\r
9768 return SaveGameOldStyle(f);
\r
9770 return SaveGamePGN(f);
\r
9773 /* Save the current position to the given file */
\r
9775 SavePositionToFile(filename)
\r
9779 char buf[MSG_SIZ];
\r
9781 if (strcmp(filename, "-") == 0) {
\r
9782 return SavePosition(stdout, 0, NULL);
\r
9784 f = fopen(filename, "a");
\r
9786 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9787 DisplayError(buf, errno);
\r
9790 SavePosition(f, 0, NULL);
\r
9796 /* Save the current position to the given open file and close the file */
\r
9798 SavePosition(f, dummy, dummy2)
\r
9806 if (appData.oldSaveStyle) {
\r
9807 tm = time((time_t *) NULL);
\r
9809 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9810 PrintOpponents(f);
\r
9811 fprintf(f, "[--------------\n");
\r
9812 PrintPosition(f, currentMove);
\r
9813 fprintf(f, "--------------]\n");
\r
9815 fen = PositionToFEN(currentMove, 1);
\r
9816 fprintf(f, "%s\n", fen);
\r
9824 ReloadCmailMsgEvent(unregister)
\r
9828 static char *inFilename = NULL;
\r
9829 static char *outFilename;
\r
9831 struct stat inbuf, outbuf;
\r
9834 /* Any registered moves are unregistered if unregister is set, */
\r
9835 /* i.e. invoked by the signal handler */
\r
9837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9838 cmailMoveRegistered[i] = FALSE;
\r
9839 if (cmailCommentList[i] != NULL) {
\r
9840 free(cmailCommentList[i]);
\r
9841 cmailCommentList[i] = NULL;
\r
9844 nCmailMovesRegistered = 0;
\r
9847 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9848 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9850 nCmailResults = 0;
\r
9852 if (inFilename == NULL) {
\r
9853 /* Because the filenames are static they only get malloced once */
\r
9854 /* and they never get freed */
\r
9855 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9856 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9858 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9859 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9862 status = stat(outFilename, &outbuf);
\r
9864 cmailMailedMove = FALSE;
\r
9866 status = stat(inFilename, &inbuf);
\r
9867 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9870 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9871 counts the games, notes how each one terminated, etc.
\r
9873 It would be nice to remove this kludge and instead gather all
\r
9874 the information while building the game list. (And to keep it
\r
9875 in the game list nodes instead of having a bunch of fixed-size
\r
9876 parallel arrays.) Note this will require getting each game's
\r
9877 termination from the PGN tags, as the game list builder does
\r
9878 not process the game moves. --mann
\r
9880 cmailMsgLoaded = TRUE;
\r
9881 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9883 /* Load first game in the file or popup game menu */
\r
9884 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9886 #endif /* !WIN32 */
\r
9894 char string[MSG_SIZ];
\r
9896 if ( cmailMailedMove
\r
9897 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9898 return TRUE; /* Allow free viewing */
\r
9901 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9902 /* with the move registered when the conditions for registering no */
\r
9904 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9905 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9906 nCmailMovesRegistered --;
\r
9908 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9910 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9911 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9915 if (cmailOldMove == -1) {
\r
9916 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9920 if (currentMove > cmailOldMove + 1) {
\r
9921 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9925 if (currentMove < cmailOldMove) {
\r
9926 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9930 if (forwardMostMove > currentMove) {
\r
9931 /* Silently truncate extra moves */
\r
9935 if ( (currentMove == cmailOldMove + 1)
\r
9936 || ( (currentMove == cmailOldMove)
\r
9937 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9938 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9939 if (gameInfo.result != GameUnfinished) {
\r
9940 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9943 if (commentList[currentMove] != NULL) {
\r
9944 cmailCommentList[lastLoadGameNumber - 1]
\r
9945 = StrSave(commentList[currentMove]);
\r
9947 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9949 if (appData.debugMode)
\r
9950 fprintf(debugFP, "Saving %s for game %d\n",
\r
9951 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9954 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9956 f = fopen(string, "w");
\r
9957 if (appData.oldSaveStyle) {
\r
9958 SaveGameOldStyle(f); /* also closes the file */
\r
9960 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9961 f = fopen(string, "w");
\r
9962 SavePosition(f, 0, NULL); /* also closes the file */
\r
9964 fprintf(f, "{--------------\n");
\r
9965 PrintPosition(f, currentMove);
\r
9966 fprintf(f, "--------------}\n\n");
\r
9968 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9971 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9972 nCmailMovesRegistered ++;
\r
9973 } else if (nCmailGames == 1) {
\r
9974 DisplayError(_("You have not made a move yet"), 0);
\r
9985 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9986 FILE *commandOutput;
\r
9987 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9988 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9994 if (! cmailMsgLoaded) {
\r
9995 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9999 if (nCmailGames == nCmailResults) {
\r
10000 DisplayError(_("No unfinished games"), 0);
\r
10004 #if CMAIL_PROHIBIT_REMAIL
\r
10005 if (cmailMailedMove) {
\r
10006 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
10007 DisplayError(msg, 0);
\r
10012 if (! (cmailMailedMove || RegisterMove())) return;
\r
10014 if ( cmailMailedMove
\r
10015 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10016 sprintf(string, partCommandString,
\r
10017 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10018 commandOutput = popen(string, "r");
\r
10020 if (commandOutput == NULL) {
\r
10021 DisplayError(_("Failed to invoke cmail"), 0);
\r
10023 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10024 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10026 if (nBuffers > 1) {
\r
10027 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10028 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10029 nBytes = MSG_SIZ - 1;
\r
10031 (void) memcpy(msg, buffer, nBytes);
\r
10033 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10035 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10036 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10039 for (i = 0; i < nCmailGames; i ++) {
\r
10040 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10041 archived = FALSE;
\r
10045 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10047 sprintf(buffer, "%s/%s.%s.archive",
\r
10049 appData.cmailGameName,
\r
10051 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10052 cmailMsgLoaded = FALSE;
\r
10056 DisplayInformation(msg);
\r
10057 pclose(commandOutput);
\r
10060 if ((*cmailMsg) != '\0') {
\r
10061 DisplayInformation(cmailMsg);
\r
10066 #endif /* !WIN32 */
\r
10075 int prependComma = 0;
\r
10077 char string[MSG_SIZ]; /* Space for game-list */
\r
10080 if (!cmailMsgLoaded) return "";
\r
10082 if (cmailMailedMove) {
\r
10083 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10085 /* Create a list of games left */
\r
10086 sprintf(string, "[");
\r
10087 for (i = 0; i < nCmailGames; i ++) {
\r
10088 if (! ( cmailMoveRegistered[i]
\r
10089 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10090 if (prependComma) {
\r
10091 sprintf(number, ",%d", i + 1);
\r
10093 sprintf(number, "%d", i + 1);
\r
10094 prependComma = 1;
\r
10097 strcat(string, number);
\r
10100 strcat(string, "]");
\r
10102 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10103 switch (nCmailGames) {
\r
10105 sprintf(cmailMsg,
\r
10106 _("Still need to make move for game\n"));
\r
10110 sprintf(cmailMsg,
\r
10111 _("Still need to make moves for both games\n"));
\r
10115 sprintf(cmailMsg,
\r
10116 _("Still need to make moves for all %d games\n"),
\r
10121 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10123 sprintf(cmailMsg,
\r
10124 _("Still need to make a move for game %s\n"),
\r
10129 if (nCmailResults == nCmailGames) {
\r
10130 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10132 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10137 sprintf(cmailMsg,
\r
10138 _("Still need to make moves for games %s\n"),
\r
10144 #endif /* WIN32 */
\r
10150 if (gameMode == Training)
\r
10151 SetTrainingModeOff();
\r
10153 Reset(TRUE, TRUE);
\r
10154 cmailMsgLoaded = FALSE;
\r
10155 if (appData.icsActive) {
\r
10156 SendToICS(ics_prefix);
\r
10157 SendToICS("refresh\n");
\r
10162 ExitEvent(status)
\r
10166 if (exiting > 2) {
\r
10167 /* Give up on clean exit */
\r
10170 if (exiting > 1) {
\r
10171 /* Keep trying for clean exit */
\r
10175 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10177 if (telnetISR != NULL) {
\r
10178 RemoveInputSource(telnetISR);
\r
10180 if (icsPR != NoProc) {
\r
10181 DestroyChildProcess(icsPR, TRUE);
\r
10184 /* Save game if resource set and not already saved by GameEnds() */
\r
10185 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10186 && forwardMostMove > 0) {
\r
10187 if (*appData.saveGameFile != NULLCHAR) {
\r
10188 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10189 } else if (appData.autoSaveGames) {
\r
10192 if (*appData.savePositionFile != NULLCHAR) {
\r
10193 SavePositionToFile(appData.savePositionFile);
\r
10196 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10198 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10199 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10201 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10202 /* make sure this other one finishes before killing it! */
\r
10203 if(endingGame) { int count = 0;
\r
10204 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10205 while(endingGame && count++ < 10) DoSleep(1);
\r
10206 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10209 /* Kill off chess programs */
\r
10210 if (first.pr != NoProc) {
\r
10211 ExitAnalyzeMode();
\r
10213 DoSleep( appData.delayBeforeQuit );
\r
10214 SendToProgram("quit\n", &first);
\r
10215 DoSleep( appData.delayAfterQuit );
\r
10216 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10218 if (second.pr != NoProc) {
\r
10219 DoSleep( appData.delayBeforeQuit );
\r
10220 SendToProgram("quit\n", &second);
\r
10221 DoSleep( appData.delayAfterQuit );
\r
10222 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10224 if (first.isr != NULL) {
\r
10225 RemoveInputSource(first.isr);
\r
10227 if (second.isr != NULL) {
\r
10228 RemoveInputSource(second.isr);
\r
10231 ShutDownFrontEnd();
\r
10238 if (appData.debugMode)
\r
10239 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10243 if (gameMode == MachinePlaysWhite ||
\r
10244 gameMode == MachinePlaysBlack) {
\r
10247 DisplayBothClocks();
\r
10249 if (gameMode == PlayFromGameFile) {
\r
10250 if (appData.timeDelay >= 0)
\r
10251 AutoPlayGameLoop();
\r
10252 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10253 Reset(FALSE, TRUE);
\r
10254 SendToICS(ics_prefix);
\r
10255 SendToICS("refresh\n");
\r
10256 } else if (currentMove < forwardMostMove) {
\r
10257 ForwardInner(forwardMostMove);
\r
10259 pauseExamInvalid = FALSE;
\r
10261 switch (gameMode) {
\r
10264 case IcsExamining:
\r
10265 pauseExamForwardMostMove = forwardMostMove;
\r
10266 pauseExamInvalid = FALSE;
\r
10267 /* fall through */
\r
10268 case IcsObserving:
\r
10269 case IcsPlayingWhite:
\r
10270 case IcsPlayingBlack:
\r
10274 case PlayFromGameFile:
\r
10275 (void) StopLoadGameTimer();
\r
10279 case BeginningOfGame:
\r
10280 if (appData.icsActive) return;
\r
10281 /* else fall through */
\r
10282 case MachinePlaysWhite:
\r
10283 case MachinePlaysBlack:
\r
10284 case TwoMachinesPlay:
\r
10285 if (forwardMostMove == 0)
\r
10286 return; /* don't pause if no one has moved */
\r
10287 if ((gameMode == MachinePlaysWhite &&
\r
10288 !WhiteOnMove(forwardMostMove)) ||
\r
10289 (gameMode == MachinePlaysBlack &&
\r
10290 WhiteOnMove(forwardMostMove))) {
\r
10301 EditCommentEvent()
\r
10303 char title[MSG_SIZ];
\r
10305 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10306 strcpy(title, _("Edit comment"));
\r
10308 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10309 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10310 parseList[currentMove - 1]);
\r
10313 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10320 char *tags = PGNTags(&gameInfo);
\r
10321 EditTagsPopUp(tags);
\r
10326 AnalyzeModeEvent()
\r
10328 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10331 if (gameMode != AnalyzeFile) {
\r
10332 if (!appData.icsEngineAnalyze) {
\r
10334 if (gameMode != EditGame) return;
\r
10336 ResurrectChessProgram();
\r
10337 SendToProgram("analyze\n", &first);
\r
10338 first.analyzing = TRUE;
\r
10339 /*first.maybeThinking = TRUE;*/
\r
10340 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10341 AnalysisPopUp(_("Analysis"),
\r
10342 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10344 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10349 StartAnalysisClock();
\r
10350 GetTimeMark(&lastNodeCountTime);
\r
10351 lastNodeCount = 0;
\r
10355 AnalyzeFileEvent()
\r
10357 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10360 if (gameMode != AnalyzeMode) {
\r
10362 if (gameMode != EditGame) return;
\r
10363 ResurrectChessProgram();
\r
10364 SendToProgram("analyze\n", &first);
\r
10365 first.analyzing = TRUE;
\r
10366 /*first.maybeThinking = TRUE;*/
\r
10367 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10368 AnalysisPopUp(_("Analysis"),
\r
10369 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10371 gameMode = AnalyzeFile;
\r
10376 StartAnalysisClock();
\r
10377 GetTimeMark(&lastNodeCountTime);
\r
10378 lastNodeCount = 0;
\r
10382 MachineWhiteEvent()
\r
10384 char buf[MSG_SIZ];
\r
10385 char *bookHit = NULL;
\r
10387 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10391 if (gameMode == PlayFromGameFile ||
\r
10392 gameMode == TwoMachinesPlay ||
\r
10393 gameMode == Training ||
\r
10394 gameMode == AnalyzeMode ||
\r
10395 gameMode == EndOfGame)
\r
10398 if (gameMode == EditPosition)
\r
10399 EditPositionDone();
\r
10401 if (!WhiteOnMove(currentMove)) {
\r
10402 DisplayError(_("It is not White's turn"), 0);
\r
10406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10407 ExitAnalyzeMode();
\r
10409 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10410 gameMode == AnalyzeFile)
\r
10413 ResurrectChessProgram(); /* in case it isn't running */
\r
10414 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10415 gameMode = MachinePlaysWhite;
\r
10418 gameMode = MachinePlaysWhite;
\r
10422 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10423 DisplayTitle(buf);
\r
10424 if (first.sendName) {
\r
10425 sprintf(buf, "name %s\n", gameInfo.black);
\r
10426 SendToProgram(buf, &first);
\r
10428 if (first.sendTime) {
\r
10429 if (first.useColors) {
\r
10430 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10432 SendTimeRemaining(&first, TRUE);
\r
10434 if (first.useColors) {
\r
10435 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10437 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10438 SetMachineThinkingEnables();
\r
10439 first.maybeThinking = TRUE;
\r
10442 if (appData.autoFlipView && !flipView) {
\r
10443 flipView = !flipView;
\r
10444 DrawPosition(FALSE, NULL);
\r
10445 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10448 if(bookHit) { // [HGM] book: simulate book reply
\r
10449 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10451 programStats.nodes = programStats.depth = programStats.time =
\r
10452 programStats.score = programStats.got_only_move = 0;
\r
10453 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10455 strcpy(bookMove, "move ");
\r
10456 strcat(bookMove, bookHit);
\r
10457 HandleMachineMove(bookMove, &first);
\r
10462 MachineBlackEvent()
\r
10464 char buf[MSG_SIZ];
\r
10465 char *bookHit = NULL;
\r
10467 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10471 if (gameMode == PlayFromGameFile ||
\r
10472 gameMode == TwoMachinesPlay ||
\r
10473 gameMode == Training ||
\r
10474 gameMode == AnalyzeMode ||
\r
10475 gameMode == EndOfGame)
\r
10478 if (gameMode == EditPosition)
\r
10479 EditPositionDone();
\r
10481 if (WhiteOnMove(currentMove)) {
\r
10482 DisplayError(_("It is not Black's turn"), 0);
\r
10486 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10487 ExitAnalyzeMode();
\r
10489 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10490 gameMode == AnalyzeFile)
\r
10493 ResurrectChessProgram(); /* in case it isn't running */
\r
10494 gameMode = MachinePlaysBlack;
\r
10498 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10499 DisplayTitle(buf);
\r
10500 if (first.sendName) {
\r
10501 sprintf(buf, "name %s\n", gameInfo.white);
\r
10502 SendToProgram(buf, &first);
\r
10504 if (first.sendTime) {
\r
10505 if (first.useColors) {
\r
10506 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10508 SendTimeRemaining(&first, FALSE);
\r
10510 if (first.useColors) {
\r
10511 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10513 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10514 SetMachineThinkingEnables();
\r
10515 first.maybeThinking = TRUE;
\r
10518 if (appData.autoFlipView && flipView) {
\r
10519 flipView = !flipView;
\r
10520 DrawPosition(FALSE, NULL);
\r
10521 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10523 if(bookHit) { // [HGM] book: simulate book reply
\r
10524 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10526 programStats.nodes = programStats.depth = programStats.time =
\r
10527 programStats.score = programStats.got_only_move = 0;
\r
10528 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10530 strcpy(bookMove, "move ");
\r
10531 strcat(bookMove, bookHit);
\r
10532 HandleMachineMove(bookMove, &first);
\r
10538 DisplayTwoMachinesTitle()
\r
10540 char buf[MSG_SIZ];
\r
10541 if (appData.matchGames > 0) {
\r
10542 if (first.twoMachinesColor[0] == 'w') {
\r
10543 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10544 gameInfo.white, gameInfo.black,
\r
10545 first.matchWins, second.matchWins,
\r
10546 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10548 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10549 gameInfo.white, gameInfo.black,
\r
10550 second.matchWins, first.matchWins,
\r
10551 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10554 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10556 DisplayTitle(buf);
\r
10560 TwoMachinesEvent P((void))
\r
10563 char buf[MSG_SIZ];
\r
10564 ChessProgramState *onmove;
\r
10565 char *bookHit = NULL;
\r
10567 if (appData.noChessProgram) return;
\r
10569 switch (gameMode) {
\r
10570 case TwoMachinesPlay:
\r
10572 case MachinePlaysWhite:
\r
10573 case MachinePlaysBlack:
\r
10574 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10575 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10578 /* fall through */
\r
10579 case BeginningOfGame:
\r
10580 case PlayFromGameFile:
\r
10583 if (gameMode != EditGame) return;
\r
10585 case EditPosition:
\r
10586 EditPositionDone();
\r
10588 case AnalyzeMode:
\r
10589 case AnalyzeFile:
\r
10590 ExitAnalyzeMode();
\r
10597 forwardMostMove = currentMove;
\r
10598 ResurrectChessProgram(); /* in case first program isn't running */
\r
10600 if (second.pr == NULL) {
\r
10601 StartChessProgram(&second);
\r
10602 if (second.protocolVersion == 1) {
\r
10603 TwoMachinesEventIfReady();
\r
10605 /* kludge: allow timeout for initial "feature" command */
\r
10607 DisplayMessage("", _("Starting second chess program"));
\r
10608 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10612 DisplayMessage("", "");
\r
10613 InitChessProgram(&second, FALSE);
\r
10614 SendToProgram("force\n", &second);
\r
10615 if (startedFromSetupPosition) {
\r
10616 SendBoard(&second, backwardMostMove);
\r
10617 if (appData.debugMode) {
\r
10618 fprintf(debugFP, "Two Machines\n");
\r
10621 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10622 SendMoveToProgram(i, &second);
\r
10625 gameMode = TwoMachinesPlay;
\r
10629 DisplayTwoMachinesTitle();
\r
10630 firstMove = TRUE;
\r
10631 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10634 onmove = &second;
\r
10637 SendToProgram(first.computerString, &first);
\r
10638 if (first.sendName) {
\r
10639 sprintf(buf, "name %s\n", second.tidy);
\r
10640 SendToProgram(buf, &first);
\r
10642 SendToProgram(second.computerString, &second);
\r
10643 if (second.sendName) {
\r
10644 sprintf(buf, "name %s\n", first.tidy);
\r
10645 SendToProgram(buf, &second);
\r
10649 if (!first.sendTime || !second.sendTime) {
\r
10650 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10651 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10653 if (onmove->sendTime) {
\r
10654 if (onmove->useColors) {
\r
10655 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10657 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10659 if (onmove->useColors) {
\r
10660 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10662 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10663 // SendToProgram("go\n", onmove);
\r
10664 onmove->maybeThinking = TRUE;
\r
10665 SetMachineThinkingEnables();
\r
10669 if(bookHit) { // [HGM] book: simulate book reply
\r
10670 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10672 programStats.nodes = programStats.depth = programStats.time =
\r
10673 programStats.score = programStats.got_only_move = 0;
\r
10674 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10676 strcpy(bookMove, "move ");
\r
10677 strcat(bookMove, bookHit);
\r
10678 HandleMachineMove(bookMove, &first);
\r
10685 if (gameMode == Training) {
\r
10686 SetTrainingModeOff();
\r
10687 gameMode = PlayFromGameFile;
\r
10688 DisplayMessage("", _("Training mode off"));
\r
10690 gameMode = Training;
\r
10691 animateTraining = appData.animate;
\r
10693 /* make sure we are not already at the end of the game */
\r
10694 if (currentMove < forwardMostMove) {
\r
10695 SetTrainingModeOn();
\r
10696 DisplayMessage("", _("Training mode on"));
\r
10698 gameMode = PlayFromGameFile;
\r
10699 DisplayError(_("Already at end of game"), 0);
\r
10708 if (!appData.icsActive) return;
\r
10709 switch (gameMode) {
\r
10710 case IcsPlayingWhite:
\r
10711 case IcsPlayingBlack:
\r
10712 case IcsObserving:
\r
10714 case BeginningOfGame:
\r
10715 case IcsExamining:
\r
10721 case EditPosition:
\r
10722 EditPositionDone();
\r
10725 case AnalyzeMode:
\r
10726 case AnalyzeFile:
\r
10727 ExitAnalyzeMode();
\r
10735 gameMode = IcsIdle;
\r
10746 switch (gameMode) {
\r
10748 SetTrainingModeOff();
\r
10750 case MachinePlaysWhite:
\r
10751 case MachinePlaysBlack:
\r
10752 case BeginningOfGame:
\r
10753 SendToProgram("force\n", &first);
\r
10754 SetUserThinkingEnables();
\r
10756 case PlayFromGameFile:
\r
10757 (void) StopLoadGameTimer();
\r
10758 if (gameFileFP != NULL) {
\r
10759 gameFileFP = NULL;
\r
10762 case EditPosition:
\r
10763 EditPositionDone();
\r
10765 case AnalyzeMode:
\r
10766 case AnalyzeFile:
\r
10767 ExitAnalyzeMode();
\r
10768 SendToProgram("force\n", &first);
\r
10770 case TwoMachinesPlay:
\r
10771 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10772 ResurrectChessProgram();
\r
10773 SetUserThinkingEnables();
\r
10776 ResurrectChessProgram();
\r
10778 case IcsPlayingBlack:
\r
10779 case IcsPlayingWhite:
\r
10780 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10782 case IcsObserving:
\r
10783 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10785 case IcsExamining:
\r
10786 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10797 first.offeredDraw = second.offeredDraw = 0;
\r
10799 if (gameMode == PlayFromGameFile) {
\r
10800 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10801 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10802 DisplayTitle("");
\r
10805 if (gameMode == MachinePlaysWhite ||
\r
10806 gameMode == MachinePlaysBlack ||
\r
10807 gameMode == TwoMachinesPlay ||
\r
10808 gameMode == EndOfGame) {
\r
10809 i = forwardMostMove;
\r
10810 while (i > currentMove) {
\r
10811 SendToProgram("undo\n", &first);
\r
10814 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10815 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10816 DisplayBothClocks();
\r
10817 if (whiteFlag || blackFlag) {
\r
10818 whiteFlag = blackFlag = 0;
\r
10820 DisplayTitle("");
\r
10823 gameMode = EditGame;
\r
10830 EditPositionEvent()
\r
10832 if (gameMode == EditPosition) {
\r
10838 if (gameMode != EditGame) return;
\r
10840 gameMode = EditPosition;
\r
10843 if (currentMove > 0)
\r
10844 CopyBoard(boards[0], boards[currentMove]);
\r
10846 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10848 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10849 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10854 ExitAnalyzeMode()
\r
10856 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10857 if (appData.icsEngineAnalyze) {
\r
10858 appData.icsEngineAnalyze = FALSE;
\r
10860 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10862 if (first.analysisSupport && first.analyzing) {
\r
10863 SendToProgram("exit\n", &first);
\r
10864 first.analyzing = FALSE;
\r
10866 AnalysisPopDown();
\r
10867 thinkOutput[0] = NULLCHAR;
\r
10871 EditPositionDone()
\r
10873 startedFromSetupPosition = TRUE;
\r
10874 InitChessProgram(&first, FALSE);
\r
10875 SendToProgram("force\n", &first);
\r
10876 if (blackPlaysFirst) {
\r
10877 strcpy(moveList[0], "");
\r
10878 strcpy(parseList[0], "");
\r
10879 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10880 CopyBoard(boards[1], boards[0]);
\r
10881 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10883 epStatus[1] = epStatus[0];
\r
10884 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10887 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10889 SendBoard(&first, forwardMostMove);
\r
10890 if (appData.debugMode) {
\r
10891 fprintf(debugFP, "EditPosDone\n");
\r
10893 DisplayTitle("");
\r
10894 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10895 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10896 gameMode = EditGame;
\r
10898 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10899 ClearHighlights(); /* [AS] */
\r
10902 /* Pause for `ms' milliseconds */
\r
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10910 GetTimeMark(&m1);
\r
10912 GetTimeMark(&m2);
\r
10913 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10918 SendMultiLineToICS(buf)
\r
10921 char temp[MSG_SIZ+1], *p;
\r
10924 len = strlen(buf);
\r
10925 if (len > MSG_SIZ)
\r
10928 strncpy(temp, buf, len);
\r
10933 if (*p == '\n' || *p == '\r')
\r
10938 strcat(temp, "\n");
\r
10940 SendToPlayer(temp, strlen(temp));
\r
10944 SetWhiteToPlayEvent()
\r
10946 if (gameMode == EditPosition) {
\r
10947 blackPlaysFirst = FALSE;
\r
10948 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10949 } else if (gameMode == IcsExamining) {
\r
10950 SendToICS(ics_prefix);
\r
10951 SendToICS("tomove white\n");
\r
10956 SetBlackToPlayEvent()
\r
10958 if (gameMode == EditPosition) {
\r
10959 blackPlaysFirst = TRUE;
\r
10960 currentMove = 1; /* kludge */
\r
10961 DisplayBothClocks();
\r
10963 } else if (gameMode == IcsExamining) {
\r
10964 SendToICS(ics_prefix);
\r
10965 SendToICS("tomove black\n");
\r
10970 EditPositionMenuEvent(selection, x, y)
\r
10971 ChessSquare selection;
\r
10974 char buf[MSG_SIZ];
\r
10975 ChessSquare piece = boards[0][y][x];
\r
10977 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10979 switch (selection) {
\r
10981 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10982 SendToICS(ics_prefix);
\r
10983 SendToICS("bsetup clear\n");
\r
10984 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10985 SendToICS(ics_prefix);
\r
10986 SendToICS("clearboard\n");
\r
10988 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10989 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10990 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10991 if (gameMode == IcsExamining) {
\r
10992 if (boards[currentMove][y][x] != EmptySquare) {
\r
10993 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10994 AAA + x, ONE + y);
\r
10998 boards[0][y][x] = p;
\r
11003 if (gameMode == EditPosition) {
\r
11004 DrawPosition(FALSE, boards[0]);
\r
11009 SetWhiteToPlayEvent();
\r
11013 SetBlackToPlayEvent();
\r
11016 case EmptySquare:
\r
11017 if (gameMode == IcsExamining) {
\r
11018 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11021 boards[0][y][x] = EmptySquare;
\r
11022 DrawPosition(FALSE, boards[0]);
\r
11026 case PromotePiece:
\r
11027 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11028 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11029 selection = (ChessSquare) (PROMOTED piece);
\r
11030 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11031 else selection = (ChessSquare)((int)piece - 1);
\r
11032 goto defaultlabel;
\r
11034 case DemotePiece:
\r
11035 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11036 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11037 selection = (ChessSquare) (DEMOTED piece);
\r
11038 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11039 else selection = (ChessSquare)((int)piece + 1);
\r
11040 goto defaultlabel;
\r
11044 if(gameInfo.variant == VariantShatranj ||
\r
11045 gameInfo.variant == VariantXiangqi ||
\r
11046 gameInfo.variant == VariantCourier )
\r
11047 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11048 goto defaultlabel;
\r
11052 if(gameInfo.variant == VariantXiangqi)
\r
11053 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11054 if(gameInfo.variant == VariantKnightmate)
\r
11055 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11058 if (gameMode == IcsExamining) {
\r
11059 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11060 PieceToChar(selection), AAA + x, ONE + y);
\r
11063 boards[0][y][x] = selection;
\r
11064 DrawPosition(FALSE, boards[0]);
\r
11072 DropMenuEvent(selection, x, y)
\r
11073 ChessSquare selection;
\r
11076 ChessMove moveType;
\r
11078 switch (gameMode) {
\r
11079 case IcsPlayingWhite:
\r
11080 case MachinePlaysBlack:
\r
11081 if (!WhiteOnMove(currentMove)) {
\r
11082 DisplayMoveError(_("It is Black's turn"));
\r
11085 moveType = WhiteDrop;
\r
11087 case IcsPlayingBlack:
\r
11088 case MachinePlaysWhite:
\r
11089 if (WhiteOnMove(currentMove)) {
\r
11090 DisplayMoveError(_("It is White's turn"));
\r
11093 moveType = BlackDrop;
\r
11096 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11102 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11103 selection = (ChessSquare) ((int) selection
\r
11104 + (int) BlackPawn - (int) WhitePawn);
\r
11106 if (boards[currentMove][y][x] != EmptySquare) {
\r
11107 DisplayMoveError(_("That square is occupied"));
\r
11111 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11117 /* Accept a pending offer of any kind from opponent */
\r
11119 if (appData.icsActive) {
\r
11120 SendToICS(ics_prefix);
\r
11121 SendToICS("accept\n");
\r
11122 } else if (cmailMsgLoaded) {
\r
11123 if (currentMove == cmailOldMove &&
\r
11124 commentList[cmailOldMove] != NULL &&
\r
11125 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11126 "Black offers a draw" : "White offers a draw")) {
\r
11128 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11131 DisplayError(_("There is no pending offer on this move"), 0);
\r
11132 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11135 /* Not used for offers from chess program */
\r
11142 /* Decline a pending offer of any kind from opponent */
\r
11144 if (appData.icsActive) {
\r
11145 SendToICS(ics_prefix);
\r
11146 SendToICS("decline\n");
\r
11147 } else if (cmailMsgLoaded) {
\r
11148 if (currentMove == cmailOldMove &&
\r
11149 commentList[cmailOldMove] != NULL &&
\r
11150 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11151 "Black offers a draw" : "White offers a draw")) {
\r
11153 AppendComment(cmailOldMove, "Draw declined");
\r
11154 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11155 #endif /*NOTDEF*/
\r
11157 DisplayError(_("There is no pending offer on this move"), 0);
\r
11160 /* Not used for offers from chess program */
\r
11167 /* Issue ICS rematch command */
\r
11168 if (appData.icsActive) {
\r
11169 SendToICS(ics_prefix);
\r
11170 SendToICS("rematch\n");
\r
11177 /* Call your opponent's flag (claim a win on time) */
\r
11178 if (appData.icsActive) {
\r
11179 SendToICS(ics_prefix);
\r
11180 SendToICS("flag\n");
\r
11182 switch (gameMode) {
\r
11185 case MachinePlaysWhite:
\r
11188 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11191 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11193 DisplayError(_("Your opponent is not out of time"), 0);
\r
11196 case MachinePlaysBlack:
\r
11199 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11202 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11204 DisplayError(_("Your opponent is not out of time"), 0);
\r
11214 /* Offer draw or accept pending draw offer from opponent */
\r
11216 if (appData.icsActive) {
\r
11217 /* Note: tournament rules require draw offers to be
\r
11218 made after you make your move but before you punch
\r
11219 your clock. Currently ICS doesn't let you do that;
\r
11220 instead, you immediately punch your clock after making
\r
11221 a move, but you can offer a draw at any time. */
\r
11223 SendToICS(ics_prefix);
\r
11224 SendToICS("draw\n");
\r
11225 } else if (cmailMsgLoaded) {
\r
11226 if (currentMove == cmailOldMove &&
\r
11227 commentList[cmailOldMove] != NULL &&
\r
11228 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11229 "Black offers a draw" : "White offers a draw")) {
\r
11230 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11232 } else if (currentMove == cmailOldMove + 1) {
\r
11233 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11234 "White offers a draw" : "Black offers a draw";
\r
11235 AppendComment(currentMove, offer);
\r
11236 DisplayComment(currentMove - 1, offer);
\r
11237 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11239 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11240 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11242 } else if (first.offeredDraw) {
\r
11243 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11245 if (first.sendDrawOffers) {
\r
11246 SendToProgram("draw\n", &first);
\r
11247 userOfferedDraw = TRUE;
\r
11255 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11257 if (appData.icsActive) {
\r
11258 SendToICS(ics_prefix);
\r
11259 SendToICS("adjourn\n");
\r
11261 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11269 /* Offer Abort or accept pending Abort offer from opponent */
\r
11271 if (appData.icsActive) {
\r
11272 SendToICS(ics_prefix);
\r
11273 SendToICS("abort\n");
\r
11275 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11282 /* Resign. You can do this even if it's not your turn. */
\r
11284 if (appData.icsActive) {
\r
11285 SendToICS(ics_prefix);
\r
11286 SendToICS("resign\n");
\r
11288 switch (gameMode) {
\r
11289 case MachinePlaysWhite:
\r
11290 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11292 case MachinePlaysBlack:
\r
11293 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11296 if (cmailMsgLoaded) {
\r
11298 if (WhiteOnMove(cmailOldMove)) {
\r
11299 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11301 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11303 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11314 StopObservingEvent()
\r
11316 /* Stop observing current games */
\r
11317 SendToICS(ics_prefix);
\r
11318 SendToICS("unobserve\n");
\r
11322 StopExaminingEvent()
\r
11324 /* Stop observing current game */
\r
11325 SendToICS(ics_prefix);
\r
11326 SendToICS("unexamine\n");
\r
11330 ForwardInner(target)
\r
11335 if (appData.debugMode)
\r
11336 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11337 target, currentMove, forwardMostMove);
\r
11339 if (gameMode == EditPosition)
\r
11342 if (gameMode == PlayFromGameFile && !pausing)
\r
11345 if (gameMode == IcsExamining && pausing)
\r
11346 limit = pauseExamForwardMostMove;
\r
11348 limit = forwardMostMove;
\r
11350 if (target > limit) target = limit;
\r
11352 if (target > 0 && moveList[target - 1][0]) {
\r
11353 int fromX, fromY, toX, toY;
\r
11354 toX = moveList[target - 1][2] - AAA;
\r
11355 toY = moveList[target - 1][3] - ONE;
\r
11356 if (moveList[target - 1][1] == '@') {
\r
11357 if (appData.highlightLastMove) {
\r
11358 SetHighlights(-1, -1, toX, toY);
\r
11361 fromX = moveList[target - 1][0] - AAA;
\r
11362 fromY = moveList[target - 1][1] - ONE;
\r
11363 if (target == currentMove + 1) {
\r
11364 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11366 if (appData.highlightLastMove) {
\r
11367 SetHighlights(fromX, fromY, toX, toY);
\r
11371 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11372 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11373 gameMode == AnalyzeFile) {
\r
11374 while (currentMove < target) {
\r
11375 SendMoveToProgram(currentMove++, &first);
\r
11378 currentMove = target;
\r
11381 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11382 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11383 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11385 DisplayBothClocks();
\r
11386 DisplayMove(currentMove - 1);
\r
11387 DrawPosition(FALSE, boards[currentMove]);
\r
11388 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11389 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11390 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11398 if (gameMode == IcsExamining && !pausing) {
\r
11399 SendToICS(ics_prefix);
\r
11400 SendToICS("forward\n");
\r
11402 ForwardInner(currentMove + 1);
\r
11409 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11410 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11411 * the remaining moves to the engine. Otherwise we get analysis output
\r
11412 * after each move.
\r
11414 if (first.analysisSupport) {
\r
11415 SendToProgram("exit\nforce\n", &first);
\r
11416 first.analyzing = FALSE;
\r
11420 if (gameMode == IcsExamining && !pausing) {
\r
11421 SendToICS(ics_prefix);
\r
11422 SendToICS("forward 999999\n");
\r
11424 ForwardInner(forwardMostMove);
\r
11427 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11428 /* we have fed all the moves, so reactivate analysis mode */
\r
11429 SendToProgram("analyze\n", &first);
\r
11430 first.analyzing = TRUE;
\r
11431 /*first.maybeThinking = TRUE;*/
\r
11432 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11437 BackwardInner(target)
\r
11440 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11442 if (appData.debugMode)
\r
11443 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11444 target, currentMove, forwardMostMove);
\r
11446 if (gameMode == EditPosition) return;
\r
11447 if (currentMove <= backwardMostMove) {
\r
11448 ClearHighlights();
\r
11449 DrawPosition(full_redraw, boards[currentMove]);
\r
11452 if (gameMode == PlayFromGameFile && !pausing)
\r
11455 if (moveList[target][0]) {
\r
11456 int fromX, fromY, toX, toY;
\r
11457 toX = moveList[target][2] - AAA;
\r
11458 toY = moveList[target][3] - ONE;
\r
11459 if (moveList[target][1] == '@') {
\r
11460 if (appData.highlightLastMove) {
\r
11461 SetHighlights(-1, -1, toX, toY);
\r
11464 fromX = moveList[target][0] - AAA;
\r
11465 fromY = moveList[target][1] - ONE;
\r
11466 if (target == currentMove - 1) {
\r
11467 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11469 if (appData.highlightLastMove) {
\r
11470 SetHighlights(fromX, fromY, toX, toY);
\r
11474 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11475 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11476 while (currentMove > target) {
\r
11477 SendToProgram("undo\n", &first);
\r
11481 currentMove = target;
\r
11484 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11485 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11486 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11488 DisplayBothClocks();
\r
11489 DisplayMove(currentMove - 1);
\r
11490 DrawPosition(full_redraw, boards[currentMove]);
\r
11491 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11492 // [HGM] PV info: routine tests if comment empty
\r
11493 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11499 if (gameMode == IcsExamining && !pausing) {
\r
11500 SendToICS(ics_prefix);
\r
11501 SendToICS("backward\n");
\r
11503 BackwardInner(currentMove - 1);
\r
11510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11511 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11512 * all the moves. Otherwise we get analysis output after each undo.
\r
11514 if (first.analysisSupport) {
\r
11515 SendToProgram("exit\nforce\n", &first);
\r
11516 first.analyzing = FALSE;
\r
11520 if (gameMode == IcsExamining && !pausing) {
\r
11521 SendToICS(ics_prefix);
\r
11522 SendToICS("backward 999999\n");
\r
11524 BackwardInner(backwardMostMove);
\r
11527 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11528 /* we have fed all the moves, so reactivate analysis mode */
\r
11529 SendToProgram("analyze\n", &first);
\r
11530 first.analyzing = TRUE;
\r
11531 /*first.maybeThinking = TRUE;*/
\r
11532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11537 ToNrEvent(int to)
\r
11539 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11540 if (to >= forwardMostMove) to = forwardMostMove;
\r
11541 if (to <= backwardMostMove) to = backwardMostMove;
\r
11542 if (to < currentMove) {
\r
11543 BackwardInner(to);
\r
11545 ForwardInner(to);
\r
11552 if (gameMode != IcsExamining) {
\r
11553 DisplayError(_("You are not examining a game"), 0);
\r
11557 DisplayError(_("You can't revert while pausing"), 0);
\r
11560 SendToICS(ics_prefix);
\r
11561 SendToICS("revert\n");
\r
11565 RetractMoveEvent()
\r
11567 switch (gameMode) {
\r
11568 case MachinePlaysWhite:
\r
11569 case MachinePlaysBlack:
\r
11570 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11571 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11574 if (forwardMostMove < 2) return;
\r
11575 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11576 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11577 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11578 DisplayBothClocks();
\r
11579 DisplayMove(currentMove - 1);
\r
11580 ClearHighlights();/*!! could figure this out*/
\r
11581 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11582 SendToProgram("remove\n", &first);
\r
11583 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11586 case BeginningOfGame:
\r
11590 case IcsPlayingWhite:
\r
11591 case IcsPlayingBlack:
\r
11592 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11593 SendToICS(ics_prefix);
\r
11594 SendToICS("takeback 2\n");
\r
11596 SendToICS(ics_prefix);
\r
11597 SendToICS("takeback 1\n");
\r
11606 ChessProgramState *cps;
\r
11608 switch (gameMode) {
\r
11609 case MachinePlaysWhite:
\r
11610 if (!WhiteOnMove(forwardMostMove)) {
\r
11611 DisplayError(_("It is your turn"), 0);
\r
11616 case MachinePlaysBlack:
\r
11617 if (WhiteOnMove(forwardMostMove)) {
\r
11618 DisplayError(_("It is your turn"), 0);
\r
11623 case TwoMachinesPlay:
\r
11624 if (WhiteOnMove(forwardMostMove) ==
\r
11625 (first.twoMachinesColor[0] == 'w')) {
\r
11631 case BeginningOfGame:
\r
11635 SendToProgram("?\n", cps);
\r
11639 TruncateGameEvent()
\r
11642 if (gameMode != EditGame) return;
\r
11649 if (forwardMostMove > currentMove) {
\r
11650 if (gameInfo.resultDetails != NULL) {
\r
11651 free(gameInfo.resultDetails);
\r
11652 gameInfo.resultDetails = NULL;
\r
11653 gameInfo.result = GameUnfinished;
\r
11655 forwardMostMove = currentMove;
\r
11656 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11664 if (appData.noChessProgram) return;
\r
11665 switch (gameMode) {
\r
11666 case MachinePlaysWhite:
\r
11667 if (WhiteOnMove(forwardMostMove)) {
\r
11668 DisplayError(_("Wait until your turn"), 0);
\r
11672 case BeginningOfGame:
\r
11673 case MachinePlaysBlack:
\r
11674 if (!WhiteOnMove(forwardMostMove)) {
\r
11675 DisplayError(_("Wait until your turn"), 0);
\r
11680 DisplayError(_("No hint available"), 0);
\r
11683 SendToProgram("hint\n", &first);
\r
11684 hintRequested = TRUE;
\r
11690 if (appData.noChessProgram) return;
\r
11691 switch (gameMode) {
\r
11692 case MachinePlaysWhite:
\r
11693 if (WhiteOnMove(forwardMostMove)) {
\r
11694 DisplayError(_("Wait until your turn"), 0);
\r
11698 case BeginningOfGame:
\r
11699 case MachinePlaysBlack:
\r
11700 if (!WhiteOnMove(forwardMostMove)) {
\r
11701 DisplayError(_("Wait until your turn"), 0);
\r
11705 case EditPosition:
\r
11706 EditPositionDone();
\r
11708 case TwoMachinesPlay:
\r
11713 SendToProgram("bk\n", &first);
\r
11714 bookOutput[0] = NULLCHAR;
\r
11715 bookRequested = TRUE;
\r
11721 char *tags = PGNTags(&gameInfo);
\r
11722 TagsPopUp(tags, CmailMsg());
\r
11726 /* end button procedures */
\r
11729 PrintPosition(fp, move)
\r
11735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11736 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11737 char c = PieceToChar(boards[move][i][j]);
\r
11738 fputc(c == 'x' ? '.' : c, fp);
\r
11739 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11742 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11743 fprintf(fp, "white to play\n");
\r
11745 fprintf(fp, "black to play\n");
\r
11749 PrintOpponents(fp)
\r
11752 if (gameInfo.white != NULL) {
\r
11753 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11755 fprintf(fp, "\n");
\r
11759 /* Find last component of program's own name, using some heuristics */
\r
11761 TidyProgramName(prog, host, buf)
\r
11762 char *prog, *host, buf[MSG_SIZ];
\r
11765 int local = (strcmp(host, "localhost") == 0);
\r
11766 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11768 while (*p == ' ') p++;
\r
11771 if (*prog == '"' || *prog == '\'') {
\r
11772 q = strchr(prog + 1, *prog);
\r
11774 q = strchr(prog, ' ');
\r
11776 if (q == NULL) q = prog + strlen(prog);
\r
11778 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11780 if(p == prog && *p == '"') p++;
\r
11781 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11782 memcpy(buf, p, q - p);
\r
11783 buf[q - p] = NULLCHAR;
\r
11785 strcat(buf, "@");
\r
11786 strcat(buf, host);
\r
11791 TimeControlTagValue()
\r
11793 char buf[MSG_SIZ];
\r
11794 if (!appData.clockMode) {
\r
11795 strcpy(buf, "-");
\r
11796 } else if (movesPerSession > 0) {
\r
11797 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11798 } else if (timeIncrement == 0) {
\r
11799 sprintf(buf, "%ld", timeControl/1000);
\r
11801 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11803 return StrSave(buf);
\r
11809 /* This routine is used only for certain modes */
\r
11810 VariantClass v = gameInfo.variant;
\r
11811 ClearGameInfo(&gameInfo);
\r
11812 gameInfo.variant = v;
\r
11814 switch (gameMode) {
\r
11815 case MachinePlaysWhite:
\r
11816 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11817 gameInfo.site = StrSave(HostName());
\r
11818 gameInfo.date = PGNDate();
\r
11819 gameInfo.round = StrSave("-");
\r
11820 gameInfo.white = StrSave(first.tidy);
\r
11821 gameInfo.black = StrSave(UserName());
\r
11822 gameInfo.timeControl = TimeControlTagValue();
\r
11825 case MachinePlaysBlack:
\r
11826 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11827 gameInfo.site = StrSave(HostName());
\r
11828 gameInfo.date = PGNDate();
\r
11829 gameInfo.round = StrSave("-");
\r
11830 gameInfo.white = StrSave(UserName());
\r
11831 gameInfo.black = StrSave(first.tidy);
\r
11832 gameInfo.timeControl = TimeControlTagValue();
\r
11835 case TwoMachinesPlay:
\r
11836 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11837 gameInfo.site = StrSave(HostName());
\r
11838 gameInfo.date = PGNDate();
\r
11839 if (matchGame > 0) {
\r
11840 char buf[MSG_SIZ];
\r
11841 sprintf(buf, "%d", matchGame);
\r
11842 gameInfo.round = StrSave(buf);
\r
11844 gameInfo.round = StrSave("-");
\r
11846 if (first.twoMachinesColor[0] == 'w') {
\r
11847 gameInfo.white = StrSave(first.tidy);
\r
11848 gameInfo.black = StrSave(second.tidy);
\r
11850 gameInfo.white = StrSave(second.tidy);
\r
11851 gameInfo.black = StrSave(first.tidy);
\r
11853 gameInfo.timeControl = TimeControlTagValue();
\r
11857 gameInfo.event = StrSave("Edited game");
\r
11858 gameInfo.site = StrSave(HostName());
\r
11859 gameInfo.date = PGNDate();
\r
11860 gameInfo.round = StrSave("-");
\r
11861 gameInfo.white = StrSave("-");
\r
11862 gameInfo.black = StrSave("-");
\r
11865 case EditPosition:
\r
11866 gameInfo.event = StrSave("Edited position");
\r
11867 gameInfo.site = StrSave(HostName());
\r
11868 gameInfo.date = PGNDate();
\r
11869 gameInfo.round = StrSave("-");
\r
11870 gameInfo.white = StrSave("-");
\r
11871 gameInfo.black = StrSave("-");
\r
11874 case IcsPlayingWhite:
\r
11875 case IcsPlayingBlack:
\r
11876 case IcsObserving:
\r
11877 case IcsExamining:
\r
11880 case PlayFromGameFile:
\r
11881 gameInfo.event = StrSave("Game from non-PGN file");
\r
11882 gameInfo.site = StrSave(HostName());
\r
11883 gameInfo.date = PGNDate();
\r
11884 gameInfo.round = StrSave("-");
\r
11885 gameInfo.white = StrSave("?");
\r
11886 gameInfo.black = StrSave("?");
\r
11895 ReplaceComment(index, text)
\r
11901 while (*text == '\n') text++;
\r
11902 len = strlen(text);
\r
11903 while (len > 0 && text[len - 1] == '\n') len--;
\r
11905 if (commentList[index] != NULL)
\r
11906 free(commentList[index]);
\r
11909 commentList[index] = NULL;
\r
11912 commentList[index] = (char *) malloc(len + 2);
\r
11913 strncpy(commentList[index], text, len);
\r
11914 commentList[index][len] = '\n';
\r
11915 commentList[index][len + 1] = NULLCHAR;
\r
11928 if (ch == '\r') continue;
\r
11930 } while (ch != '\0');
\r
11934 AppendComment(index, text)
\r
11941 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11944 while (*text == '\n') text++;
\r
11945 len = strlen(text);
\r
11946 while (len > 0 && text[len - 1] == '\n') len--;
\r
11948 if (len == 0) return;
\r
11950 if (commentList[index] != NULL) {
\r
11951 old = commentList[index];
\r
11952 oldlen = strlen(old);
\r
11953 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11954 strcpy(commentList[index], old);
\r
11956 strncpy(&commentList[index][oldlen], text, len);
\r
11957 commentList[index][oldlen + len] = '\n';
\r
11958 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11960 commentList[index] = (char *) malloc(len + 2);
\r
11961 strncpy(commentList[index], text, len);
\r
11962 commentList[index][len] = '\n';
\r
11963 commentList[index][len + 1] = NULLCHAR;
\r
11967 static char * FindStr( char * text, char * sub_text )
\r
11969 char * result = strstr( text, sub_text );
\r
11971 if( result != NULL ) {
\r
11972 result += strlen( sub_text );
\r
11978 /* [AS] Try to extract PV info from PGN comment */
\r
11979 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11980 char *GetInfoFromComment( int index, char * text )
\r
11982 char * sep = text;
\r
11984 if( text != NULL && index > 0 ) {
\r
11987 int time = -1, sec = 0, deci;
\r
11988 char * s_eval = FindStr( text, "[%eval " );
\r
11989 char * s_emt = FindStr( text, "[%emt " );
\r
11991 if( s_eval != NULL || s_emt != NULL ) {
\r
11995 if( s_eval != NULL ) {
\r
11996 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
12000 if( delim != ']' ) {
\r
12005 if( s_emt != NULL ) {
\r
12009 /* We expect something like: [+|-]nnn.nn/dd */
\r
12010 int score_lo = 0;
\r
12012 sep = strchr( text, '/' );
\r
12013 if( sep == NULL || sep < (text+4) ) {
\r
12017 time = -1; sec = -1; deci = -1;
\r
12018 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12019 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12020 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12021 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12025 if( score_lo < 0 || score_lo >= 100 ) {
\r
12029 if(sec >= 0) time = 600*time + 10*sec; else
\r
12030 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12032 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12034 /* [HGM] PV time: now locate end of PV info */
\r
12035 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12037 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12039 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12041 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12042 while(*sep == ' ') sep++;
\r
12045 if( depth <= 0 ) {
\r
12053 pvInfoList[index-1].depth = depth;
\r
12054 pvInfoList[index-1].score = score;
\r
12055 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12061 SendToProgram(message, cps)
\r
12063 ChessProgramState *cps;
\r
12065 int count, outCount, error;
\r
12066 char buf[MSG_SIZ];
\r
12068 if (cps->pr == NULL) return;
\r
12071 if (appData.debugMode) {
\r
12073 GetTimeMark(&now);
\r
12074 fprintf(debugFP, "%ld >%-6s: %s",
\r
12075 SubtractTimeMarks(&now, &programStartTime),
\r
12076 cps->which, message);
\r
12079 count = strlen(message);
\r
12080 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12081 if (outCount < count && !exiting
\r
12082 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12083 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12084 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12085 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12086 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12087 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12089 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12091 gameInfo.resultDetails = buf;
\r
12093 DisplayFatalError(buf, error, 1);
\r
12098 ReceiveFromProgram(isr, closure, message, count, error)
\r
12099 InputSourceRef isr;
\r
12100 VOIDSTAR closure;
\r
12106 char buf[MSG_SIZ];
\r
12107 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12109 if (isr != cps->isr) return; /* Killed intentionally */
\r
12110 if (count <= 0) {
\r
12111 if (count == 0) {
\r
12113 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12114 cps->which, cps->program);
\r
12115 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12116 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12117 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12118 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12120 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12122 gameInfo.resultDetails = buf;
\r
12124 RemoveInputSource(cps->isr);
\r
12125 DisplayFatalError(buf, 0, 1);
\r
12128 _("Error reading from %s chess program (%s)"),
\r
12129 cps->which, cps->program);
\r
12130 RemoveInputSource(cps->isr);
\r
12132 /* [AS] Program is misbehaving badly... kill it */
\r
12133 if( count == -2 ) {
\r
12134 DestroyChildProcess( cps->pr, 9 );
\r
12135 cps->pr = NoProc;
\r
12138 DisplayFatalError(buf, error, 1);
\r
12143 if ((end_str = strchr(message, '\r')) != NULL)
\r
12144 *end_str = NULLCHAR;
\r
12145 if ((end_str = strchr(message, '\n')) != NULL)
\r
12146 *end_str = NULLCHAR;
\r
12148 if (appData.debugMode) {
\r
12149 TimeMark now; int print = 1;
\r
12150 char *quote = ""; char c; int i;
\r
12152 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12153 char start = message[0];
\r
12154 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12155 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12156 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12157 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12158 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12159 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12160 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12161 { quote = "# "; print = (appData.engineComments == 2); }
\r
12162 message[0] = start; // restore original message
\r
12165 GetTimeMark(&now);
\r
12166 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12167 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12173 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12174 if (appData.icsEngineAnalyze) {
\r
12175 if (strstr(message, "whisper") != NULL ||
\r
12176 strstr(message, "kibitz") != NULL ||
\r
12177 strstr(message, "tellics") != NULL) return;
\r
12180 HandleMachineMove(message, cps);
\r
12185 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12186 ChessProgramState *cps;
\r
12187 int mps, inc, sd, st;
\r
12190 char buf[MSG_SIZ];
\r
12193 if( timeControl_2 > 0 ) {
\r
12194 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12195 tc = timeControl_2;
\r
12198 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12199 inc /= cps->timeOdds;
\r
12200 st /= cps->timeOdds;
\r
12202 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12205 /* Set exact time per move, normally using st command */
\r
12206 if (cps->stKludge) {
\r
12207 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12208 seconds = st % 60;
\r
12209 if (seconds == 0) {
\r
12210 sprintf(buf, "level 1 %d\n", st/60);
\r
12212 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12215 sprintf(buf, "st %d\n", st);
\r
12218 /* Set conventional or incremental time control, using level command */
\r
12219 if (seconds == 0) {
\r
12220 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12221 Fixed in later versions, but still avoid :seconds
\r
12222 when seconds is 0. */
\r
12223 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12225 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12226 seconds, inc/1000);
\r
12229 SendToProgram(buf, cps);
\r
12231 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12232 /* Orthogonally, limit search to given depth */
\r
12234 if (cps->sdKludge) {
\r
12235 sprintf(buf, "depth\n%d\n", sd);
\r
12237 sprintf(buf, "sd %d\n", sd);
\r
12239 SendToProgram(buf, cps);
\r
12242 if(cps->nps > 0) { /* [HGM] nps */
\r
12243 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12245 sprintf(buf, "nps %d\n", cps->nps);
\r
12246 SendToProgram(buf, cps);
\r
12251 ChessProgramState *WhitePlayer()
\r
12252 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12254 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12255 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12261 SendTimeRemaining(cps, machineWhite)
\r
12262 ChessProgramState *cps;
\r
12263 int /*boolean*/ machineWhite;
\r
12265 char message[MSG_SIZ];
\r
12266 long time, otime;
\r
12268 /* Note: this routine must be called when the clocks are stopped
\r
12269 or when they have *just* been set or switched; otherwise
\r
12270 it will be off by the time since the current tick started.
\r
12272 if (machineWhite) {
\r
12273 time = whiteTimeRemaining / 10;
\r
12274 otime = blackTimeRemaining / 10;
\r
12276 time = blackTimeRemaining / 10;
\r
12277 otime = whiteTimeRemaining / 10;
\r
12279 /* [HGM] translate opponent's time by time-odds factor */
\r
12280 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12281 if (appData.debugMode) {
\r
12282 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12285 if (time <= 0) time = 1;
\r
12286 if (otime <= 0) otime = 1;
\r
12288 sprintf(message, "time %ld\n", time);
\r
12289 SendToProgram(message, cps);
\r
12291 sprintf(message, "otim %ld\n", otime);
\r
12292 SendToProgram(message, cps);
\r
12296 BoolFeature(p, name, loc, cps)
\r
12300 ChessProgramState *cps;
\r
12302 char buf[MSG_SIZ];
\r
12303 int len = strlen(name);
\r
12305 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12307 sscanf(*p, "%d", &val);
\r
12308 *loc = (val != 0);
\r
12309 while (**p && **p != ' ') (*p)++;
\r
12310 sprintf(buf, "accepted %s\n", name);
\r
12311 SendToProgram(buf, cps);
\r
12318 IntFeature(p, name, loc, cps)
\r
12322 ChessProgramState *cps;
\r
12324 char buf[MSG_SIZ];
\r
12325 int len = strlen(name);
\r
12326 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12328 sscanf(*p, "%d", loc);
\r
12329 while (**p && **p != ' ') (*p)++;
\r
12330 sprintf(buf, "accepted %s\n", name);
\r
12331 SendToProgram(buf, cps);
\r
12338 StringFeature(p, name, loc, cps)
\r
12342 ChessProgramState *cps;
\r
12344 char buf[MSG_SIZ];
\r
12345 int len = strlen(name);
\r
12346 if (strncmp((*p), name, len) == 0
\r
12347 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12349 sscanf(*p, "%[^\"]", loc);
\r
12350 while (**p && **p != '\"') (*p)++;
\r
12351 if (**p == '\"') (*p)++;
\r
12352 sprintf(buf, "accepted %s\n", name);
\r
12353 SendToProgram(buf, cps);
\r
12360 ParseOption(Option *opt, ChessProgramState *cps)
\r
12361 // [HGM] options: process the string that defines an engine option, and determine
\r
12362 // name, type, default value, and allowed value range
\r
12364 char *p, *q, buf[MSG_SIZ];
\r
12365 int n, min = (-1)<<31, max = 1<<31, def;
\r
12367 if(p = strstr(opt->name, " -spin ")) {
\r
12368 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12369 if(max < min) max = min; // enforce consistency
\r
12370 if(def < min) def = min;
\r
12371 if(def > max) def = max;
\r
12372 opt->value = def;
\r
12375 opt->type = Spin;
\r
12376 } else if(p = strstr(opt->name, " -string ")) {
\r
12377 opt->textValue = p+9;
\r
12378 opt->type = TextBox;
\r
12379 } else if(p = strstr(opt->name, " -check ")) {
\r
12380 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12381 opt->value = (def != 0);
\r
12382 opt->type = CheckBox;
\r
12383 } else if(p = strstr(opt->name, " -combo ")) {
\r
12384 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12385 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12386 opt->value = n = 0;
\r
12387 while(q = StrStr(q, " /// ")) {
\r
12388 n++; *q = 0; // count choices, and null-terminate each of them
\r
12390 if(*q == '*') { // remember default, which is marked with * prefix
\r
12394 cps->comboList[cps->comboCnt++] = q;
\r
12396 cps->comboList[cps->comboCnt++] = NULL;
\r
12397 opt->max = n + 1;
\r
12398 opt->type = ComboBox;
\r
12399 } else if(p = strstr(opt->name, " -button")) {
\r
12400 opt->type = Button;
\r
12401 } else if(p = strstr(opt->name, " -save")) {
\r
12402 opt->type = SaveButton;
\r
12403 } else return FALSE;
\r
12404 *p = 0; // terminate option name
\r
12405 // now look if the command-line options define a setting for this engine option.
\r
12406 if(cps->optionSettings && cps->optionSettings[0])
\r
12407 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12408 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12409 sprintf(buf, "option %s", p);
\r
12410 if(p = strstr(buf, ",")) *p = 0;
\r
12411 strcat(buf, "\n");
\r
12412 SendToProgram(buf, cps);
\r
12418 FeatureDone(cps, val)
\r
12419 ChessProgramState* cps;
\r
12422 DelayedEventCallback cb = GetDelayedEvent();
\r
12423 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12424 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12425 CancelDelayedEvent();
\r
12426 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12428 cps->initDone = val;
\r
12431 /* Parse feature command from engine */
\r
12433 ParseFeatures(args, cps)
\r
12435 ChessProgramState *cps;
\r
12440 char buf[MSG_SIZ];
\r
12443 while (*p == ' ') p++;
\r
12444 if (*p == NULLCHAR) return;
\r
12446 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12447 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12448 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12449 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12450 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12451 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12452 /* Engine can disable reuse, but can't enable it if user said no */
\r
12453 if (!val) cps->reuse = FALSE;
\r
12456 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12457 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12458 if (gameMode == TwoMachinesPlay) {
\r
12459 DisplayTwoMachinesTitle();
\r
12461 DisplayTitle("");
\r
12465 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12466 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12467 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12468 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12469 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12470 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12471 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12472 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12473 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12474 if (IntFeature(&p, "done", &val, cps)) {
\r
12475 FeatureDone(cps, val);
\r
12478 /* Added by Tord: */
\r
12479 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12480 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12481 /* End of additions by Tord */
\r
12483 /* [HGM] added features: */
\r
12484 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12485 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12486 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12487 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12488 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12489 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12490 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12491 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12492 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12493 cps->nrOptions--;
\r
12494 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12495 DisplayError(buf, 0);
\r
12499 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12500 /* End of additions by HGM */
\r
12502 /* unknown feature: complain and skip */
\r
12504 while (*q && *q != '=') q++;
\r
12505 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12506 SendToProgram(buf, cps);
\r
12510 if (*p == '\"') {
\r
12512 while (*p && *p != '\"') p++;
\r
12513 if (*p == '\"') p++;
\r
12515 while (*p && *p != ' ') p++;
\r
12523 PeriodicUpdatesEvent(newState)
\r
12526 if (newState == appData.periodicUpdates)
\r
12529 appData.periodicUpdates=newState;
\r
12531 /* Display type changes, so update it now */
\r
12532 DisplayAnalysis();
\r
12534 /* Get the ball rolling again... */
\r
12536 AnalysisPeriodicEvent(1);
\r
12537 StartAnalysisClock();
\r
12542 PonderNextMoveEvent(newState)
\r
12545 if (newState == appData.ponderNextMove) return;
\r
12546 if (gameMode == EditPosition) EditPositionDone();
\r
12548 SendToProgram("hard\n", &first);
\r
12549 if (gameMode == TwoMachinesPlay) {
\r
12550 SendToProgram("hard\n", &second);
\r
12553 SendToProgram("easy\n", &first);
\r
12554 thinkOutput[0] = NULLCHAR;
\r
12555 if (gameMode == TwoMachinesPlay) {
\r
12556 SendToProgram("easy\n", &second);
\r
12559 appData.ponderNextMove = newState;
\r
12563 NewSettingEvent(option, command, value)
\r
12565 int option, value;
\r
12567 char buf[MSG_SIZ];
\r
12569 if (gameMode == EditPosition) EditPositionDone();
\r
12570 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12571 SendToProgram(buf, &first);
\r
12572 if (gameMode == TwoMachinesPlay) {
\r
12573 SendToProgram(buf, &second);
\r
12578 ShowThinkingEvent()
\r
12579 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12581 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12582 int newState = appData.showThinking
\r
12583 // [HGM] thinking: other features now need thinking output as well
\r
12584 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12586 if (oldState == newState) return;
\r
12587 oldState = newState;
\r
12588 if (gameMode == EditPosition) EditPositionDone();
\r
12590 SendToProgram("post\n", &first);
\r
12591 if (gameMode == TwoMachinesPlay) {
\r
12592 SendToProgram("post\n", &second);
\r
12595 SendToProgram("nopost\n", &first);
\r
12596 thinkOutput[0] = NULLCHAR;
\r
12597 if (gameMode == TwoMachinesPlay) {
\r
12598 SendToProgram("nopost\n", &second);
\r
12601 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12605 AskQuestionEvent(title, question, replyPrefix, which)
\r
12606 char *title; char *question; char *replyPrefix; char *which;
\r
12608 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12609 if (pr == NoProc) return;
\r
12610 AskQuestion(title, question, replyPrefix, pr);
\r
12614 DisplayMove(moveNumber)
\r
12617 char message[MSG_SIZ];
\r
12618 char res[MSG_SIZ];
\r
12619 char cpThinkOutput[MSG_SIZ];
\r
12621 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12623 if (moveNumber == forwardMostMove - 1 ||
\r
12624 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12626 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12628 if (strchr(cpThinkOutput, '\n')) {
\r
12629 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12632 *cpThinkOutput = NULLCHAR;
\r
12635 /* [AS] Hide thinking from human user */
\r
12636 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12637 *cpThinkOutput = NULLCHAR;
\r
12638 if( thinkOutput[0] != NULLCHAR ) {
\r
12641 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12642 cpThinkOutput[i] = '.';
\r
12644 cpThinkOutput[i] = NULLCHAR;
\r
12645 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12649 if (moveNumber == forwardMostMove - 1 &&
\r
12650 gameInfo.resultDetails != NULL) {
\r
12651 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12652 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12654 sprintf(res, " {%s} %s",
\r
12655 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12658 res[0] = NULLCHAR;
\r
12661 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12662 DisplayMessage(res, cpThinkOutput);
\r
12664 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12665 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12666 parseList[moveNumber], res);
\r
12667 DisplayMessage(message, cpThinkOutput);
\r
12672 DisplayAnalysisText(text)
\r
12675 char buf[MSG_SIZ];
\r
12677 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12678 || appData.icsEngineAnalyze) {
\r
12679 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12680 AnalysisPopUp(buf, text);
\r
12685 only_one_move(str)
\r
12688 while (*str && isspace(*str)) ++str;
\r
12689 while (*str && !isspace(*str)) ++str;
\r
12690 if (!*str) return 1;
\r
12691 while (*str && isspace(*str)) ++str;
\r
12692 if (!*str) return 1;
\r
12697 DisplayAnalysis()
\r
12699 char buf[MSG_SIZ];
\r
12700 char lst[MSG_SIZ / 2];
\r
12702 static char *xtra[] = { "", " (--)", " (++)" };
\r
12705 if (programStats.time == 0) {
\r
12706 programStats.time = 1;
\r
12709 if (programStats.got_only_move) {
\r
12710 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12712 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12714 nps = (u64ToDouble(programStats.nodes) /
\r
12715 ((double)programStats.time /100.0));
\r
12717 cs = programStats.time % 100;
\r
12718 s = programStats.time / 100;
\r
12719 h = (s / (60*60));
\r
12724 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12725 if (programStats.move_name[0] != NULLCHAR) {
\r
12726 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12727 programStats.depth,
\r
12728 programStats.nr_moves-programStats.moves_left,
\r
12729 programStats.nr_moves, programStats.move_name,
\r
12730 ((float)programStats.score)/100.0, lst,
\r
12731 only_one_move(lst)?
\r
12732 xtra[programStats.got_fail] : "",
\r
12733 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12735 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12736 programStats.depth,
\r
12737 programStats.nr_moves-programStats.moves_left,
\r
12738 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12740 only_one_move(lst)?
\r
12741 xtra[programStats.got_fail] : "",
\r
12742 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12745 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12746 programStats.depth,
\r
12747 ((float)programStats.score)/100.0,
\r
12749 only_one_move(lst)?
\r
12750 xtra[programStats.got_fail] : "",
\r
12751 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12754 DisplayAnalysisText(buf);
\r
12758 DisplayComment(moveNumber, text)
\r
12762 char title[MSG_SIZ];
\r
12763 char buf[8000]; // comment can be long!
\r
12764 int score, depth;
\r
12766 if( appData.autoDisplayComment ) {
\r
12767 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12768 strcpy(title, "Comment");
\r
12770 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12771 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12772 parseList[moveNumber]);
\r
12774 } else title[0] = 0;
\r
12776 // [HGM] PV info: display PV info together with (or as) comment
\r
12777 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12778 if(text == NULL) text = "";
\r
12779 score = pvInfoList[moveNumber].score;
\r
12780 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12781 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12782 CommentPopUp(title, buf);
\r
12784 if (text != NULL)
\r
12785 CommentPopUp(title, text);
\r
12788 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12789 * might be busy thinking or pondering. It can be omitted if your
\r
12790 * gnuchess is configured to stop thinking immediately on any user
\r
12791 * input. However, that gnuchess feature depends on the FIONREAD
\r
12792 * ioctl, which does not work properly on some flavors of Unix.
\r
12796 ChessProgramState *cps;
\r
12799 if (!cps->useSigint) return;
\r
12800 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12801 switch (gameMode) {
\r
12802 case MachinePlaysWhite:
\r
12803 case MachinePlaysBlack:
\r
12804 case TwoMachinesPlay:
\r
12805 case IcsPlayingWhite:
\r
12806 case IcsPlayingBlack:
\r
12807 case AnalyzeMode:
\r
12808 case AnalyzeFile:
\r
12809 /* Skip if we know it isn't thinking */
\r
12810 if (!cps->maybeThinking) return;
\r
12811 if (appData.debugMode)
\r
12812 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12813 InterruptChildProcess(cps->pr);
\r
12814 cps->maybeThinking = FALSE;
\r
12819 #endif /*ATTENTION*/
\r
12825 if (whiteTimeRemaining <= 0) {
\r
12826 if (!whiteFlag) {
\r
12827 whiteFlag = TRUE;
\r
12828 if (appData.icsActive) {
\r
12829 if (appData.autoCallFlag &&
\r
12830 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12831 SendToICS(ics_prefix);
\r
12832 SendToICS("flag\n");
\r
12836 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12838 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12839 if (appData.autoCallFlag) {
\r
12840 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12847 if (blackTimeRemaining <= 0) {
\r
12848 if (!blackFlag) {
\r
12849 blackFlag = TRUE;
\r
12850 if (appData.icsActive) {
\r
12851 if (appData.autoCallFlag &&
\r
12852 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12853 SendToICS(ics_prefix);
\r
12854 SendToICS("flag\n");
\r
12858 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12860 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12861 if (appData.autoCallFlag) {
\r
12862 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12873 CheckTimeControl()
\r
12875 if (!appData.clockMode || appData.icsActive ||
\r
12876 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12879 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12881 if ( !WhiteOnMove(forwardMostMove) )
\r
12882 /* White made time control */
\r
12883 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12884 /* [HGM] time odds: correct new time quota for time odds! */
\r
12885 / WhitePlayer()->timeOdds;
\r
12887 /* Black made time control */
\r
12888 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12889 / WhitePlayer()->other->timeOdds;
\r
12893 DisplayBothClocks()
\r
12895 int wom = gameMode == EditPosition ?
\r
12896 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12897 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12898 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12902 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12903 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12904 to use other calls if you don't. Clocks will be less accurate if
\r
12905 you have neither ftime nor gettimeofday.
\r
12908 /* VS 2008 requires the #include outside of the function */
\r
12909 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12910 #include <sys/timeb.h>
\r
12913 /* Get the current time as a TimeMark */
\r
12918 #if HAVE_GETTIMEOFDAY
\r
12920 struct timeval timeVal;
\r
12921 struct timezone timeZone;
\r
12923 gettimeofday(&timeVal, &timeZone);
\r
12924 tm->sec = (long) timeVal.tv_sec;
\r
12925 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12927 #else /*!HAVE_GETTIMEOFDAY*/
\r
12930 // include <sys/timeb.h> / moved to just above start of function
\r
12931 struct timeb timeB;
\r
12934 tm->sec = (long) timeB.time;
\r
12935 tm->ms = (int) timeB.millitm;
\r
12937 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12938 tm->sec = (long) time(NULL);
\r
12944 /* Return the difference in milliseconds between two
\r
12945 time marks. We assume the difference will fit in a long!
\r
12948 SubtractTimeMarks(tm2, tm1)
\r
12949 TimeMark *tm2, *tm1;
\r
12951 return 1000L*(tm2->sec - tm1->sec) +
\r
12952 (long) (tm2->ms - tm1->ms);
\r
12957 * Code to manage the game clocks.
\r
12959 * In tournament play, black starts the clock and then white makes a move.
\r
12960 * We give the human user a slight advantage if he is playing white---the
\r
12961 * clocks don't run until he makes his first move, so it takes zero time.
\r
12962 * Also, we don't account for network lag, so we could get out of sync
\r
12963 * with GNU Chess's clock -- but then, referees are always right.
\r
12966 static TimeMark tickStartTM;
\r
12967 static long intendedTickLength;
\r
12970 NextTickLength(timeRemaining)
\r
12971 long timeRemaining;
\r
12973 long nominalTickLength, nextTickLength;
\r
12975 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12976 nominalTickLength = 100L;
\r
12978 nominalTickLength = 1000L;
\r
12979 nextTickLength = timeRemaining % nominalTickLength;
\r
12980 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12982 return nextTickLength;
\r
12985 /* Adjust clock one minute up or down */
\r
12987 AdjustClock(Boolean which, int dir)
\r
12989 if(which) blackTimeRemaining += 60000*dir;
\r
12990 else whiteTimeRemaining += 60000*dir;
\r
12991 DisplayBothClocks();
\r
12994 /* Stop clocks and reset to a fresh time control */
\r
12998 (void) StopClockTimer();
\r
12999 if (appData.icsActive) {
\r
13000 whiteTimeRemaining = blackTimeRemaining = 0;
\r
13001 } else { /* [HGM] correct new time quote for time odds */
\r
13002 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
13003 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
13005 if (whiteFlag || blackFlag) {
\r
13006 DisplayTitle("");
\r
13007 whiteFlag = blackFlag = FALSE;
\r
13009 DisplayBothClocks();
\r
13012 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13014 /* Decrement running clock by amount of time that has passed */
\r
13016 DecrementClocks()
\r
13018 long timeRemaining;
\r
13019 long lastTickLength, fudge;
\r
13022 if (!appData.clockMode) return;
\r
13023 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13025 GetTimeMark(&now);
\r
13027 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13029 /* Fudge if we woke up a little too soon */
\r
13030 fudge = intendedTickLength - lastTickLength;
\r
13031 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13033 if (WhiteOnMove(forwardMostMove)) {
\r
13034 if(whiteNPS >= 0) lastTickLength = 0;
\r
13035 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13036 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13037 WhiteOnMove(currentMove));
\r
13039 if(blackNPS >= 0) lastTickLength = 0;
\r
13040 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13041 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13042 !WhiteOnMove(currentMove));
\r
13045 if (CheckFlags()) return;
\r
13047 tickStartTM = now;
\r
13048 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13049 StartClockTimer(intendedTickLength);
\r
13051 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13052 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13053 * with increment) the time remaining has increased to a level above the
\r
13054 * threshold, reset the alarm so it can sound again.
\r
13057 if (appData.icsActive && appData.icsAlarm) {
\r
13059 /* make sure we are dealing with the user's clock */
\r
13060 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13061 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13064 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13065 alarmSounded = FALSE;
\r
13066 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13067 PlayAlarmSound();
\r
13068 alarmSounded = TRUE;
\r
13074 /* A player has just moved, so stop the previously running
\r
13075 clock and (if in clock mode) start the other one.
\r
13076 We redisplay both clocks in case we're in ICS mode, because
\r
13077 ICS gives us an update to both clocks after every move.
\r
13078 Note that this routine is called *after* forwardMostMove
\r
13079 is updated, so the last fractional tick must be subtracted
\r
13080 from the color that is *not* on move now.
\r
13085 long lastTickLength;
\r
13087 int flagged = FALSE;
\r
13089 GetTimeMark(&now);
\r
13091 if (StopClockTimer() && appData.clockMode) {
\r
13092 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13093 if (WhiteOnMove(forwardMostMove)) {
\r
13094 if(blackNPS >= 0) lastTickLength = 0;
\r
13095 blackTimeRemaining -= lastTickLength;
\r
13096 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13097 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13098 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13099 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13101 if(whiteNPS >= 0) lastTickLength = 0;
\r
13102 whiteTimeRemaining -= lastTickLength;
\r
13103 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13104 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13105 pvInfoList[forwardMostMove-1].time =
\r
13106 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13108 flagged = CheckFlags();
\r
13110 CheckTimeControl();
\r
13112 if (flagged || !appData.clockMode) return;
\r
13114 switch (gameMode) {
\r
13115 case MachinePlaysBlack:
\r
13116 case MachinePlaysWhite:
\r
13117 case BeginningOfGame:
\r
13118 if (pausing) return;
\r
13122 case PlayFromGameFile:
\r
13123 case IcsExamining:
\r
13130 tickStartTM = now;
\r
13131 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13132 whiteTimeRemaining : blackTimeRemaining);
\r
13133 StartClockTimer(intendedTickLength);
\r
13137 /* Stop both clocks */
\r
13141 long lastTickLength;
\r
13144 if (!StopClockTimer()) return;
\r
13145 if (!appData.clockMode) return;
\r
13147 GetTimeMark(&now);
\r
13149 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13150 if (WhiteOnMove(forwardMostMove)) {
\r
13151 if(whiteNPS >= 0) lastTickLength = 0;
\r
13152 whiteTimeRemaining -= lastTickLength;
\r
13153 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13155 if(blackNPS >= 0) lastTickLength = 0;
\r
13156 blackTimeRemaining -= lastTickLength;
\r
13157 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13162 /* Start clock of player on move. Time may have been reset, so
\r
13163 if clock is already running, stop and restart it. */
\r
13167 (void) StopClockTimer(); /* in case it was running already */
\r
13168 DisplayBothClocks();
\r
13169 if (CheckFlags()) return;
\r
13171 if (!appData.clockMode) return;
\r
13172 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13174 GetTimeMark(&tickStartTM);
\r
13175 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13176 whiteTimeRemaining : blackTimeRemaining);
\r
13178 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13179 whiteNPS = blackNPS = -1;
\r
13180 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13181 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13182 whiteNPS = first.nps;
\r
13183 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13184 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13185 blackNPS = first.nps;
\r
13186 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13187 whiteNPS = second.nps;
\r
13188 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13189 blackNPS = second.nps;
\r
13190 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13192 StartClockTimer(intendedTickLength);
\r
13199 long second, minute, hour, day;
\r
13201 static char buf[32];
\r
13203 if (ms > 0 && ms <= 9900) {
\r
13204 /* convert milliseconds to tenths, rounding up */
\r
13205 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13207 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13211 /* convert milliseconds to seconds, rounding up */
\r
13212 /* use floating point to avoid strangeness of integer division
\r
13213 with negative dividends on many machines */
\r
13214 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13216 if (second < 0) {
\r
13218 second = -second;
\r
13221 day = second / (60 * 60 * 24);
\r
13222 second = second % (60 * 60 * 24);
\r
13223 hour = second / (60 * 60);
\r
13224 second = second % (60 * 60);
\r
13225 minute = second / 60;
\r
13226 second = second % 60;
\r
13229 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13230 sign, day, hour, minute, second);
\r
13231 else if (hour > 0)
\r
13232 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13234 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13241 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13244 StrStr(string, match)
\r
13245 char *string, *match;
\r
13249 length = strlen(match);
\r
13251 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13252 if (!strncmp(match, string, length))
\r
13259 StrCaseStr(string, match)
\r
13260 char *string, *match;
\r
13262 int i, j, length;
\r
13264 length = strlen(match);
\r
13266 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13267 for (j = 0; j < length; j++) {
\r
13268 if (ToLower(match[j]) != ToLower(string[j]))
\r
13271 if (j == length) return string;
\r
13277 #ifndef _amigados
\r
13279 StrCaseCmp(s1, s2)
\r
13285 c1 = ToLower(*s1++);
\r
13286 c2 = ToLower(*s2++);
\r
13287 if (c1 > c2) return 1;
\r
13288 if (c1 < c2) return -1;
\r
13289 if (c1 == NULLCHAR) return 0;
\r
13298 return isupper(c) ? tolower(c) : c;
\r
13306 return islower(c) ? toupper(c) : c;
\r
13308 #endif /* !_amigados */
\r
13316 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13323 StrSavePtr(s, savePtr)
\r
13324 char *s, **savePtr;
\r
13329 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13330 strcpy(*savePtr, s);
\r
13332 return(*savePtr);
\r
13340 char buf[MSG_SIZ];
\r
13342 clock = time((time_t *)NULL);
\r
13343 tm = localtime(&clock);
\r
13344 sprintf(buf, "%04d.%02d.%02d",
\r
13345 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13346 return StrSave(buf);
\r
13351 PositionToFEN(move, useFEN960)
\r
13355 int i, j, fromX, fromY, toX, toY;
\r
13360 ChessSquare piece;
\r
13362 whiteToPlay = (gameMode == EditPosition) ?
\r
13363 !blackPlaysFirst : (move % 2 == 0);
\r
13366 /* Piece placement data */
\r
13367 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13369 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13370 if (boards[move][i][j] == EmptySquare) {
\r
13372 } else { ChessSquare piece = boards[move][i][j];
\r
13373 if (emptycount > 0) {
\r
13374 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13375 *p++ = '0' + emptycount;
\r
13376 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13379 if(PieceToChar(piece) == '+') {
\r
13380 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13382 piece = (ChessSquare)(DEMOTED piece);
\r
13384 *p++ = PieceToChar(piece);
\r
13385 if(p[-1] == '~') {
\r
13386 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13387 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13392 if (emptycount > 0) {
\r
13393 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13394 *p++ = '0' + emptycount;
\r
13395 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13402 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13403 if( gameInfo.holdingsWidth ) {
\r
13404 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13406 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13407 piece = boards[move][i][BOARD_WIDTH-1];
\r
13408 if( piece != EmptySquare )
\r
13409 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13410 *p++ = PieceToChar(piece);
\r
13412 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13413 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13414 if( piece != EmptySquare )
\r
13415 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13416 *p++ = PieceToChar(piece);
\r
13419 if( q == p ) *p++ = '-';
\r
13424 /* Active color */
\r
13425 *p++ = whiteToPlay ? 'w' : 'b';
\r
13428 if(nrCastlingRights) {
\r
13430 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13431 /* [HGM] write directly from rights */
\r
13432 if(castlingRights[move][2] >= 0 &&
\r
13433 castlingRights[move][0] >= 0 )
\r
13434 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13435 if(castlingRights[move][2] >= 0 &&
\r
13436 castlingRights[move][1] >= 0 )
\r
13437 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13438 if(castlingRights[move][5] >= 0 &&
\r
13439 castlingRights[move][3] >= 0 )
\r
13440 *p++ = castlingRights[move][3] + AAA;
\r
13441 if(castlingRights[move][5] >= 0 &&
\r
13442 castlingRights[move][4] >= 0 )
\r
13443 *p++ = castlingRights[move][4] + AAA;
\r
13446 /* [HGM] write true castling rights */
\r
13447 if( nrCastlingRights == 6 ) {
\r
13448 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13449 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13450 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13451 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13452 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13453 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13454 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13455 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13458 if (q == p) *p++ = '-'; /* No castling rights */
\r
13462 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13463 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13464 /* En passant target square */
\r
13465 if (move > backwardMostMove) {
\r
13466 fromX = moveList[move - 1][0] - AAA;
\r
13467 fromY = moveList[move - 1][1] - ONE;
\r
13468 toX = moveList[move - 1][2] - AAA;
\r
13469 toY = moveList[move - 1][3] - ONE;
\r
13470 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13471 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13472 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13474 /* 2-square pawn move just happened */
\r
13475 *p++ = toX + AAA;
\r
13476 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13486 /* [HGM] find reversible plies */
\r
13487 { int i = 0, j=move;
\r
13489 if (appData.debugMode) { int k;
\r
13490 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13491 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13492 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13496 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13497 if( j == backwardMostMove ) i += initialRulePlies;
\r
13498 sprintf(p, "%d ", i);
\r
13499 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13501 /* Fullmove number */
\r
13502 sprintf(p, "%d", (move / 2) + 1);
\r
13504 return StrSave(buf);
\r
13508 ParseFEN(board, blackPlaysFirst, fen)
\r
13510 int *blackPlaysFirst;
\r
13516 ChessSquare piece;
\r
13520 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13521 if(gameInfo.holdingsWidth) {
\r
13522 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13523 board[i][0] = EmptySquare; /* black holdings */
\r
13524 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13525 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13526 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13530 /* Piece placement data */
\r
13531 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13534 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13535 if (*p == '/') p++;
\r
13536 emptycount = gameInfo.boardWidth - j;
\r
13537 while (emptycount--)
\r
13538 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13540 #if(BOARD_SIZE >= 10)
\r
13541 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13542 p++; emptycount=10;
\r
13543 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13544 while (emptycount--)
\r
13545 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13547 } else if (isdigit(*p)) {
\r
13548 emptycount = *p++ - '0';
\r
13549 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13550 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13551 while (emptycount--)
\r
13552 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13553 } else if (*p == '+' || isalpha(*p)) {
\r
13554 if (j >= gameInfo.boardWidth) return FALSE;
\r
13556 piece = CharToPiece(*++p);
\r
13557 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13558 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13559 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13560 } else piece = CharToPiece(*p++);
\r
13562 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13563 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13564 piece = (ChessSquare) (PROMOTED piece);
\r
13565 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13568 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13574 while (*p == '/' || *p == ' ') p++;
\r
13576 /* [HGM] look for Crazyhouse holdings here */
\r
13577 while(*p==' ') p++;
\r
13578 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13579 if(*p == '[') p++;
\r
13580 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13581 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13582 /* if we would allow FEN reading to set board size, we would */
\r
13583 /* have to add holdings and shift the board read so far here */
\r
13584 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13586 if((int) piece >= (int) BlackPawn ) {
\r
13587 i = (int)piece - (int)BlackPawn;
\r
13588 i = PieceToNumber((ChessSquare)i);
\r
13589 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13590 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13591 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13593 i = (int)piece - (int)WhitePawn;
\r
13594 i = PieceToNumber((ChessSquare)i);
\r
13595 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13596 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13597 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13601 if(*p == ']') *p++;
\r
13604 while(*p == ' ') p++;
\r
13606 /* Active color */
\r
13609 *blackPlaysFirst = FALSE;
\r
13612 *blackPlaysFirst = TRUE;
\r
13618 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13619 /* return the extra info in global variiables */
\r
13621 /* set defaults in case FEN is incomplete */
\r
13622 FENepStatus = EP_UNKNOWN;
\r
13623 for(i=0; i<nrCastlingRights; i++ ) {
\r
13624 FENcastlingRights[i] =
\r
13625 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13626 } /* assume possible unless obviously impossible */
\r
13627 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13628 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13629 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13630 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13631 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13632 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13633 FENrulePlies = 0;
\r
13635 while(*p==' ') p++;
\r
13636 if(nrCastlingRights) {
\r
13637 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13638 /* castling indicator present, so default becomes no castlings */
\r
13639 for(i=0; i<nrCastlingRights; i++ ) {
\r
13640 FENcastlingRights[i] = -1;
\r
13643 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13644 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13645 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13646 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13647 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13649 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13650 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13651 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13655 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13656 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13657 FENcastlingRights[2] = whiteKingFile;
\r
13660 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13661 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13662 FENcastlingRights[2] = whiteKingFile;
\r
13665 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13666 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13667 FENcastlingRights[5] = blackKingFile;
\r
13670 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13671 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13672 FENcastlingRights[5] = blackKingFile;
\r
13675 default: /* FRC castlings */
\r
13676 if(c >= 'a') { /* black rights */
\r
13677 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13678 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13679 if(i == BOARD_RGHT) break;
\r
13680 FENcastlingRights[5] = i;
\r
13682 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13683 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13685 FENcastlingRights[3] = c;
\r
13687 FENcastlingRights[4] = c;
\r
13688 } else { /* white rights */
\r
13689 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13690 if(board[0][i] == WhiteKing) break;
\r
13691 if(i == BOARD_RGHT) break;
\r
13692 FENcastlingRights[2] = i;
\r
13693 c -= AAA - 'a' + 'A';
\r
13694 if(board[0][c] >= WhiteKing) break;
\r
13696 FENcastlingRights[0] = c;
\r
13698 FENcastlingRights[1] = c;
\r
13702 if (appData.debugMode) {
\r
13703 fprintf(debugFP, "FEN castling rights:");
\r
13704 for(i=0; i<nrCastlingRights; i++)
\r
13705 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13706 fprintf(debugFP, "\n");
\r
13709 while(*p==' ') p++;
\r
13712 /* read e.p. field in games that know e.p. capture */
\r
13713 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13714 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13716 p++; FENepStatus = EP_NONE;
\r
13718 char c = *p++ - AAA;
\r
13720 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13721 if(*p >= '0' && *p <='9') *p++;
\r
13727 if(sscanf(p, "%d", &i) == 1) {
\r
13728 FENrulePlies = i; /* 50-move ply counter */
\r
13729 /* (The move number is still ignored) */
\r
13736 EditPositionPasteFEN(char *fen)
\r
13738 if (fen != NULL) {
\r
13739 Board initial_position;
\r
13741 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13742 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13745 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13746 EditPositionEvent();
\r
13747 blackPlaysFirst = savedBlackPlaysFirst;
\r
13748 CopyBoard(boards[0], initial_position);
\r
13749 /* [HGM] copy FEN attributes as well */
\r
13751 initialRulePlies = FENrulePlies;
\r
13752 epStatus[0] = FENepStatus;
\r
13753 for( i=0; i<nrCastlingRights; i++ )
\r
13754 castlingRights[0][i] = FENcastlingRights[i];
\r
13756 EditPositionDone();
\r
13757 DisplayBothClocks();
\r
13758 DrawPosition(FALSE, boards[currentMove]);
\r