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 && result != GameIsDrawn)
\r
7836 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7837 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7838 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7839 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7841 if (appData.debugMode) {
\r
7842 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7843 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7846 result = GameIsDrawn;
\r
7847 sprintf(buf, "%s but bare king", resultDetails);
\r
7848 resultDetails = buf;
\r
7854 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7855 if(result==WhiteWins) c = '+';
\r
7856 if(result==BlackWins) c = '-';
\r
7857 if(resultDetails != NULL)
\r
7858 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7860 if (resultDetails != NULL) {
\r
7861 gameInfo.result = result;
\r
7862 gameInfo.resultDetails = StrSave(resultDetails);
\r
7864 /* display last move only if game was not loaded from file */
\r
7865 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7866 DisplayMove(currentMove - 1);
\r
7868 if (forwardMostMove != 0) {
\r
7869 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7870 if (*appData.saveGameFile != NULLCHAR) {
\r
7871 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7872 } else if (appData.autoSaveGames) {
\r
7875 if (*appData.savePositionFile != NULLCHAR) {
\r
7876 SavePositionToFile(appData.savePositionFile);
\r
7881 /* Tell program how game ended in case it is learning */
\r
7882 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7883 /* engine died and we got here through time loss. In that */
\r
7884 /* case we will get a fatal error writing the pipe, which */
\r
7885 /* would otherwise lose us the PGN. */
\r
7886 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7887 /* output during GameEnds should never be fatal anymore */
\r
7888 if (gameMode == MachinePlaysWhite ||
\r
7889 gameMode == MachinePlaysBlack ||
\r
7890 gameMode == TwoMachinesPlay ||
\r
7891 gameMode == IcsPlayingWhite ||
\r
7892 gameMode == IcsPlayingBlack ||
\r
7893 gameMode == BeginningOfGame) {
\r
7894 char buf[MSG_SIZ];
\r
7895 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7897 if (first.pr != NoProc) {
\r
7898 SendToProgram(buf, &first);
\r
7900 if (second.pr != NoProc &&
\r
7901 gameMode == TwoMachinesPlay) {
\r
7902 SendToProgram(buf, &second);
\r
7907 if (appData.icsActive) {
\r
7908 if (appData.quietPlay &&
\r
7909 (gameMode == IcsPlayingWhite ||
\r
7910 gameMode == IcsPlayingBlack)) {
\r
7911 SendToICS(ics_prefix);
\r
7912 SendToICS("set shout 1\n");
\r
7914 nextGameMode = IcsIdle;
\r
7915 ics_user_moved = FALSE;
\r
7916 /* clean up premove. It's ugly when the game has ended and the
\r
7917 * premove highlights are still on the board.
\r
7920 gotPremove = FALSE;
\r
7921 ClearPremoveHighlights();
\r
7922 DrawPosition(FALSE, boards[currentMove]);
\r
7924 if (whosays == GE_ICS) {
\r
7927 if (gameMode == IcsPlayingWhite)
\r
7928 PlayIcsWinSound();
\r
7929 else if(gameMode == IcsPlayingBlack)
\r
7930 PlayIcsLossSound();
\r
7933 if (gameMode == IcsPlayingBlack)
\r
7934 PlayIcsWinSound();
\r
7935 else if(gameMode == IcsPlayingWhite)
\r
7936 PlayIcsLossSound();
\r
7939 PlayIcsDrawSound();
\r
7942 PlayIcsUnfinishedSound();
\r
7945 } else if (gameMode == EditGame ||
\r
7946 gameMode == PlayFromGameFile ||
\r
7947 gameMode == AnalyzeMode ||
\r
7948 gameMode == AnalyzeFile) {
\r
7949 nextGameMode = gameMode;
\r
7951 nextGameMode = EndOfGame;
\r
7956 nextGameMode = gameMode;
\r
7959 if (appData.noChessProgram) {
\r
7960 gameMode = nextGameMode;
\r
7962 endingGame = 0; /* [HGM] crash */
\r
7966 if (first.reuse) {
\r
7967 /* Put first chess program into idle state */
\r
7968 if (first.pr != NoProc &&
\r
7969 (gameMode == MachinePlaysWhite ||
\r
7970 gameMode == MachinePlaysBlack ||
\r
7971 gameMode == TwoMachinesPlay ||
\r
7972 gameMode == IcsPlayingWhite ||
\r
7973 gameMode == IcsPlayingBlack ||
\r
7974 gameMode == BeginningOfGame)) {
\r
7975 SendToProgram("force\n", &first);
\r
7976 if (first.usePing) {
\r
7977 char buf[MSG_SIZ];
\r
7978 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7979 SendToProgram(buf, &first);
\r
7982 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7983 /* Kill off first chess program */
\r
7984 if (first.isr != NULL)
\r
7985 RemoveInputSource(first.isr);
\r
7988 if (first.pr != NoProc) {
\r
7989 ExitAnalyzeMode();
\r
7990 DoSleep( appData.delayBeforeQuit );
\r
7991 SendToProgram("quit\n", &first);
\r
7992 DoSleep( appData.delayAfterQuit );
\r
7993 DestroyChildProcess(first.pr, first.useSigterm);
\r
7995 first.pr = NoProc;
\r
7997 if (second.reuse) {
\r
7998 /* Put second chess program into idle state */
\r
7999 if (second.pr != NoProc &&
\r
8000 gameMode == TwoMachinesPlay) {
\r
8001 SendToProgram("force\n", &second);
\r
8002 if (second.usePing) {
\r
8003 char buf[MSG_SIZ];
\r
8004 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
8005 SendToProgram(buf, &second);
\r
8008 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8009 /* Kill off second chess program */
\r
8010 if (second.isr != NULL)
\r
8011 RemoveInputSource(second.isr);
\r
8012 second.isr = NULL;
\r
8014 if (second.pr != NoProc) {
\r
8015 DoSleep( appData.delayBeforeQuit );
\r
8016 SendToProgram("quit\n", &second);
\r
8017 DoSleep( appData.delayAfterQuit );
\r
8018 DestroyChildProcess(second.pr, second.useSigterm);
\r
8020 second.pr = NoProc;
\r
8023 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8026 if (first.twoMachinesColor[0] == 'w') {
\r
8027 first.matchWins++;
\r
8029 second.matchWins++;
\r
8033 if (first.twoMachinesColor[0] == 'b') {
\r
8034 first.matchWins++;
\r
8036 second.matchWins++;
\r
8042 if (matchGame < appData.matchGames) {
\r
8044 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8045 tmp = first.twoMachinesColor;
\r
8046 first.twoMachinesColor = second.twoMachinesColor;
\r
8047 second.twoMachinesColor = tmp;
\r
8049 gameMode = nextGameMode;
\r
8051 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8052 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8053 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8054 endingGame = 0; /* [HGM] crash */
\r
8057 char buf[MSG_SIZ];
\r
8058 gameMode = nextGameMode;
\r
8059 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8060 first.tidy, second.tidy,
\r
8061 first.matchWins, second.matchWins,
\r
8062 appData.matchGames - (first.matchWins + second.matchWins));
\r
8063 DisplayFatalError(buf, 0, 0);
\r
8066 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8067 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8068 ExitAnalyzeMode();
\r
8069 gameMode = nextGameMode;
\r
8071 endingGame = 0; /* [HGM] crash */
\r
8074 /* Assumes program was just initialized (initString sent).
\r
8075 Leaves program in force mode. */
\r
8077 FeedMovesToProgram(cps, upto)
\r
8078 ChessProgramState *cps;
\r
8083 if (appData.debugMode)
\r
8084 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8085 startedFromSetupPosition ? "position and " : "",
\r
8086 backwardMostMove, upto, cps->which);
\r
8087 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8088 // [HGM] variantswitch: make engine aware of new variant
\r
8089 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8090 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8091 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8092 SendToProgram(buf, cps);
\r
8093 currentlyInitializedVariant = gameInfo.variant;
\r
8095 SendToProgram("force\n", cps);
\r
8096 if (startedFromSetupPosition) {
\r
8097 SendBoard(cps, backwardMostMove);
\r
8098 if (appData.debugMode) {
\r
8099 fprintf(debugFP, "feedMoves\n");
\r
8102 for (i = backwardMostMove; i < upto; i++) {
\r
8103 SendMoveToProgram(i, cps);
\r
8109 ResurrectChessProgram()
\r
8111 /* The chess program may have exited.
\r
8112 If so, restart it and feed it all the moves made so far. */
\r
8114 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8116 StartChessProgram(&first);
\r
8117 InitChessProgram(&first, FALSE);
\r
8118 FeedMovesToProgram(&first, currentMove);
\r
8120 if (!first.sendTime) {
\r
8121 /* can't tell gnuchess what its clock should read,
\r
8122 so we bow to its notion. */
\r
8124 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8125 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8128 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8129 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8130 SendToProgram("analyze\n", &first);
\r
8131 first.analyzing = TRUE;
\r
8136 * Button procedures
\r
8139 Reset(redraw, init)
\r
8144 if (appData.debugMode) {
\r
8145 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8146 redraw, init, gameMode);
\r
8148 pausing = pauseExamInvalid = FALSE;
\r
8149 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8151 whiteFlag = blackFlag = FALSE;
\r
8152 userOfferedDraw = FALSE;
\r
8153 hintRequested = bookRequested = FALSE;
\r
8154 first.maybeThinking = FALSE;
\r
8155 second.maybeThinking = FALSE;
\r
8156 first.bookSuspend = FALSE; // [HGM] book
\r
8157 second.bookSuspend = FALSE;
\r
8158 thinkOutput[0] = NULLCHAR;
\r
8159 lastHint[0] = NULLCHAR;
\r
8160 ClearGameInfo(&gameInfo);
\r
8161 gameInfo.variant = StringToVariant(appData.variant);
\r
8162 ics_user_moved = ics_clock_paused = FALSE;
\r
8163 ics_getting_history = H_FALSE;
\r
8165 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8166 ClearProgramStats();
\r
8167 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8170 ClearHighlights();
\r
8171 flipView = appData.flipView;
\r
8172 ClearPremoveHighlights();
\r
8173 gotPremove = FALSE;
\r
8174 alarmSounded = FALSE;
\r
8176 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8177 if(appData.serverMovesName != NULL) {
\r
8178 /* [HGM] prepare to make moves file for broadcasting */
\r
8179 clock_t t = clock();
\r
8180 if(serverMoves != NULL) fclose(serverMoves);
\r
8181 serverMoves = fopen(appData.serverMovesName, "r");
\r
8182 if(serverMoves != NULL) {
\r
8183 fclose(serverMoves);
\r
8184 /* delay 15 sec before overwriting, so all clients can see end */
\r
8185 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8187 serverMoves = fopen(appData.serverMovesName, "w");
\r
8190 ExitAnalyzeMode();
\r
8191 gameMode = BeginningOfGame;
\r
8193 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8194 InitPosition(redraw);
\r
8195 for (i = 0; i < MAX_MOVES; i++) {
\r
8196 if (commentList[i] != NULL) {
\r
8197 free(commentList[i]);
\r
8198 commentList[i] = NULL;
\r
8202 timeRemaining[0][0] = whiteTimeRemaining;
\r
8203 timeRemaining[1][0] = blackTimeRemaining;
\r
8204 if (first.pr == NULL) {
\r
8205 StartChessProgram(&first);
\r
8208 InitChessProgram(&first, startedFromSetupPosition);
\r
8211 DisplayMessage("", "");
\r
8212 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8216 AutoPlayGameLoop()
\r
8219 if (!AutoPlayOneMove())
\r
8221 if (matchMode || appData.timeDelay == 0)
\r
8223 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8225 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8234 int fromX, fromY, toX, toY;
\r
8236 if (appData.debugMode) {
\r
8237 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8240 if (gameMode != PlayFromGameFile)
\r
8243 if (currentMove >= forwardMostMove) {
\r
8244 gameMode = EditGame;
\r
8247 /* [AS] Clear current move marker at the end of a game */
\r
8248 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8253 toX = moveList[currentMove][2] - AAA;
\r
8254 toY = moveList[currentMove][3] - ONE;
\r
8256 if (moveList[currentMove][1] == '@') {
\r
8257 if (appData.highlightLastMove) {
\r
8258 SetHighlights(-1, -1, toX, toY);
\r
8261 fromX = moveList[currentMove][0] - AAA;
\r
8262 fromY = moveList[currentMove][1] - ONE;
\r
8264 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8266 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8268 if (appData.highlightLastMove) {
\r
8269 SetHighlights(fromX, fromY, toX, toY);
\r
8272 DisplayMove(currentMove);
\r
8273 SendMoveToProgram(currentMove++, &first);
\r
8274 DisplayBothClocks();
\r
8275 DrawPosition(FALSE, boards[currentMove]);
\r
8276 // [HGM] PV info: always display, routine tests if empty
\r
8277 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8283 LoadGameOneMove(readAhead)
\r
8284 ChessMove readAhead;
\r
8286 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8287 char promoChar = NULLCHAR;
\r
8288 ChessMove moveType;
\r
8289 char move[MSG_SIZ];
\r
8292 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8293 gameMode != AnalyzeMode && gameMode != Training) {
\r
8294 gameFileFP = NULL;
\r
8298 yyboardindex = forwardMostMove;
\r
8299 if (readAhead != (ChessMove)0) {
\r
8300 moveType = readAhead;
\r
8302 if (gameFileFP == NULL)
\r
8304 moveType = (ChessMove) yylex();
\r
8308 switch (moveType) {
\r
8310 if (appData.debugMode)
\r
8311 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8313 if (*p == '{' || *p == '[' || *p == '(') {
\r
8314 p[strlen(p) - 1] = NULLCHAR;
\r
8318 /* append the comment but don't display it */
\r
8319 while (*p == '\n') p++;
\r
8320 AppendComment(currentMove, p);
\r
8323 case WhiteCapturesEnPassant:
\r
8324 case BlackCapturesEnPassant:
\r
8325 case WhitePromotionChancellor:
\r
8326 case BlackPromotionChancellor:
\r
8327 case WhitePromotionArchbishop:
\r
8328 case BlackPromotionArchbishop:
\r
8329 case WhitePromotionCentaur:
\r
8330 case BlackPromotionCentaur:
\r
8331 case WhitePromotionQueen:
\r
8332 case BlackPromotionQueen:
\r
8333 case WhitePromotionRook:
\r
8334 case BlackPromotionRook:
\r
8335 case WhitePromotionBishop:
\r
8336 case BlackPromotionBishop:
\r
8337 case WhitePromotionKnight:
\r
8338 case BlackPromotionKnight:
\r
8339 case WhitePromotionKing:
\r
8340 case BlackPromotionKing:
\r
8342 case WhiteKingSideCastle:
\r
8343 case WhiteQueenSideCastle:
\r
8344 case BlackKingSideCastle:
\r
8345 case BlackQueenSideCastle:
\r
8346 case WhiteKingSideCastleWild:
\r
8347 case WhiteQueenSideCastleWild:
\r
8348 case BlackKingSideCastleWild:
\r
8349 case BlackQueenSideCastleWild:
\r
8351 case WhiteHSideCastleFR:
\r
8352 case WhiteASideCastleFR:
\r
8353 case BlackHSideCastleFR:
\r
8354 case BlackASideCastleFR:
\r
8356 if (appData.debugMode)
\r
8357 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8358 fromX = currentMoveString[0] - AAA;
\r
8359 fromY = currentMoveString[1] - ONE;
\r
8360 toX = currentMoveString[2] - AAA;
\r
8361 toY = currentMoveString[3] - ONE;
\r
8362 promoChar = currentMoveString[4];
\r
8367 if (appData.debugMode)
\r
8368 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8369 fromX = moveType == WhiteDrop ?
\r
8370 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8371 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8372 fromY = DROP_RANK;
\r
8373 toX = currentMoveString[2] - AAA;
\r
8374 toY = currentMoveString[3] - ONE;
\r
8380 case GameUnfinished:
\r
8381 if (appData.debugMode)
\r
8382 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8383 p = strchr(yy_text, '{');
\r
8384 if (p == NULL) p = strchr(yy_text, '(');
\r
8387 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8389 q = strchr(p, *p == '{' ? '}' : ')');
\r
8390 if (q != NULL) *q = NULLCHAR;
\r
8393 GameEnds(moveType, p, GE_FILE);
\r
8395 if (cmailMsgLoaded) {
\r
8396 ClearHighlights();
\r
8397 flipView = WhiteOnMove(currentMove);
\r
8398 if (moveType == GameUnfinished) flipView = !flipView;
\r
8399 if (appData.debugMode)
\r
8400 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8404 case (ChessMove) 0: /* end of file */
\r
8405 if (appData.debugMode)
\r
8406 fprintf(debugFP, "Parser hit end of file\n");
\r
8407 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8408 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8412 case MT_CHECKMATE:
\r
8413 if (WhiteOnMove(currentMove)) {
\r
8414 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8416 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8419 case MT_STALEMATE:
\r
8420 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8426 case MoveNumberOne:
\r
8427 if (lastLoadGameStart == GNUChessGame) {
\r
8428 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8429 if (appData.debugMode)
\r
8430 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8431 yy_text, (int) moveType);
\r
8432 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8434 /* else fall thru */
\r
8437 case GNUChessGame:
\r
8439 /* Reached start of next game in file */
\r
8440 if (appData.debugMode)
\r
8441 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8442 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8443 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8447 case MT_CHECKMATE:
\r
8448 if (WhiteOnMove(currentMove)) {
\r
8449 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8451 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8454 case MT_STALEMATE:
\r
8455 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8461 case PositionDiagram: /* should not happen; ignore */
\r
8462 case ElapsedTime: /* ignore */
\r
8463 case NAG: /* ignore */
\r
8464 if (appData.debugMode)
\r
8465 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8466 yy_text, (int) moveType);
\r
8467 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8470 if (appData.testLegality) {
\r
8471 if (appData.debugMode)
\r
8472 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8473 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8474 (forwardMostMove / 2) + 1,
\r
8475 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8476 DisplayError(move, 0);
\r
8479 if (appData.debugMode)
\r
8480 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8481 yy_text, currentMoveString);
\r
8482 fromX = currentMoveString[0] - AAA;
\r
8483 fromY = currentMoveString[1] - ONE;
\r
8484 toX = currentMoveString[2] - AAA;
\r
8485 toY = currentMoveString[3] - ONE;
\r
8486 promoChar = currentMoveString[4];
\r
8490 case AmbiguousMove:
\r
8491 if (appData.debugMode)
\r
8492 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8493 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8494 (forwardMostMove / 2) + 1,
\r
8495 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8496 DisplayError(move, 0);
\r
8501 case ImpossibleMove:
\r
8502 if (appData.debugMode)
\r
8503 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8504 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8505 (forwardMostMove / 2) + 1,
\r
8506 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8507 DisplayError(move, 0);
\r
8513 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8514 DrawPosition(FALSE, boards[currentMove]);
\r
8515 DisplayBothClocks();
\r
8516 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8517 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8519 (void) StopLoadGameTimer();
\r
8520 gameFileFP = NULL;
\r
8521 cmailOldMove = forwardMostMove;
\r
8524 /* currentMoveString is set as a side-effect of yylex */
\r
8525 strcat(currentMoveString, "\n");
\r
8526 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8528 thinkOutput[0] = NULLCHAR;
\r
8529 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8530 currentMove = forwardMostMove;
\r
8535 /* Load the nth game from the given file */
\r
8537 LoadGameFromFile(filename, n, title, useList)
\r
8541 /*Boolean*/ int useList;
\r
8544 char buf[MSG_SIZ];
\r
8546 if (strcmp(filename, "-") == 0) {
\r
8550 f = fopen(filename, "rb");
\r
8552 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8553 DisplayError(buf, errno);
\r
8557 if (fseek(f, 0, 0) == -1) {
\r
8558 /* f is not seekable; probably a pipe */
\r
8561 if (useList && n == 0) {
\r
8562 int error = GameListBuild(f);
\r
8564 DisplayError(_("Cannot build game list"), error);
\r
8565 } else if (!ListEmpty(&gameList) &&
\r
8566 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8567 GameListPopUp(f, title);
\r
8570 GameListDestroy();
\r
8573 if (n == 0) n = 1;
\r
8574 return LoadGame(f, n, title, FALSE);
\r
8579 MakeRegisteredMove()
\r
8581 int fromX, fromY, toX, toY;
\r
8583 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8584 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8587 if (appData.debugMode)
\r
8588 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8589 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8591 thinkOutput[0] = NULLCHAR;
\r
8592 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8593 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8594 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8595 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8596 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8597 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8598 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8599 ShowMove(fromX, fromY, toX, toY);
\r
8601 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8602 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8607 case MT_CHECKMATE:
\r
8608 if (WhiteOnMove(currentMove)) {
\r
8609 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8611 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8615 case MT_STALEMATE:
\r
8616 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8622 case CMAIL_RESIGN:
\r
8623 if (WhiteOnMove(currentMove)) {
\r
8624 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8626 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8630 case CMAIL_ACCEPT:
\r
8631 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8642 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8644 CmailLoadGame(f, gameNumber, title, useList)
\r
8652 if (gameNumber > nCmailGames) {
\r
8653 DisplayError(_("No more games in this message"), 0);
\r
8656 if (f == lastLoadGameFP) {
\r
8657 int offset = gameNumber - lastLoadGameNumber;
\r
8658 if (offset == 0) {
\r
8659 cmailMsg[0] = NULLCHAR;
\r
8660 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8661 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8662 nCmailMovesRegistered--;
\r
8664 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8665 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8666 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8669 if (! RegisterMove()) return FALSE;
\r
8673 retVal = LoadGame(f, gameNumber, title, useList);
\r
8675 /* Make move registered during previous look at this game, if any */
\r
8676 MakeRegisteredMove();
\r
8678 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8679 commentList[currentMove]
\r
8680 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8681 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8687 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8689 ReloadGame(offset)
\r
8692 int gameNumber = lastLoadGameNumber + offset;
\r
8693 if (lastLoadGameFP == NULL) {
\r
8694 DisplayError(_("No game has been loaded yet"), 0);
\r
8697 if (gameNumber <= 0) {
\r
8698 DisplayError(_("Can't back up any further"), 0);
\r
8701 if (cmailMsgLoaded) {
\r
8702 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8703 lastLoadGameTitle, lastLoadGameUseList);
\r
8705 return LoadGame(lastLoadGameFP, gameNumber,
\r
8706 lastLoadGameTitle, lastLoadGameUseList);
\r
8712 /* Load the nth game from open file f */
\r
8714 LoadGame(f, gameNumber, title, useList)
\r
8721 char buf[MSG_SIZ];
\r
8722 int gn = gameNumber;
\r
8723 ListGame *lg = NULL;
\r
8724 int numPGNTags = 0;
\r
8726 GameMode oldGameMode;
\r
8727 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8729 if (appData.debugMode)
\r
8730 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8732 if (gameMode == Training )
\r
8733 SetTrainingModeOff();
\r
8735 oldGameMode = gameMode;
\r
8736 if (gameMode != BeginningOfGame) {
\r
8737 Reset(FALSE, TRUE);
\r
8741 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8742 fclose(lastLoadGameFP);
\r
8746 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8749 fseek(f, lg->offset, 0);
\r
8750 GameListHighlight(gameNumber);
\r
8754 DisplayError(_("Game number out of range"), 0);
\r
8758 GameListDestroy();
\r
8759 if (fseek(f, 0, 0) == -1) {
\r
8760 if (f == lastLoadGameFP ?
\r
8761 gameNumber == lastLoadGameNumber + 1 :
\r
8762 gameNumber == 1) {
\r
8765 DisplayError(_("Can't seek on game file"), 0);
\r
8770 lastLoadGameFP = f;
\r
8771 lastLoadGameNumber = gameNumber;
\r
8772 strcpy(lastLoadGameTitle, title);
\r
8773 lastLoadGameUseList = useList;
\r
8777 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8778 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8779 lg->gameInfo.black);
\r
8780 DisplayTitle(buf);
\r
8781 } else if (*title != NULLCHAR) {
\r
8782 if (gameNumber > 1) {
\r
8783 sprintf(buf, "%s %d", title, gameNumber);
\r
8784 DisplayTitle(buf);
\r
8786 DisplayTitle(title);
\r
8790 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8791 gameMode = PlayFromGameFile;
\r
8795 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8796 CopyBoard(boards[0], initialPosition);
\r
8800 * Skip the first gn-1 games in the file.
\r
8801 * Also skip over anything that precedes an identifiable
\r
8802 * start of game marker, to avoid being confused by
\r
8803 * garbage at the start of the file. Currently
\r
8804 * recognized start of game markers are the move number "1",
\r
8805 * the pattern "gnuchess .* game", the pattern
\r
8806 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8807 * A game that starts with one of the latter two patterns
\r
8808 * will also have a move number 1, possibly
\r
8809 * following a position diagram.
\r
8810 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8811 * start with an unnumbered move. Does that break anything?
\r
8813 cm = lastLoadGameStart = (ChessMove) 0;
\r
8815 yyboardindex = forwardMostMove;
\r
8816 cm = (ChessMove) yylex();
\r
8818 case (ChessMove) 0:
\r
8819 if (cmailMsgLoaded) {
\r
8820 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8822 Reset(TRUE, TRUE);
\r
8823 DisplayError(_("Game not found in file"), 0);
\r
8827 case GNUChessGame:
\r
8830 lastLoadGameStart = cm;
\r
8833 case MoveNumberOne:
\r
8834 switch (lastLoadGameStart) {
\r
8835 case GNUChessGame:
\r
8839 case MoveNumberOne:
\r
8840 case (ChessMove) 0:
\r
8841 gn--; /* count this game */
\r
8842 lastLoadGameStart = cm;
\r
8851 switch (lastLoadGameStart) {
\r
8852 case GNUChessGame:
\r
8854 case MoveNumberOne:
\r
8855 case (ChessMove) 0:
\r
8856 gn--; /* count this game */
\r
8857 lastLoadGameStart = cm;
\r
8860 lastLoadGameStart = cm; /* game counted already */
\r
8868 yyboardindex = forwardMostMove;
\r
8869 cm = (ChessMove) yylex();
\r
8870 } while (cm == PGNTag || cm == Comment);
\r
8877 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8878 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8879 != CMAIL_OLD_RESULT) {
\r
8880 nCmailResults ++ ;
\r
8881 cmailResult[ CMAIL_MAX_GAMES
\r
8882 - gn - 1] = CMAIL_OLD_RESULT;
\r
8888 /* Only a NormalMove can be at the start of a game
\r
8889 * without a position diagram. */
\r
8890 if (lastLoadGameStart == (ChessMove) 0) {
\r
8892 lastLoadGameStart = MoveNumberOne;
\r
8901 if (appData.debugMode)
\r
8902 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8904 if (cm == XBoardGame) {
\r
8905 /* Skip any header junk before position diagram and/or move 1 */
\r
8907 yyboardindex = forwardMostMove;
\r
8908 cm = (ChessMove) yylex();
\r
8910 if (cm == (ChessMove) 0 ||
\r
8911 cm == GNUChessGame || cm == XBoardGame) {
\r
8912 /* Empty game; pretend end-of-file and handle later */
\r
8913 cm = (ChessMove) 0;
\r
8917 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8918 cm == PGNTag || cm == Comment)
\r
8921 } else if (cm == GNUChessGame) {
\r
8922 if (gameInfo.event != NULL) {
\r
8923 free(gameInfo.event);
\r
8925 gameInfo.event = StrSave(yy_text);
\r
8928 startedFromSetupPosition = FALSE;
\r
8929 while (cm == PGNTag) {
\r
8930 if (appData.debugMode)
\r
8931 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8932 err = ParsePGNTag(yy_text, &gameInfo);
\r
8933 if (!err) numPGNTags++;
\r
8935 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8936 if(gameInfo.variant != oldVariant) {
\r
8937 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8938 InitPosition(TRUE);
\r
8939 oldVariant = gameInfo.variant;
\r
8940 if (appData.debugMode)
\r
8941 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8945 if (gameInfo.fen != NULL) {
\r
8946 Board initial_position;
\r
8947 startedFromSetupPosition = TRUE;
\r
8948 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8949 Reset(TRUE, TRUE);
\r
8950 DisplayError(_("Bad FEN position in file"), 0);
\r
8953 CopyBoard(boards[0], initial_position);
\r
8954 if (blackPlaysFirst) {
\r
8955 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8956 CopyBoard(boards[1], initial_position);
\r
8957 strcpy(moveList[0], "");
\r
8958 strcpy(parseList[0], "");
\r
8959 timeRemaining[0][1] = whiteTimeRemaining;
\r
8960 timeRemaining[1][1] = blackTimeRemaining;
\r
8961 if (commentList[0] != NULL) {
\r
8962 commentList[1] = commentList[0];
\r
8963 commentList[0] = NULL;
\r
8966 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8968 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8970 initialRulePlies = FENrulePlies;
\r
8971 epStatus[forwardMostMove] = FENepStatus;
\r
8972 for( i=0; i< nrCastlingRights; i++ )
\r
8973 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8975 yyboardindex = forwardMostMove;
\r
8976 free(gameInfo.fen);
\r
8977 gameInfo.fen = NULL;
\r
8980 yyboardindex = forwardMostMove;
\r
8981 cm = (ChessMove) yylex();
\r
8983 /* Handle comments interspersed among the tags */
\r
8984 while (cm == Comment) {
\r
8986 if (appData.debugMode)
\r
8987 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8989 if (*p == '{' || *p == '[' || *p == '(') {
\r
8990 p[strlen(p) - 1] = NULLCHAR;
\r
8993 while (*p == '\n') p++;
\r
8994 AppendComment(currentMove, p);
\r
8995 yyboardindex = forwardMostMove;
\r
8996 cm = (ChessMove) yylex();
\r
9000 /* don't rely on existence of Event tag since if game was
\r
9001 * pasted from clipboard the Event tag may not exist
\r
9003 if (numPGNTags > 0){
\r
9005 if (gameInfo.variant == VariantNormal) {
\r
9006 gameInfo.variant = StringToVariant(gameInfo.event);
\r
9009 if( appData.autoDisplayTags ) {
\r
9010 tags = PGNTags(&gameInfo);
\r
9011 TagsPopUp(tags, CmailMsg());
\r
9016 /* Make something up, but don't display it now */
\r
9021 if (cm == PositionDiagram) {
\r
9024 Board initial_position;
\r
9026 if (appData.debugMode)
\r
9027 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9029 if (!startedFromSetupPosition) {
\r
9031 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9032 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9042 initial_position[i][j++] = CharToPiece(*p);
\r
9045 while (*p == ' ' || *p == '\t' ||
\r
9046 *p == '\n' || *p == '\r') p++;
\r
9048 if (strncmp(p, "black", strlen("black"))==0)
\r
9049 blackPlaysFirst = TRUE;
\r
9051 blackPlaysFirst = FALSE;
\r
9052 startedFromSetupPosition = TRUE;
\r
9054 CopyBoard(boards[0], initial_position);
\r
9055 if (blackPlaysFirst) {
\r
9056 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9057 CopyBoard(boards[1], initial_position);
\r
9058 strcpy(moveList[0], "");
\r
9059 strcpy(parseList[0], "");
\r
9060 timeRemaining[0][1] = whiteTimeRemaining;
\r
9061 timeRemaining[1][1] = blackTimeRemaining;
\r
9062 if (commentList[0] != NULL) {
\r
9063 commentList[1] = commentList[0];
\r
9064 commentList[0] = NULL;
\r
9067 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9070 yyboardindex = forwardMostMove;
\r
9071 cm = (ChessMove) yylex();
\r
9074 if (first.pr == NoProc) {
\r
9075 StartChessProgram(&first);
\r
9077 InitChessProgram(&first, FALSE);
\r
9078 SendToProgram("force\n", &first);
\r
9079 if (startedFromSetupPosition) {
\r
9080 SendBoard(&first, forwardMostMove);
\r
9081 if (appData.debugMode) {
\r
9082 fprintf(debugFP, "Load Game\n");
\r
9084 DisplayBothClocks();
\r
9087 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9088 loadFlag = appData.suppressLoadMoves;
\r
9090 while (cm == Comment) {
\r
9092 if (appData.debugMode)
\r
9093 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9095 if (*p == '{' || *p == '[' || *p == '(') {
\r
9096 p[strlen(p) - 1] = NULLCHAR;
\r
9099 while (*p == '\n') p++;
\r
9100 AppendComment(currentMove, p);
\r
9101 yyboardindex = forwardMostMove;
\r
9102 cm = (ChessMove) yylex();
\r
9105 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9106 cm == WhiteWins || cm == BlackWins ||
\r
9107 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9108 DisplayMessage("", _("No moves in game"));
\r
9109 if (cmailMsgLoaded) {
\r
9110 if (appData.debugMode)
\r
9111 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9112 ClearHighlights();
\r
9115 DrawPosition(FALSE, boards[currentMove]);
\r
9116 DisplayBothClocks();
\r
9117 gameMode = EditGame;
\r
9119 gameFileFP = NULL;
\r
9124 // [HGM] PV info: routine tests if comment empty
\r
9125 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9126 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9128 if (!matchMode && appData.timeDelay != 0)
\r
9129 DrawPosition(FALSE, boards[currentMove]);
\r
9131 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9132 programStats.ok_to_send = 1;
\r
9135 /* if the first token after the PGN tags is a move
\r
9136 * and not move number 1, retrieve it from the parser
\r
9138 if (cm != MoveNumberOne)
\r
9139 LoadGameOneMove(cm);
\r
9141 /* load the remaining moves from the file */
\r
9142 while (LoadGameOneMove((ChessMove)0)) {
\r
9143 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9144 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9147 /* rewind to the start of the game */
\r
9148 currentMove = backwardMostMove;
\r
9150 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9152 if (oldGameMode == AnalyzeFile ||
\r
9153 oldGameMode == AnalyzeMode) {
\r
9154 AnalyzeFileEvent();
\r
9157 if (matchMode || appData.timeDelay == 0) {
\r
9159 gameMode = EditGame;
\r
9161 } else if (appData.timeDelay > 0) {
\r
9162 AutoPlayGameLoop();
\r
9165 if (appData.debugMode)
\r
9166 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9168 loadFlag = 0; /* [HGM] true game starts */
\r
9172 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9174 ReloadPosition(offset)
\r
9177 int positionNumber = lastLoadPositionNumber + offset;
\r
9178 if (lastLoadPositionFP == NULL) {
\r
9179 DisplayError(_("No position has been loaded yet"), 0);
\r
9182 if (positionNumber <= 0) {
\r
9183 DisplayError(_("Can't back up any further"), 0);
\r
9186 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9187 lastLoadPositionTitle);
\r
9190 /* Load the nth position from the given file */
\r
9192 LoadPositionFromFile(filename, n, title)
\r
9198 char buf[MSG_SIZ];
\r
9200 if (strcmp(filename, "-") == 0) {
\r
9201 return LoadPosition(stdin, n, "stdin");
\r
9203 f = fopen(filename, "rb");
\r
9205 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9206 DisplayError(buf, errno);
\r
9209 return LoadPosition(f, n, title);
\r
9214 /* Load the nth position from the given open file, and close it */
\r
9216 LoadPosition(f, positionNumber, title)
\r
9218 int positionNumber;
\r
9221 char *p, line[MSG_SIZ];
\r
9222 Board initial_position;
\r
9223 int i, j, fenMode, pn;
\r
9225 if (gameMode == Training )
\r
9226 SetTrainingModeOff();
\r
9228 if (gameMode != BeginningOfGame) {
\r
9229 Reset(FALSE, TRUE);
\r
9231 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9232 fclose(lastLoadPositionFP);
\r
9234 if (positionNumber == 0) positionNumber = 1;
\r
9235 lastLoadPositionFP = f;
\r
9236 lastLoadPositionNumber = positionNumber;
\r
9237 strcpy(lastLoadPositionTitle, title);
\r
9238 if (first.pr == NoProc) {
\r
9239 StartChessProgram(&first);
\r
9240 InitChessProgram(&first, FALSE);
\r
9242 pn = positionNumber;
\r
9243 if (positionNumber < 0) {
\r
9244 /* Negative position number means to seek to that byte offset */
\r
9245 if (fseek(f, -positionNumber, 0) == -1) {
\r
9246 DisplayError(_("Can't seek on position file"), 0);
\r
9251 if (fseek(f, 0, 0) == -1) {
\r
9252 if (f == lastLoadPositionFP ?
\r
9253 positionNumber == lastLoadPositionNumber + 1 :
\r
9254 positionNumber == 1) {
\r
9257 DisplayError(_("Can't seek on position file"), 0);
\r
9262 /* See if this file is FEN or old-style xboard */
\r
9263 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9264 DisplayError(_("Position not found in file"), 0);
\r
9268 switch (line[0]) {
\r
9269 case '#': case 'x':
\r
9273 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9274 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9275 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9276 case '7': case '8': case '9':
\r
9277 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9278 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9279 case 'C': case 'W': case 'c': case 'w':
\r
9284 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9285 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9289 if (fenMode || line[0] == '#') pn--;
\r
9291 /* skip positions before number pn */
\r
9292 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9293 Reset(TRUE, TRUE);
\r
9294 DisplayError(_("Position not found in file"), 0);
\r
9297 if (fenMode || line[0] == '#') pn--;
\r
9302 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9303 DisplayError(_("Bad FEN position in file"), 0);
\r
9307 (void) fgets(line, MSG_SIZ, f);
\r
9308 (void) fgets(line, MSG_SIZ, f);
\r
9310 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9311 (void) fgets(line, MSG_SIZ, f);
\r
9312 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9315 initial_position[i][j++] = CharToPiece(*p);
\r
9319 blackPlaysFirst = FALSE;
\r
9321 (void) fgets(line, MSG_SIZ, f);
\r
9322 if (strncmp(line, "black", strlen("black"))==0)
\r
9323 blackPlaysFirst = TRUE;
\r
9326 startedFromSetupPosition = TRUE;
\r
9328 SendToProgram("force\n", &first);
\r
9329 CopyBoard(boards[0], initial_position);
\r
9330 if (blackPlaysFirst) {
\r
9331 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9332 strcpy(moveList[0], "");
\r
9333 strcpy(parseList[0], "");
\r
9334 CopyBoard(boards[1], initial_position);
\r
9335 DisplayMessage("", _("Black to play"));
\r
9337 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9338 DisplayMessage("", _("White to play"));
\r
9340 /* [HGM] copy FEN attributes as well */
\r
9342 initialRulePlies = FENrulePlies;
\r
9343 epStatus[forwardMostMove] = FENepStatus;
\r
9344 for( i=0; i< nrCastlingRights; i++ )
\r
9345 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9347 SendBoard(&first, forwardMostMove);
\r
9348 if (appData.debugMode) {
\r
9350 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9351 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9352 fprintf(debugFP, "Load Position\n");
\r
9355 if (positionNumber > 1) {
\r
9356 sprintf(line, "%s %d", title, positionNumber);
\r
9357 DisplayTitle(line);
\r
9359 DisplayTitle(title);
\r
9361 gameMode = EditGame;
\r
9364 timeRemaining[0][1] = whiteTimeRemaining;
\r
9365 timeRemaining[1][1] = blackTimeRemaining;
\r
9366 DrawPosition(FALSE, boards[currentMove]);
\r
9373 CopyPlayerNameIntoFileName(dest, src)
\r
9374 char **dest, *src;
\r
9376 while (*src != NULLCHAR && *src != ',') {
\r
9377 if (*src == ' ') {
\r
9381 *(*dest)++ = *src++;
\r
9386 char *DefaultFileName(ext)
\r
9389 static char def[MSG_SIZ];
\r
9392 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9394 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9396 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9400 def[0] = NULLCHAR;
\r
9405 /* Save the current game to the given file */
\r
9407 SaveGameToFile(filename, append)
\r
9412 char buf[MSG_SIZ];
\r
9414 if (strcmp(filename, "-") == 0) {
\r
9415 return SaveGame(stdout, 0, NULL);
\r
9417 f = fopen(filename, append ? "a" : "w");
\r
9419 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9420 DisplayError(buf, errno);
\r
9423 return SaveGame(f, 0, NULL);
\r
9432 static char buf[MSG_SIZ];
\r
9435 p = strchr(str, ' ');
\r
9436 if (p == NULL) return str;
\r
9437 strncpy(buf, str, p - str);
\r
9438 buf[p - str] = NULLCHAR;
\r
9442 #define PGN_MAX_LINE 75
\r
9444 #define PGN_SIDE_WHITE 0
\r
9445 #define PGN_SIDE_BLACK 1
\r
9448 static int FindFirstMoveOutOfBook( int side )
\r
9452 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9453 int index = backwardMostMove;
\r
9454 int has_book_hit = 0;
\r
9456 if( (index % 2) != side ) {
\r
9460 while( index < forwardMostMove ) {
\r
9461 /* Check to see if engine is in book */
\r
9462 int depth = pvInfoList[index].depth;
\r
9463 int score = pvInfoList[index].score;
\r
9466 if( depth <= 2 ) {
\r
9469 else if( score == 0 && depth == 63 ) {
\r
9470 in_book = 1; /* Zappa */
\r
9472 else if( score == 2 && depth == 99 ) {
\r
9473 in_book = 1; /* Abrok */
\r
9476 has_book_hit += in_book;
\r
9492 void GetOutOfBookInfo( char * buf )
\r
9496 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9498 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9499 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9503 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9504 for( i=0; i<2; i++ ) {
\r
9508 if( i > 0 && oob[0] >= 0 ) {
\r
9509 strcat( buf, " " );
\r
9512 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9513 sprintf( buf+strlen(buf), "%s%.2f",
\r
9514 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9515 pvInfoList[idx].score / 100.0 );
\r
9521 /* Save game in PGN style and close the file */
\r
9526 int i, offset, linelen, newblock;
\r
9528 // char *movetext;
\r
9530 int movelen, numlen, blank;
\r
9531 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9533 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9535 tm = time((time_t *) NULL);
\r
9537 PrintPGNTags(f, &gameInfo);
\r
9539 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9540 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9541 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9542 fprintf(f, "\n{--------------\n");
\r
9543 PrintPosition(f, backwardMostMove);
\r
9544 fprintf(f, "--------------}\n");
\r
9548 /* [AS] Out of book annotation */
\r
9549 if( appData.saveOutOfBookInfo ) {
\r
9552 GetOutOfBookInfo( buf );
\r
9554 if( buf[0] != '\0' ) {
\r
9555 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9562 i = backwardMostMove;
\r
9566 while (i < forwardMostMove) {
\r
9567 /* Print comments preceding this move */
\r
9568 if (commentList[i] != NULL) {
\r
9569 if (linelen > 0) fprintf(f, "\n");
\r
9570 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9575 /* Format move number */
\r
9576 if ((i % 2) == 0) {
\r
9577 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9580 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9582 numtext[0] = NULLCHAR;
\r
9585 numlen = strlen(numtext);
\r
9588 /* Print move number */
\r
9589 blank = linelen > 0 && numlen > 0;
\r
9590 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9599 fprintf(f, numtext);
\r
9600 linelen += numlen;
\r
9603 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9606 blank = linelen > 0 && movelen > 0;
\r
9607 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9616 fprintf(f, parseList[i]);
\r
9617 linelen += movelen;
\r
9619 /* [AS] Add PV info if present */
\r
9620 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9621 /* [HGM] add time */
\r
9622 char buf[MSG_SIZ]; int seconds = 0;
\r
9625 if(i >= backwardMostMove) {
\r
9626 if(WhiteOnMove(i))
\r
9627 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9628 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9630 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9631 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9633 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9635 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9638 if( seconds <= 0) buf[0] = 0; else
\r
9639 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9640 seconds = (seconds + 4)/10; // round to full seconds
\r
9641 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9642 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9645 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9646 pvInfoList[i].score >= 0 ? "+" : "",
\r
9647 pvInfoList[i].score / 100.0,
\r
9648 pvInfoList[i].depth,
\r
9651 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9653 /* Print score/depth */
\r
9654 blank = linelen > 0 && movelen > 0;
\r
9655 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9664 fprintf(f, move_buffer);
\r
9665 linelen += movelen;
\r
9671 /* Start a new line */
\r
9672 if (linelen > 0) fprintf(f, "\n");
\r
9674 /* Print comments after last move */
\r
9675 if (commentList[i] != NULL) {
\r
9676 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9679 /* Print result */
\r
9680 if (gameInfo.resultDetails != NULL &&
\r
9681 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9682 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9683 PGNResult(gameInfo.result));
\r
9685 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9692 /* Save game in old style and close the file */
\r
9694 SaveGameOldStyle(f)
\r
9700 tm = time((time_t *) NULL);
\r
9702 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9703 PrintOpponents(f);
\r
9705 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9706 fprintf(f, "\n[--------------\n");
\r
9707 PrintPosition(f, backwardMostMove);
\r
9708 fprintf(f, "--------------]\n");
\r
9713 i = backwardMostMove;
\r
9714 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9716 while (i < forwardMostMove) {
\r
9717 if (commentList[i] != NULL) {
\r
9718 fprintf(f, "[%s]\n", commentList[i]);
\r
9721 if ((i % 2) == 1) {
\r
9722 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9725 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9727 if (commentList[i] != NULL) {
\r
9731 if (i >= forwardMostMove) {
\r
9735 fprintf(f, "%s\n", parseList[i]);
\r
9740 if (commentList[i] != NULL) {
\r
9741 fprintf(f, "[%s]\n", commentList[i]);
\r
9744 /* This isn't really the old style, but it's close enough */
\r
9745 if (gameInfo.resultDetails != NULL &&
\r
9746 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9747 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9748 gameInfo.resultDetails);
\r
9750 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9757 /* Save the current game to open file f and close the file */
\r
9759 SaveGame(f, dummy, dummy2)
\r
9764 if (gameMode == EditPosition) EditPositionDone();
\r
9765 if (appData.oldSaveStyle)
\r
9766 return SaveGameOldStyle(f);
\r
9768 return SaveGamePGN(f);
\r
9771 /* Save the current position to the given file */
\r
9773 SavePositionToFile(filename)
\r
9777 char buf[MSG_SIZ];
\r
9779 if (strcmp(filename, "-") == 0) {
\r
9780 return SavePosition(stdout, 0, NULL);
\r
9782 f = fopen(filename, "a");
\r
9784 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9785 DisplayError(buf, errno);
\r
9788 SavePosition(f, 0, NULL);
\r
9794 /* Save the current position to the given open file and close the file */
\r
9796 SavePosition(f, dummy, dummy2)
\r
9804 if (appData.oldSaveStyle) {
\r
9805 tm = time((time_t *) NULL);
\r
9807 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9808 PrintOpponents(f);
\r
9809 fprintf(f, "[--------------\n");
\r
9810 PrintPosition(f, currentMove);
\r
9811 fprintf(f, "--------------]\n");
\r
9813 fen = PositionToFEN(currentMove, 1);
\r
9814 fprintf(f, "%s\n", fen);
\r
9822 ReloadCmailMsgEvent(unregister)
\r
9826 static char *inFilename = NULL;
\r
9827 static char *outFilename;
\r
9829 struct stat inbuf, outbuf;
\r
9832 /* Any registered moves are unregistered if unregister is set, */
\r
9833 /* i.e. invoked by the signal handler */
\r
9835 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9836 cmailMoveRegistered[i] = FALSE;
\r
9837 if (cmailCommentList[i] != NULL) {
\r
9838 free(cmailCommentList[i]);
\r
9839 cmailCommentList[i] = NULL;
\r
9842 nCmailMovesRegistered = 0;
\r
9845 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9846 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9848 nCmailResults = 0;
\r
9850 if (inFilename == NULL) {
\r
9851 /* Because the filenames are static they only get malloced once */
\r
9852 /* and they never get freed */
\r
9853 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9854 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9856 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9857 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9860 status = stat(outFilename, &outbuf);
\r
9862 cmailMailedMove = FALSE;
\r
9864 status = stat(inFilename, &inbuf);
\r
9865 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9868 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9869 counts the games, notes how each one terminated, etc.
\r
9871 It would be nice to remove this kludge and instead gather all
\r
9872 the information while building the game list. (And to keep it
\r
9873 in the game list nodes instead of having a bunch of fixed-size
\r
9874 parallel arrays.) Note this will require getting each game's
\r
9875 termination from the PGN tags, as the game list builder does
\r
9876 not process the game moves. --mann
\r
9878 cmailMsgLoaded = TRUE;
\r
9879 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9881 /* Load first game in the file or popup game menu */
\r
9882 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9884 #endif /* !WIN32 */
\r
9892 char string[MSG_SIZ];
\r
9894 if ( cmailMailedMove
\r
9895 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9896 return TRUE; /* Allow free viewing */
\r
9899 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9900 /* with the move registered when the conditions for registering no */
\r
9902 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9903 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9904 nCmailMovesRegistered --;
\r
9906 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9908 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9909 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9913 if (cmailOldMove == -1) {
\r
9914 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9918 if (currentMove > cmailOldMove + 1) {
\r
9919 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9923 if (currentMove < cmailOldMove) {
\r
9924 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9928 if (forwardMostMove > currentMove) {
\r
9929 /* Silently truncate extra moves */
\r
9933 if ( (currentMove == cmailOldMove + 1)
\r
9934 || ( (currentMove == cmailOldMove)
\r
9935 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9936 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9937 if (gameInfo.result != GameUnfinished) {
\r
9938 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9941 if (commentList[currentMove] != NULL) {
\r
9942 cmailCommentList[lastLoadGameNumber - 1]
\r
9943 = StrSave(commentList[currentMove]);
\r
9945 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9947 if (appData.debugMode)
\r
9948 fprintf(debugFP, "Saving %s for game %d\n",
\r
9949 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9952 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9954 f = fopen(string, "w");
\r
9955 if (appData.oldSaveStyle) {
\r
9956 SaveGameOldStyle(f); /* also closes the file */
\r
9958 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9959 f = fopen(string, "w");
\r
9960 SavePosition(f, 0, NULL); /* also closes the file */
\r
9962 fprintf(f, "{--------------\n");
\r
9963 PrintPosition(f, currentMove);
\r
9964 fprintf(f, "--------------}\n\n");
\r
9966 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9969 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9970 nCmailMovesRegistered ++;
\r
9971 } else if (nCmailGames == 1) {
\r
9972 DisplayError(_("You have not made a move yet"), 0);
\r
9983 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9984 FILE *commandOutput;
\r
9985 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9986 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9992 if (! cmailMsgLoaded) {
\r
9993 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9997 if (nCmailGames == nCmailResults) {
\r
9998 DisplayError(_("No unfinished games"), 0);
\r
10002 #if CMAIL_PROHIBIT_REMAIL
\r
10003 if (cmailMailedMove) {
\r
10004 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
10005 DisplayError(msg, 0);
\r
10010 if (! (cmailMailedMove || RegisterMove())) return;
\r
10012 if ( cmailMailedMove
\r
10013 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10014 sprintf(string, partCommandString,
\r
10015 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10016 commandOutput = popen(string, "r");
\r
10018 if (commandOutput == NULL) {
\r
10019 DisplayError(_("Failed to invoke cmail"), 0);
\r
10021 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10022 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10024 if (nBuffers > 1) {
\r
10025 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10026 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10027 nBytes = MSG_SIZ - 1;
\r
10029 (void) memcpy(msg, buffer, nBytes);
\r
10031 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10033 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10034 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10037 for (i = 0; i < nCmailGames; i ++) {
\r
10038 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10039 archived = FALSE;
\r
10043 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10045 sprintf(buffer, "%s/%s.%s.archive",
\r
10047 appData.cmailGameName,
\r
10049 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10050 cmailMsgLoaded = FALSE;
\r
10054 DisplayInformation(msg);
\r
10055 pclose(commandOutput);
\r
10058 if ((*cmailMsg) != '\0') {
\r
10059 DisplayInformation(cmailMsg);
\r
10064 #endif /* !WIN32 */
\r
10073 int prependComma = 0;
\r
10075 char string[MSG_SIZ]; /* Space for game-list */
\r
10078 if (!cmailMsgLoaded) return "";
\r
10080 if (cmailMailedMove) {
\r
10081 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10083 /* Create a list of games left */
\r
10084 sprintf(string, "[");
\r
10085 for (i = 0; i < nCmailGames; i ++) {
\r
10086 if (! ( cmailMoveRegistered[i]
\r
10087 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10088 if (prependComma) {
\r
10089 sprintf(number, ",%d", i + 1);
\r
10091 sprintf(number, "%d", i + 1);
\r
10092 prependComma = 1;
\r
10095 strcat(string, number);
\r
10098 strcat(string, "]");
\r
10100 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10101 switch (nCmailGames) {
\r
10103 sprintf(cmailMsg,
\r
10104 _("Still need to make move for game\n"));
\r
10108 sprintf(cmailMsg,
\r
10109 _("Still need to make moves for both games\n"));
\r
10113 sprintf(cmailMsg,
\r
10114 _("Still need to make moves for all %d games\n"),
\r
10119 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10121 sprintf(cmailMsg,
\r
10122 _("Still need to make a move for game %s\n"),
\r
10127 if (nCmailResults == nCmailGames) {
\r
10128 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10130 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10135 sprintf(cmailMsg,
\r
10136 _("Still need to make moves for games %s\n"),
\r
10142 #endif /* WIN32 */
\r
10148 if (gameMode == Training)
\r
10149 SetTrainingModeOff();
\r
10151 Reset(TRUE, TRUE);
\r
10152 cmailMsgLoaded = FALSE;
\r
10153 if (appData.icsActive) {
\r
10154 SendToICS(ics_prefix);
\r
10155 SendToICS("refresh\n");
\r
10160 ExitEvent(status)
\r
10164 if (exiting > 2) {
\r
10165 /* Give up on clean exit */
\r
10168 if (exiting > 1) {
\r
10169 /* Keep trying for clean exit */
\r
10173 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10175 if (telnetISR != NULL) {
\r
10176 RemoveInputSource(telnetISR);
\r
10178 if (icsPR != NoProc) {
\r
10179 DestroyChildProcess(icsPR, TRUE);
\r
10182 /* Save game if resource set and not already saved by GameEnds() */
\r
10183 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10184 && forwardMostMove > 0) {
\r
10185 if (*appData.saveGameFile != NULLCHAR) {
\r
10186 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10187 } else if (appData.autoSaveGames) {
\r
10190 if (*appData.savePositionFile != NULLCHAR) {
\r
10191 SavePositionToFile(appData.savePositionFile);
\r
10194 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10196 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10197 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10199 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10200 /* make sure this other one finishes before killing it! */
\r
10201 if(endingGame) { int count = 0;
\r
10202 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10203 while(endingGame && count++ < 10) DoSleep(1);
\r
10204 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10207 /* Kill off chess programs */
\r
10208 if (first.pr != NoProc) {
\r
10209 ExitAnalyzeMode();
\r
10211 DoSleep( appData.delayBeforeQuit );
\r
10212 SendToProgram("quit\n", &first);
\r
10213 DoSleep( appData.delayAfterQuit );
\r
10214 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10216 if (second.pr != NoProc) {
\r
10217 DoSleep( appData.delayBeforeQuit );
\r
10218 SendToProgram("quit\n", &second);
\r
10219 DoSleep( appData.delayAfterQuit );
\r
10220 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10222 if (first.isr != NULL) {
\r
10223 RemoveInputSource(first.isr);
\r
10225 if (second.isr != NULL) {
\r
10226 RemoveInputSource(second.isr);
\r
10229 ShutDownFrontEnd();
\r
10236 if (appData.debugMode)
\r
10237 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10241 if (gameMode == MachinePlaysWhite ||
\r
10242 gameMode == MachinePlaysBlack) {
\r
10245 DisplayBothClocks();
\r
10247 if (gameMode == PlayFromGameFile) {
\r
10248 if (appData.timeDelay >= 0)
\r
10249 AutoPlayGameLoop();
\r
10250 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10251 Reset(FALSE, TRUE);
\r
10252 SendToICS(ics_prefix);
\r
10253 SendToICS("refresh\n");
\r
10254 } else if (currentMove < forwardMostMove) {
\r
10255 ForwardInner(forwardMostMove);
\r
10257 pauseExamInvalid = FALSE;
\r
10259 switch (gameMode) {
\r
10262 case IcsExamining:
\r
10263 pauseExamForwardMostMove = forwardMostMove;
\r
10264 pauseExamInvalid = FALSE;
\r
10265 /* fall through */
\r
10266 case IcsObserving:
\r
10267 case IcsPlayingWhite:
\r
10268 case IcsPlayingBlack:
\r
10272 case PlayFromGameFile:
\r
10273 (void) StopLoadGameTimer();
\r
10277 case BeginningOfGame:
\r
10278 if (appData.icsActive) return;
\r
10279 /* else fall through */
\r
10280 case MachinePlaysWhite:
\r
10281 case MachinePlaysBlack:
\r
10282 case TwoMachinesPlay:
\r
10283 if (forwardMostMove == 0)
\r
10284 return; /* don't pause if no one has moved */
\r
10285 if ((gameMode == MachinePlaysWhite &&
\r
10286 !WhiteOnMove(forwardMostMove)) ||
\r
10287 (gameMode == MachinePlaysBlack &&
\r
10288 WhiteOnMove(forwardMostMove))) {
\r
10299 EditCommentEvent()
\r
10301 char title[MSG_SIZ];
\r
10303 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10304 strcpy(title, _("Edit comment"));
\r
10306 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10307 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10308 parseList[currentMove - 1]);
\r
10311 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10318 char *tags = PGNTags(&gameInfo);
\r
10319 EditTagsPopUp(tags);
\r
10324 AnalyzeModeEvent()
\r
10326 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10329 if (gameMode != AnalyzeFile) {
\r
10330 if (!appData.icsEngineAnalyze) {
\r
10332 if (gameMode != EditGame) return;
\r
10334 ResurrectChessProgram();
\r
10335 SendToProgram("analyze\n", &first);
\r
10336 first.analyzing = TRUE;
\r
10337 /*first.maybeThinking = TRUE;*/
\r
10338 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10339 AnalysisPopUp(_("Analysis"),
\r
10340 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10342 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10347 StartAnalysisClock();
\r
10348 GetTimeMark(&lastNodeCountTime);
\r
10349 lastNodeCount = 0;
\r
10353 AnalyzeFileEvent()
\r
10355 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10358 if (gameMode != AnalyzeMode) {
\r
10360 if (gameMode != EditGame) return;
\r
10361 ResurrectChessProgram();
\r
10362 SendToProgram("analyze\n", &first);
\r
10363 first.analyzing = TRUE;
\r
10364 /*first.maybeThinking = TRUE;*/
\r
10365 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10366 AnalysisPopUp(_("Analysis"),
\r
10367 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10369 gameMode = AnalyzeFile;
\r
10374 StartAnalysisClock();
\r
10375 GetTimeMark(&lastNodeCountTime);
\r
10376 lastNodeCount = 0;
\r
10380 MachineWhiteEvent()
\r
10382 char buf[MSG_SIZ];
\r
10383 char *bookHit = NULL;
\r
10385 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10389 if (gameMode == PlayFromGameFile ||
\r
10390 gameMode == TwoMachinesPlay ||
\r
10391 gameMode == Training ||
\r
10392 gameMode == AnalyzeMode ||
\r
10393 gameMode == EndOfGame)
\r
10396 if (gameMode == EditPosition)
\r
10397 EditPositionDone();
\r
10399 if (!WhiteOnMove(currentMove)) {
\r
10400 DisplayError(_("It is not White's turn"), 0);
\r
10404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10405 ExitAnalyzeMode();
\r
10407 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10408 gameMode == AnalyzeFile)
\r
10411 ResurrectChessProgram(); /* in case it isn't running */
\r
10412 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10413 gameMode = MachinePlaysWhite;
\r
10416 gameMode = MachinePlaysWhite;
\r
10420 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10421 DisplayTitle(buf);
\r
10422 if (first.sendName) {
\r
10423 sprintf(buf, "name %s\n", gameInfo.black);
\r
10424 SendToProgram(buf, &first);
\r
10426 if (first.sendTime) {
\r
10427 if (first.useColors) {
\r
10428 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10430 SendTimeRemaining(&first, TRUE);
\r
10432 if (first.useColors) {
\r
10433 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10435 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10436 SetMachineThinkingEnables();
\r
10437 first.maybeThinking = TRUE;
\r
10440 if (appData.autoFlipView && !flipView) {
\r
10441 flipView = !flipView;
\r
10442 DrawPosition(FALSE, NULL);
\r
10443 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10446 if(bookHit) { // [HGM] book: simulate book reply
\r
10447 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10449 programStats.nodes = programStats.depth = programStats.time =
\r
10450 programStats.score = programStats.got_only_move = 0;
\r
10451 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10453 strcpy(bookMove, "move ");
\r
10454 strcat(bookMove, bookHit);
\r
10455 HandleMachineMove(bookMove, &first);
\r
10460 MachineBlackEvent()
\r
10462 char buf[MSG_SIZ];
\r
10463 char *bookHit = NULL;
\r
10465 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10469 if (gameMode == PlayFromGameFile ||
\r
10470 gameMode == TwoMachinesPlay ||
\r
10471 gameMode == Training ||
\r
10472 gameMode == AnalyzeMode ||
\r
10473 gameMode == EndOfGame)
\r
10476 if (gameMode == EditPosition)
\r
10477 EditPositionDone();
\r
10479 if (WhiteOnMove(currentMove)) {
\r
10480 DisplayError(_("It is not Black's turn"), 0);
\r
10484 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10485 ExitAnalyzeMode();
\r
10487 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10488 gameMode == AnalyzeFile)
\r
10491 ResurrectChessProgram(); /* in case it isn't running */
\r
10492 gameMode = MachinePlaysBlack;
\r
10496 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10497 DisplayTitle(buf);
\r
10498 if (first.sendName) {
\r
10499 sprintf(buf, "name %s\n", gameInfo.white);
\r
10500 SendToProgram(buf, &first);
\r
10502 if (first.sendTime) {
\r
10503 if (first.useColors) {
\r
10504 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10506 SendTimeRemaining(&first, FALSE);
\r
10508 if (first.useColors) {
\r
10509 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10511 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10512 SetMachineThinkingEnables();
\r
10513 first.maybeThinking = TRUE;
\r
10516 if (appData.autoFlipView && flipView) {
\r
10517 flipView = !flipView;
\r
10518 DrawPosition(FALSE, NULL);
\r
10519 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10521 if(bookHit) { // [HGM] book: simulate book reply
\r
10522 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10524 programStats.nodes = programStats.depth = programStats.time =
\r
10525 programStats.score = programStats.got_only_move = 0;
\r
10526 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10528 strcpy(bookMove, "move ");
\r
10529 strcat(bookMove, bookHit);
\r
10530 HandleMachineMove(bookMove, &first);
\r
10536 DisplayTwoMachinesTitle()
\r
10538 char buf[MSG_SIZ];
\r
10539 if (appData.matchGames > 0) {
\r
10540 if (first.twoMachinesColor[0] == 'w') {
\r
10541 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10542 gameInfo.white, gameInfo.black,
\r
10543 first.matchWins, second.matchWins,
\r
10544 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10546 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10547 gameInfo.white, gameInfo.black,
\r
10548 second.matchWins, first.matchWins,
\r
10549 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10552 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10554 DisplayTitle(buf);
\r
10558 TwoMachinesEvent P((void))
\r
10561 char buf[MSG_SIZ];
\r
10562 ChessProgramState *onmove;
\r
10563 char *bookHit = NULL;
\r
10565 if (appData.noChessProgram) return;
\r
10567 switch (gameMode) {
\r
10568 case TwoMachinesPlay:
\r
10570 case MachinePlaysWhite:
\r
10571 case MachinePlaysBlack:
\r
10572 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10573 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10576 /* fall through */
\r
10577 case BeginningOfGame:
\r
10578 case PlayFromGameFile:
\r
10581 if (gameMode != EditGame) return;
\r
10583 case EditPosition:
\r
10584 EditPositionDone();
\r
10586 case AnalyzeMode:
\r
10587 case AnalyzeFile:
\r
10588 ExitAnalyzeMode();
\r
10595 forwardMostMove = currentMove;
\r
10596 ResurrectChessProgram(); /* in case first program isn't running */
\r
10598 if (second.pr == NULL) {
\r
10599 StartChessProgram(&second);
\r
10600 if (second.protocolVersion == 1) {
\r
10601 TwoMachinesEventIfReady();
\r
10603 /* kludge: allow timeout for initial "feature" command */
\r
10605 DisplayMessage("", _("Starting second chess program"));
\r
10606 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10610 DisplayMessage("", "");
\r
10611 InitChessProgram(&second, FALSE);
\r
10612 SendToProgram("force\n", &second);
\r
10613 if (startedFromSetupPosition) {
\r
10614 SendBoard(&second, backwardMostMove);
\r
10615 if (appData.debugMode) {
\r
10616 fprintf(debugFP, "Two Machines\n");
\r
10619 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10620 SendMoveToProgram(i, &second);
\r
10623 gameMode = TwoMachinesPlay;
\r
10627 DisplayTwoMachinesTitle();
\r
10628 firstMove = TRUE;
\r
10629 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10632 onmove = &second;
\r
10635 SendToProgram(first.computerString, &first);
\r
10636 if (first.sendName) {
\r
10637 sprintf(buf, "name %s\n", second.tidy);
\r
10638 SendToProgram(buf, &first);
\r
10640 SendToProgram(second.computerString, &second);
\r
10641 if (second.sendName) {
\r
10642 sprintf(buf, "name %s\n", first.tidy);
\r
10643 SendToProgram(buf, &second);
\r
10647 if (!first.sendTime || !second.sendTime) {
\r
10648 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10649 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10651 if (onmove->sendTime) {
\r
10652 if (onmove->useColors) {
\r
10653 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10655 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10657 if (onmove->useColors) {
\r
10658 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10660 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10661 // SendToProgram("go\n", onmove);
\r
10662 onmove->maybeThinking = TRUE;
\r
10663 SetMachineThinkingEnables();
\r
10667 if(bookHit) { // [HGM] book: simulate book reply
\r
10668 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10670 programStats.nodes = programStats.depth = programStats.time =
\r
10671 programStats.score = programStats.got_only_move = 0;
\r
10672 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10674 strcpy(bookMove, "move ");
\r
10675 strcat(bookMove, bookHit);
\r
10676 HandleMachineMove(bookMove, &first);
\r
10683 if (gameMode == Training) {
\r
10684 SetTrainingModeOff();
\r
10685 gameMode = PlayFromGameFile;
\r
10686 DisplayMessage("", _("Training mode off"));
\r
10688 gameMode = Training;
\r
10689 animateTraining = appData.animate;
\r
10691 /* make sure we are not already at the end of the game */
\r
10692 if (currentMove < forwardMostMove) {
\r
10693 SetTrainingModeOn();
\r
10694 DisplayMessage("", _("Training mode on"));
\r
10696 gameMode = PlayFromGameFile;
\r
10697 DisplayError(_("Already at end of game"), 0);
\r
10706 if (!appData.icsActive) return;
\r
10707 switch (gameMode) {
\r
10708 case IcsPlayingWhite:
\r
10709 case IcsPlayingBlack:
\r
10710 case IcsObserving:
\r
10712 case BeginningOfGame:
\r
10713 case IcsExamining:
\r
10719 case EditPosition:
\r
10720 EditPositionDone();
\r
10723 case AnalyzeMode:
\r
10724 case AnalyzeFile:
\r
10725 ExitAnalyzeMode();
\r
10733 gameMode = IcsIdle;
\r
10744 switch (gameMode) {
\r
10746 SetTrainingModeOff();
\r
10748 case MachinePlaysWhite:
\r
10749 case MachinePlaysBlack:
\r
10750 case BeginningOfGame:
\r
10751 SendToProgram("force\n", &first);
\r
10752 SetUserThinkingEnables();
\r
10754 case PlayFromGameFile:
\r
10755 (void) StopLoadGameTimer();
\r
10756 if (gameFileFP != NULL) {
\r
10757 gameFileFP = NULL;
\r
10760 case EditPosition:
\r
10761 EditPositionDone();
\r
10763 case AnalyzeMode:
\r
10764 case AnalyzeFile:
\r
10765 ExitAnalyzeMode();
\r
10766 SendToProgram("force\n", &first);
\r
10768 case TwoMachinesPlay:
\r
10769 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10770 ResurrectChessProgram();
\r
10771 SetUserThinkingEnables();
\r
10774 ResurrectChessProgram();
\r
10776 case IcsPlayingBlack:
\r
10777 case IcsPlayingWhite:
\r
10778 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10780 case IcsObserving:
\r
10781 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10783 case IcsExamining:
\r
10784 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10795 first.offeredDraw = second.offeredDraw = 0;
\r
10797 if (gameMode == PlayFromGameFile) {
\r
10798 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10799 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10800 DisplayTitle("");
\r
10803 if (gameMode == MachinePlaysWhite ||
\r
10804 gameMode == MachinePlaysBlack ||
\r
10805 gameMode == TwoMachinesPlay ||
\r
10806 gameMode == EndOfGame) {
\r
10807 i = forwardMostMove;
\r
10808 while (i > currentMove) {
\r
10809 SendToProgram("undo\n", &first);
\r
10812 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10813 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10814 DisplayBothClocks();
\r
10815 if (whiteFlag || blackFlag) {
\r
10816 whiteFlag = blackFlag = 0;
\r
10818 DisplayTitle("");
\r
10821 gameMode = EditGame;
\r
10828 EditPositionEvent()
\r
10830 if (gameMode == EditPosition) {
\r
10836 if (gameMode != EditGame) return;
\r
10838 gameMode = EditPosition;
\r
10841 if (currentMove > 0)
\r
10842 CopyBoard(boards[0], boards[currentMove]);
\r
10844 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10846 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10847 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10852 ExitAnalyzeMode()
\r
10854 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10855 if (appData.icsEngineAnalyze) {
\r
10856 appData.icsEngineAnalyze = FALSE;
\r
10858 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10860 if (first.analysisSupport && first.analyzing) {
\r
10861 SendToProgram("exit\n", &first);
\r
10862 first.analyzing = FALSE;
\r
10864 AnalysisPopDown();
\r
10865 thinkOutput[0] = NULLCHAR;
\r
10869 EditPositionDone()
\r
10871 startedFromSetupPosition = TRUE;
\r
10872 InitChessProgram(&first, FALSE);
\r
10873 SendToProgram("force\n", &first);
\r
10874 if (blackPlaysFirst) {
\r
10875 strcpy(moveList[0], "");
\r
10876 strcpy(parseList[0], "");
\r
10877 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10878 CopyBoard(boards[1], boards[0]);
\r
10879 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10881 epStatus[1] = epStatus[0];
\r
10882 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10885 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10887 SendBoard(&first, forwardMostMove);
\r
10888 if (appData.debugMode) {
\r
10889 fprintf(debugFP, "EditPosDone\n");
\r
10891 DisplayTitle("");
\r
10892 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10893 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10894 gameMode = EditGame;
\r
10896 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10897 ClearHighlights(); /* [AS] */
\r
10900 /* Pause for `ms' milliseconds */
\r
10901 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10908 GetTimeMark(&m1);
\r
10910 GetTimeMark(&m2);
\r
10911 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10914 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10916 SendMultiLineToICS(buf)
\r
10919 char temp[MSG_SIZ+1], *p;
\r
10922 len = strlen(buf);
\r
10923 if (len > MSG_SIZ)
\r
10926 strncpy(temp, buf, len);
\r
10931 if (*p == '\n' || *p == '\r')
\r
10936 strcat(temp, "\n");
\r
10938 SendToPlayer(temp, strlen(temp));
\r
10942 SetWhiteToPlayEvent()
\r
10944 if (gameMode == EditPosition) {
\r
10945 blackPlaysFirst = FALSE;
\r
10946 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10947 } else if (gameMode == IcsExamining) {
\r
10948 SendToICS(ics_prefix);
\r
10949 SendToICS("tomove white\n");
\r
10954 SetBlackToPlayEvent()
\r
10956 if (gameMode == EditPosition) {
\r
10957 blackPlaysFirst = TRUE;
\r
10958 currentMove = 1; /* kludge */
\r
10959 DisplayBothClocks();
\r
10961 } else if (gameMode == IcsExamining) {
\r
10962 SendToICS(ics_prefix);
\r
10963 SendToICS("tomove black\n");
\r
10968 EditPositionMenuEvent(selection, x, y)
\r
10969 ChessSquare selection;
\r
10972 char buf[MSG_SIZ];
\r
10973 ChessSquare piece = boards[0][y][x];
\r
10975 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10977 switch (selection) {
\r
10979 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10980 SendToICS(ics_prefix);
\r
10981 SendToICS("bsetup clear\n");
\r
10982 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10983 SendToICS(ics_prefix);
\r
10984 SendToICS("clearboard\n");
\r
10986 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10987 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10988 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10989 if (gameMode == IcsExamining) {
\r
10990 if (boards[currentMove][y][x] != EmptySquare) {
\r
10991 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10992 AAA + x, ONE + y);
\r
10996 boards[0][y][x] = p;
\r
11001 if (gameMode == EditPosition) {
\r
11002 DrawPosition(FALSE, boards[0]);
\r
11007 SetWhiteToPlayEvent();
\r
11011 SetBlackToPlayEvent();
\r
11014 case EmptySquare:
\r
11015 if (gameMode == IcsExamining) {
\r
11016 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11019 boards[0][y][x] = EmptySquare;
\r
11020 DrawPosition(FALSE, boards[0]);
\r
11024 case PromotePiece:
\r
11025 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11026 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11027 selection = (ChessSquare) (PROMOTED piece);
\r
11028 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11029 else selection = (ChessSquare)((int)piece - 1);
\r
11030 goto defaultlabel;
\r
11032 case DemotePiece:
\r
11033 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11034 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11035 selection = (ChessSquare) (DEMOTED piece);
\r
11036 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11037 else selection = (ChessSquare)((int)piece + 1);
\r
11038 goto defaultlabel;
\r
11042 if(gameInfo.variant == VariantShatranj ||
\r
11043 gameInfo.variant == VariantXiangqi ||
\r
11044 gameInfo.variant == VariantCourier )
\r
11045 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11046 goto defaultlabel;
\r
11050 if(gameInfo.variant == VariantXiangqi)
\r
11051 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11052 if(gameInfo.variant == VariantKnightmate)
\r
11053 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11056 if (gameMode == IcsExamining) {
\r
11057 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11058 PieceToChar(selection), AAA + x, ONE + y);
\r
11061 boards[0][y][x] = selection;
\r
11062 DrawPosition(FALSE, boards[0]);
\r
11070 DropMenuEvent(selection, x, y)
\r
11071 ChessSquare selection;
\r
11074 ChessMove moveType;
\r
11076 switch (gameMode) {
\r
11077 case IcsPlayingWhite:
\r
11078 case MachinePlaysBlack:
\r
11079 if (!WhiteOnMove(currentMove)) {
\r
11080 DisplayMoveError(_("It is Black's turn"));
\r
11083 moveType = WhiteDrop;
\r
11085 case IcsPlayingBlack:
\r
11086 case MachinePlaysWhite:
\r
11087 if (WhiteOnMove(currentMove)) {
\r
11088 DisplayMoveError(_("It is White's turn"));
\r
11091 moveType = BlackDrop;
\r
11094 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11100 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11101 selection = (ChessSquare) ((int) selection
\r
11102 + (int) BlackPawn - (int) WhitePawn);
\r
11104 if (boards[currentMove][y][x] != EmptySquare) {
\r
11105 DisplayMoveError(_("That square is occupied"));
\r
11109 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11115 /* Accept a pending offer of any kind from opponent */
\r
11117 if (appData.icsActive) {
\r
11118 SendToICS(ics_prefix);
\r
11119 SendToICS("accept\n");
\r
11120 } else if (cmailMsgLoaded) {
\r
11121 if (currentMove == cmailOldMove &&
\r
11122 commentList[cmailOldMove] != NULL &&
\r
11123 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11124 "Black offers a draw" : "White offers a draw")) {
\r
11126 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11127 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11129 DisplayError(_("There is no pending offer on this move"), 0);
\r
11130 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11133 /* Not used for offers from chess program */
\r
11140 /* Decline a pending offer of any kind from opponent */
\r
11142 if (appData.icsActive) {
\r
11143 SendToICS(ics_prefix);
\r
11144 SendToICS("decline\n");
\r
11145 } else if (cmailMsgLoaded) {
\r
11146 if (currentMove == cmailOldMove &&
\r
11147 commentList[cmailOldMove] != NULL &&
\r
11148 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11149 "Black offers a draw" : "White offers a draw")) {
\r
11151 AppendComment(cmailOldMove, "Draw declined");
\r
11152 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11153 #endif /*NOTDEF*/
\r
11155 DisplayError(_("There is no pending offer on this move"), 0);
\r
11158 /* Not used for offers from chess program */
\r
11165 /* Issue ICS rematch command */
\r
11166 if (appData.icsActive) {
\r
11167 SendToICS(ics_prefix);
\r
11168 SendToICS("rematch\n");
\r
11175 /* Call your opponent's flag (claim a win on time) */
\r
11176 if (appData.icsActive) {
\r
11177 SendToICS(ics_prefix);
\r
11178 SendToICS("flag\n");
\r
11180 switch (gameMode) {
\r
11183 case MachinePlaysWhite:
\r
11186 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11189 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11191 DisplayError(_("Your opponent is not out of time"), 0);
\r
11194 case MachinePlaysBlack:
\r
11197 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11200 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11202 DisplayError(_("Your opponent is not out of time"), 0);
\r
11212 /* Offer draw or accept pending draw offer from opponent */
\r
11214 if (appData.icsActive) {
\r
11215 /* Note: tournament rules require draw offers to be
\r
11216 made after you make your move but before you punch
\r
11217 your clock. Currently ICS doesn't let you do that;
\r
11218 instead, you immediately punch your clock after making
\r
11219 a move, but you can offer a draw at any time. */
\r
11221 SendToICS(ics_prefix);
\r
11222 SendToICS("draw\n");
\r
11223 } else if (cmailMsgLoaded) {
\r
11224 if (currentMove == cmailOldMove &&
\r
11225 commentList[cmailOldMove] != NULL &&
\r
11226 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11227 "Black offers a draw" : "White offers a draw")) {
\r
11228 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11229 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11230 } else if (currentMove == cmailOldMove + 1) {
\r
11231 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11232 "White offers a draw" : "Black offers a draw";
\r
11233 AppendComment(currentMove, offer);
\r
11234 DisplayComment(currentMove - 1, offer);
\r
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11237 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11238 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11240 } else if (first.offeredDraw) {
\r
11241 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11243 if (first.sendDrawOffers) {
\r
11244 SendToProgram("draw\n", &first);
\r
11245 userOfferedDraw = TRUE;
\r
11253 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11255 if (appData.icsActive) {
\r
11256 SendToICS(ics_prefix);
\r
11257 SendToICS("adjourn\n");
\r
11259 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11267 /* Offer Abort or accept pending Abort offer from opponent */
\r
11269 if (appData.icsActive) {
\r
11270 SendToICS(ics_prefix);
\r
11271 SendToICS("abort\n");
\r
11273 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11280 /* Resign. You can do this even if it's not your turn. */
\r
11282 if (appData.icsActive) {
\r
11283 SendToICS(ics_prefix);
\r
11284 SendToICS("resign\n");
\r
11286 switch (gameMode) {
\r
11287 case MachinePlaysWhite:
\r
11288 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11290 case MachinePlaysBlack:
\r
11291 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11294 if (cmailMsgLoaded) {
\r
11296 if (WhiteOnMove(cmailOldMove)) {
\r
11297 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11299 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11301 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11312 StopObservingEvent()
\r
11314 /* Stop observing current games */
\r
11315 SendToICS(ics_prefix);
\r
11316 SendToICS("unobserve\n");
\r
11320 StopExaminingEvent()
\r
11322 /* Stop observing current game */
\r
11323 SendToICS(ics_prefix);
\r
11324 SendToICS("unexamine\n");
\r
11328 ForwardInner(target)
\r
11333 if (appData.debugMode)
\r
11334 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11335 target, currentMove, forwardMostMove);
\r
11337 if (gameMode == EditPosition)
\r
11340 if (gameMode == PlayFromGameFile && !pausing)
\r
11343 if (gameMode == IcsExamining && pausing)
\r
11344 limit = pauseExamForwardMostMove;
\r
11346 limit = forwardMostMove;
\r
11348 if (target > limit) target = limit;
\r
11350 if (target > 0 && moveList[target - 1][0]) {
\r
11351 int fromX, fromY, toX, toY;
\r
11352 toX = moveList[target - 1][2] - AAA;
\r
11353 toY = moveList[target - 1][3] - ONE;
\r
11354 if (moveList[target - 1][1] == '@') {
\r
11355 if (appData.highlightLastMove) {
\r
11356 SetHighlights(-1, -1, toX, toY);
\r
11359 fromX = moveList[target - 1][0] - AAA;
\r
11360 fromY = moveList[target - 1][1] - ONE;
\r
11361 if (target == currentMove + 1) {
\r
11362 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11364 if (appData.highlightLastMove) {
\r
11365 SetHighlights(fromX, fromY, toX, toY);
\r
11369 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11370 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11371 gameMode == AnalyzeFile) {
\r
11372 while (currentMove < target) {
\r
11373 SendMoveToProgram(currentMove++, &first);
\r
11376 currentMove = target;
\r
11379 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11380 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11381 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11383 DisplayBothClocks();
\r
11384 DisplayMove(currentMove - 1);
\r
11385 DrawPosition(FALSE, boards[currentMove]);
\r
11386 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11387 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11388 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11396 if (gameMode == IcsExamining && !pausing) {
\r
11397 SendToICS(ics_prefix);
\r
11398 SendToICS("forward\n");
\r
11400 ForwardInner(currentMove + 1);
\r
11407 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11408 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11409 * the remaining moves to the engine. Otherwise we get analysis output
\r
11410 * after each move.
\r
11412 if (first.analysisSupport) {
\r
11413 SendToProgram("exit\nforce\n", &first);
\r
11414 first.analyzing = FALSE;
\r
11418 if (gameMode == IcsExamining && !pausing) {
\r
11419 SendToICS(ics_prefix);
\r
11420 SendToICS("forward 999999\n");
\r
11422 ForwardInner(forwardMostMove);
\r
11425 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11426 /* we have fed all the moves, so reactivate analysis mode */
\r
11427 SendToProgram("analyze\n", &first);
\r
11428 first.analyzing = TRUE;
\r
11429 /*first.maybeThinking = TRUE;*/
\r
11430 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11435 BackwardInner(target)
\r
11438 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11440 if (appData.debugMode)
\r
11441 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11442 target, currentMove, forwardMostMove);
\r
11444 if (gameMode == EditPosition) return;
\r
11445 if (currentMove <= backwardMostMove) {
\r
11446 ClearHighlights();
\r
11447 DrawPosition(full_redraw, boards[currentMove]);
\r
11450 if (gameMode == PlayFromGameFile && !pausing)
\r
11453 if (moveList[target][0]) {
\r
11454 int fromX, fromY, toX, toY;
\r
11455 toX = moveList[target][2] - AAA;
\r
11456 toY = moveList[target][3] - ONE;
\r
11457 if (moveList[target][1] == '@') {
\r
11458 if (appData.highlightLastMove) {
\r
11459 SetHighlights(-1, -1, toX, toY);
\r
11462 fromX = moveList[target][0] - AAA;
\r
11463 fromY = moveList[target][1] - ONE;
\r
11464 if (target == currentMove - 1) {
\r
11465 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11467 if (appData.highlightLastMove) {
\r
11468 SetHighlights(fromX, fromY, toX, toY);
\r
11472 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11473 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11474 while (currentMove > target) {
\r
11475 SendToProgram("undo\n", &first);
\r
11479 currentMove = target;
\r
11482 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11483 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11484 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11486 DisplayBothClocks();
\r
11487 DisplayMove(currentMove - 1);
\r
11488 DrawPosition(full_redraw, boards[currentMove]);
\r
11489 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11490 // [HGM] PV info: routine tests if comment empty
\r
11491 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11497 if (gameMode == IcsExamining && !pausing) {
\r
11498 SendToICS(ics_prefix);
\r
11499 SendToICS("backward\n");
\r
11501 BackwardInner(currentMove - 1);
\r
11508 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11509 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11510 * all the moves. Otherwise we get analysis output after each undo.
\r
11512 if (first.analysisSupport) {
\r
11513 SendToProgram("exit\nforce\n", &first);
\r
11514 first.analyzing = FALSE;
\r
11518 if (gameMode == IcsExamining && !pausing) {
\r
11519 SendToICS(ics_prefix);
\r
11520 SendToICS("backward 999999\n");
\r
11522 BackwardInner(backwardMostMove);
\r
11525 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11526 /* we have fed all the moves, so reactivate analysis mode */
\r
11527 SendToProgram("analyze\n", &first);
\r
11528 first.analyzing = TRUE;
\r
11529 /*first.maybeThinking = TRUE;*/
\r
11530 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11535 ToNrEvent(int to)
\r
11537 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11538 if (to >= forwardMostMove) to = forwardMostMove;
\r
11539 if (to <= backwardMostMove) to = backwardMostMove;
\r
11540 if (to < currentMove) {
\r
11541 BackwardInner(to);
\r
11543 ForwardInner(to);
\r
11550 if (gameMode != IcsExamining) {
\r
11551 DisplayError(_("You are not examining a game"), 0);
\r
11555 DisplayError(_("You can't revert while pausing"), 0);
\r
11558 SendToICS(ics_prefix);
\r
11559 SendToICS("revert\n");
\r
11563 RetractMoveEvent()
\r
11565 switch (gameMode) {
\r
11566 case MachinePlaysWhite:
\r
11567 case MachinePlaysBlack:
\r
11568 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11569 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11572 if (forwardMostMove < 2) return;
\r
11573 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11574 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11575 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11576 DisplayBothClocks();
\r
11577 DisplayMove(currentMove - 1);
\r
11578 ClearHighlights();/*!! could figure this out*/
\r
11579 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11580 SendToProgram("remove\n", &first);
\r
11581 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11584 case BeginningOfGame:
\r
11588 case IcsPlayingWhite:
\r
11589 case IcsPlayingBlack:
\r
11590 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11591 SendToICS(ics_prefix);
\r
11592 SendToICS("takeback 2\n");
\r
11594 SendToICS(ics_prefix);
\r
11595 SendToICS("takeback 1\n");
\r
11604 ChessProgramState *cps;
\r
11606 switch (gameMode) {
\r
11607 case MachinePlaysWhite:
\r
11608 if (!WhiteOnMove(forwardMostMove)) {
\r
11609 DisplayError(_("It is your turn"), 0);
\r
11614 case MachinePlaysBlack:
\r
11615 if (WhiteOnMove(forwardMostMove)) {
\r
11616 DisplayError(_("It is your turn"), 0);
\r
11621 case TwoMachinesPlay:
\r
11622 if (WhiteOnMove(forwardMostMove) ==
\r
11623 (first.twoMachinesColor[0] == 'w')) {
\r
11629 case BeginningOfGame:
\r
11633 SendToProgram("?\n", cps);
\r
11637 TruncateGameEvent()
\r
11640 if (gameMode != EditGame) return;
\r
11647 if (forwardMostMove > currentMove) {
\r
11648 if (gameInfo.resultDetails != NULL) {
\r
11649 free(gameInfo.resultDetails);
\r
11650 gameInfo.resultDetails = NULL;
\r
11651 gameInfo.result = GameUnfinished;
\r
11653 forwardMostMove = currentMove;
\r
11654 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11662 if (appData.noChessProgram) return;
\r
11663 switch (gameMode) {
\r
11664 case MachinePlaysWhite:
\r
11665 if (WhiteOnMove(forwardMostMove)) {
\r
11666 DisplayError(_("Wait until your turn"), 0);
\r
11670 case BeginningOfGame:
\r
11671 case MachinePlaysBlack:
\r
11672 if (!WhiteOnMove(forwardMostMove)) {
\r
11673 DisplayError(_("Wait until your turn"), 0);
\r
11678 DisplayError(_("No hint available"), 0);
\r
11681 SendToProgram("hint\n", &first);
\r
11682 hintRequested = TRUE;
\r
11688 if (appData.noChessProgram) return;
\r
11689 switch (gameMode) {
\r
11690 case MachinePlaysWhite:
\r
11691 if (WhiteOnMove(forwardMostMove)) {
\r
11692 DisplayError(_("Wait until your turn"), 0);
\r
11696 case BeginningOfGame:
\r
11697 case MachinePlaysBlack:
\r
11698 if (!WhiteOnMove(forwardMostMove)) {
\r
11699 DisplayError(_("Wait until your turn"), 0);
\r
11703 case EditPosition:
\r
11704 EditPositionDone();
\r
11706 case TwoMachinesPlay:
\r
11711 SendToProgram("bk\n", &first);
\r
11712 bookOutput[0] = NULLCHAR;
\r
11713 bookRequested = TRUE;
\r
11719 char *tags = PGNTags(&gameInfo);
\r
11720 TagsPopUp(tags, CmailMsg());
\r
11724 /* end button procedures */
\r
11727 PrintPosition(fp, move)
\r
11733 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11734 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11735 char c = PieceToChar(boards[move][i][j]);
\r
11736 fputc(c == 'x' ? '.' : c, fp);
\r
11737 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11740 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11741 fprintf(fp, "white to play\n");
\r
11743 fprintf(fp, "black to play\n");
\r
11747 PrintOpponents(fp)
\r
11750 if (gameInfo.white != NULL) {
\r
11751 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11753 fprintf(fp, "\n");
\r
11757 /* Find last component of program's own name, using some heuristics */
\r
11759 TidyProgramName(prog, host, buf)
\r
11760 char *prog, *host, buf[MSG_SIZ];
\r
11763 int local = (strcmp(host, "localhost") == 0);
\r
11764 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11766 while (*p == ' ') p++;
\r
11769 if (*prog == '"' || *prog == '\'') {
\r
11770 q = strchr(prog + 1, *prog);
\r
11772 q = strchr(prog, ' ');
\r
11774 if (q == NULL) q = prog + strlen(prog);
\r
11776 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11778 if(p == prog && *p == '"') p++;
\r
11779 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11780 memcpy(buf, p, q - p);
\r
11781 buf[q - p] = NULLCHAR;
\r
11783 strcat(buf, "@");
\r
11784 strcat(buf, host);
\r
11789 TimeControlTagValue()
\r
11791 char buf[MSG_SIZ];
\r
11792 if (!appData.clockMode) {
\r
11793 strcpy(buf, "-");
\r
11794 } else if (movesPerSession > 0) {
\r
11795 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11796 } else if (timeIncrement == 0) {
\r
11797 sprintf(buf, "%ld", timeControl/1000);
\r
11799 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11801 return StrSave(buf);
\r
11807 /* This routine is used only for certain modes */
\r
11808 VariantClass v = gameInfo.variant;
\r
11809 ClearGameInfo(&gameInfo);
\r
11810 gameInfo.variant = v;
\r
11812 switch (gameMode) {
\r
11813 case MachinePlaysWhite:
\r
11814 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11815 gameInfo.site = StrSave(HostName());
\r
11816 gameInfo.date = PGNDate();
\r
11817 gameInfo.round = StrSave("-");
\r
11818 gameInfo.white = StrSave(first.tidy);
\r
11819 gameInfo.black = StrSave(UserName());
\r
11820 gameInfo.timeControl = TimeControlTagValue();
\r
11823 case MachinePlaysBlack:
\r
11824 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11825 gameInfo.site = StrSave(HostName());
\r
11826 gameInfo.date = PGNDate();
\r
11827 gameInfo.round = StrSave("-");
\r
11828 gameInfo.white = StrSave(UserName());
\r
11829 gameInfo.black = StrSave(first.tidy);
\r
11830 gameInfo.timeControl = TimeControlTagValue();
\r
11833 case TwoMachinesPlay:
\r
11834 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11835 gameInfo.site = StrSave(HostName());
\r
11836 gameInfo.date = PGNDate();
\r
11837 if (matchGame > 0) {
\r
11838 char buf[MSG_SIZ];
\r
11839 sprintf(buf, "%d", matchGame);
\r
11840 gameInfo.round = StrSave(buf);
\r
11842 gameInfo.round = StrSave("-");
\r
11844 if (first.twoMachinesColor[0] == 'w') {
\r
11845 gameInfo.white = StrSave(first.tidy);
\r
11846 gameInfo.black = StrSave(second.tidy);
\r
11848 gameInfo.white = StrSave(second.tidy);
\r
11849 gameInfo.black = StrSave(first.tidy);
\r
11851 gameInfo.timeControl = TimeControlTagValue();
\r
11855 gameInfo.event = StrSave("Edited game");
\r
11856 gameInfo.site = StrSave(HostName());
\r
11857 gameInfo.date = PGNDate();
\r
11858 gameInfo.round = StrSave("-");
\r
11859 gameInfo.white = StrSave("-");
\r
11860 gameInfo.black = StrSave("-");
\r
11863 case EditPosition:
\r
11864 gameInfo.event = StrSave("Edited position");
\r
11865 gameInfo.site = StrSave(HostName());
\r
11866 gameInfo.date = PGNDate();
\r
11867 gameInfo.round = StrSave("-");
\r
11868 gameInfo.white = StrSave("-");
\r
11869 gameInfo.black = StrSave("-");
\r
11872 case IcsPlayingWhite:
\r
11873 case IcsPlayingBlack:
\r
11874 case IcsObserving:
\r
11875 case IcsExamining:
\r
11878 case PlayFromGameFile:
\r
11879 gameInfo.event = StrSave("Game from non-PGN file");
\r
11880 gameInfo.site = StrSave(HostName());
\r
11881 gameInfo.date = PGNDate();
\r
11882 gameInfo.round = StrSave("-");
\r
11883 gameInfo.white = StrSave("?");
\r
11884 gameInfo.black = StrSave("?");
\r
11893 ReplaceComment(index, text)
\r
11899 while (*text == '\n') text++;
\r
11900 len = strlen(text);
\r
11901 while (len > 0 && text[len - 1] == '\n') len--;
\r
11903 if (commentList[index] != NULL)
\r
11904 free(commentList[index]);
\r
11907 commentList[index] = NULL;
\r
11910 commentList[index] = (char *) malloc(len + 2);
\r
11911 strncpy(commentList[index], text, len);
\r
11912 commentList[index][len] = '\n';
\r
11913 commentList[index][len + 1] = NULLCHAR;
\r
11926 if (ch == '\r') continue;
\r
11928 } while (ch != '\0');
\r
11932 AppendComment(index, text)
\r
11939 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11942 while (*text == '\n') text++;
\r
11943 len = strlen(text);
\r
11944 while (len > 0 && text[len - 1] == '\n') len--;
\r
11946 if (len == 0) return;
\r
11948 if (commentList[index] != NULL) {
\r
11949 old = commentList[index];
\r
11950 oldlen = strlen(old);
\r
11951 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11952 strcpy(commentList[index], old);
\r
11954 strncpy(&commentList[index][oldlen], text, len);
\r
11955 commentList[index][oldlen + len] = '\n';
\r
11956 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11958 commentList[index] = (char *) malloc(len + 2);
\r
11959 strncpy(commentList[index], text, len);
\r
11960 commentList[index][len] = '\n';
\r
11961 commentList[index][len + 1] = NULLCHAR;
\r
11965 static char * FindStr( char * text, char * sub_text )
\r
11967 char * result = strstr( text, sub_text );
\r
11969 if( result != NULL ) {
\r
11970 result += strlen( sub_text );
\r
11976 /* [AS] Try to extract PV info from PGN comment */
\r
11977 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11978 char *GetInfoFromComment( int index, char * text )
\r
11980 char * sep = text;
\r
11982 if( text != NULL && index > 0 ) {
\r
11985 int time = -1, sec = 0, deci;
\r
11986 char * s_eval = FindStr( text, "[%eval " );
\r
11987 char * s_emt = FindStr( text, "[%emt " );
\r
11989 if( s_eval != NULL || s_emt != NULL ) {
\r
11993 if( s_eval != NULL ) {
\r
11994 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11998 if( delim != ']' ) {
\r
12003 if( s_emt != NULL ) {
\r
12007 /* We expect something like: [+|-]nnn.nn/dd */
\r
12008 int score_lo = 0;
\r
12010 sep = strchr( text, '/' );
\r
12011 if( sep == NULL || sep < (text+4) ) {
\r
12015 time = -1; sec = -1; deci = -1;
\r
12016 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12017 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12018 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12019 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12023 if( score_lo < 0 || score_lo >= 100 ) {
\r
12027 if(sec >= 0) time = 600*time + 10*sec; else
\r
12028 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12030 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12032 /* [HGM] PV time: now locate end of PV info */
\r
12033 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12035 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12037 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12039 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12040 while(*sep == ' ') sep++;
\r
12043 if( depth <= 0 ) {
\r
12051 pvInfoList[index-1].depth = depth;
\r
12052 pvInfoList[index-1].score = score;
\r
12053 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12059 SendToProgram(message, cps)
\r
12061 ChessProgramState *cps;
\r
12063 int count, outCount, error;
\r
12064 char buf[MSG_SIZ];
\r
12066 if (cps->pr == NULL) return;
\r
12069 if (appData.debugMode) {
\r
12071 GetTimeMark(&now);
\r
12072 fprintf(debugFP, "%ld >%-6s: %s",
\r
12073 SubtractTimeMarks(&now, &programStartTime),
\r
12074 cps->which, message);
\r
12077 count = strlen(message);
\r
12078 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12079 if (outCount < count && !exiting
\r
12080 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12081 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12082 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12083 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12084 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12085 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12087 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12089 gameInfo.resultDetails = buf;
\r
12091 DisplayFatalError(buf, error, 1);
\r
12096 ReceiveFromProgram(isr, closure, message, count, error)
\r
12097 InputSourceRef isr;
\r
12098 VOIDSTAR closure;
\r
12104 char buf[MSG_SIZ];
\r
12105 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12107 if (isr != cps->isr) return; /* Killed intentionally */
\r
12108 if (count <= 0) {
\r
12109 if (count == 0) {
\r
12111 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12112 cps->which, cps->program);
\r
12113 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12114 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12115 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12116 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12118 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12120 gameInfo.resultDetails = buf;
\r
12122 RemoveInputSource(cps->isr);
\r
12123 DisplayFatalError(buf, 0, 1);
\r
12126 _("Error reading from %s chess program (%s)"),
\r
12127 cps->which, cps->program);
\r
12128 RemoveInputSource(cps->isr);
\r
12130 /* [AS] Program is misbehaving badly... kill it */
\r
12131 if( count == -2 ) {
\r
12132 DestroyChildProcess( cps->pr, 9 );
\r
12133 cps->pr = NoProc;
\r
12136 DisplayFatalError(buf, error, 1);
\r
12141 if ((end_str = strchr(message, '\r')) != NULL)
\r
12142 *end_str = NULLCHAR;
\r
12143 if ((end_str = strchr(message, '\n')) != NULL)
\r
12144 *end_str = NULLCHAR;
\r
12146 if (appData.debugMode) {
\r
12147 TimeMark now; int print = 1;
\r
12148 char *quote = ""; char c; int i;
\r
12150 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12151 char start = message[0];
\r
12152 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12153 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12154 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12155 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12156 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12157 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12158 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12159 { quote = "# "; print = (appData.engineComments == 2); }
\r
12160 message[0] = start; // restore original message
\r
12163 GetTimeMark(&now);
\r
12164 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12165 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12171 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12172 if (appData.icsEngineAnalyze) {
\r
12173 if (strstr(message, "whisper") != NULL ||
\r
12174 strstr(message, "kibitz") != NULL ||
\r
12175 strstr(message, "tellics") != NULL) return;
\r
12178 HandleMachineMove(message, cps);
\r
12183 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12184 ChessProgramState *cps;
\r
12185 int mps, inc, sd, st;
\r
12188 char buf[MSG_SIZ];
\r
12191 if( timeControl_2 > 0 ) {
\r
12192 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12193 tc = timeControl_2;
\r
12196 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12197 inc /= cps->timeOdds;
\r
12198 st /= cps->timeOdds;
\r
12200 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12203 /* Set exact time per move, normally using st command */
\r
12204 if (cps->stKludge) {
\r
12205 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12206 seconds = st % 60;
\r
12207 if (seconds == 0) {
\r
12208 sprintf(buf, "level 1 %d\n", st/60);
\r
12210 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12213 sprintf(buf, "st %d\n", st);
\r
12216 /* Set conventional or incremental time control, using level command */
\r
12217 if (seconds == 0) {
\r
12218 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12219 Fixed in later versions, but still avoid :seconds
\r
12220 when seconds is 0. */
\r
12221 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12223 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12224 seconds, inc/1000);
\r
12227 SendToProgram(buf, cps);
\r
12229 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12230 /* Orthogonally, limit search to given depth */
\r
12232 if (cps->sdKludge) {
\r
12233 sprintf(buf, "depth\n%d\n", sd);
\r
12235 sprintf(buf, "sd %d\n", sd);
\r
12237 SendToProgram(buf, cps);
\r
12240 if(cps->nps > 0) { /* [HGM] nps */
\r
12241 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12243 sprintf(buf, "nps %d\n", cps->nps);
\r
12244 SendToProgram(buf, cps);
\r
12249 ChessProgramState *WhitePlayer()
\r
12250 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12252 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12253 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12259 SendTimeRemaining(cps, machineWhite)
\r
12260 ChessProgramState *cps;
\r
12261 int /*boolean*/ machineWhite;
\r
12263 char message[MSG_SIZ];
\r
12264 long time, otime;
\r
12266 /* Note: this routine must be called when the clocks are stopped
\r
12267 or when they have *just* been set or switched; otherwise
\r
12268 it will be off by the time since the current tick started.
\r
12270 if (machineWhite) {
\r
12271 time = whiteTimeRemaining / 10;
\r
12272 otime = blackTimeRemaining / 10;
\r
12274 time = blackTimeRemaining / 10;
\r
12275 otime = whiteTimeRemaining / 10;
\r
12277 /* [HGM] translate opponent's time by time-odds factor */
\r
12278 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12279 if (appData.debugMode) {
\r
12280 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12283 if (time <= 0) time = 1;
\r
12284 if (otime <= 0) otime = 1;
\r
12286 sprintf(message, "time %ld\n", time);
\r
12287 SendToProgram(message, cps);
\r
12289 sprintf(message, "otim %ld\n", otime);
\r
12290 SendToProgram(message, cps);
\r
12294 BoolFeature(p, name, loc, cps)
\r
12298 ChessProgramState *cps;
\r
12300 char buf[MSG_SIZ];
\r
12301 int len = strlen(name);
\r
12303 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12305 sscanf(*p, "%d", &val);
\r
12306 *loc = (val != 0);
\r
12307 while (**p && **p != ' ') (*p)++;
\r
12308 sprintf(buf, "accepted %s\n", name);
\r
12309 SendToProgram(buf, cps);
\r
12316 IntFeature(p, name, loc, cps)
\r
12320 ChessProgramState *cps;
\r
12322 char buf[MSG_SIZ];
\r
12323 int len = strlen(name);
\r
12324 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12326 sscanf(*p, "%d", loc);
\r
12327 while (**p && **p != ' ') (*p)++;
\r
12328 sprintf(buf, "accepted %s\n", name);
\r
12329 SendToProgram(buf, cps);
\r
12336 StringFeature(p, name, loc, cps)
\r
12340 ChessProgramState *cps;
\r
12342 char buf[MSG_SIZ];
\r
12343 int len = strlen(name);
\r
12344 if (strncmp((*p), name, len) == 0
\r
12345 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12347 sscanf(*p, "%[^\"]", loc);
\r
12348 while (**p && **p != '\"') (*p)++;
\r
12349 if (**p == '\"') (*p)++;
\r
12350 sprintf(buf, "accepted %s\n", name);
\r
12351 SendToProgram(buf, cps);
\r
12358 ParseOption(Option *opt, ChessProgramState *cps)
\r
12359 // [HGM] options: process the string that defines an engine option, and determine
\r
12360 // name, type, default value, and allowed value range
\r
12362 char *p, *q, buf[MSG_SIZ];
\r
12363 int n, min = (-1)<<31, max = 1<<31, def;
\r
12365 if(p = strstr(opt->name, " -spin ")) {
\r
12366 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12367 if(max < min) max = min; // enforce consistency
\r
12368 if(def < min) def = min;
\r
12369 if(def > max) def = max;
\r
12370 opt->value = def;
\r
12373 opt->type = Spin;
\r
12374 } else if(p = strstr(opt->name, " -string ")) {
\r
12375 opt->textValue = p+9;
\r
12376 opt->type = TextBox;
\r
12377 } else if(p = strstr(opt->name, " -check ")) {
\r
12378 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12379 opt->value = (def != 0);
\r
12380 opt->type = CheckBox;
\r
12381 } else if(p = strstr(opt->name, " -combo ")) {
\r
12382 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12383 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12384 opt->value = n = 0;
\r
12385 while(q = StrStr(q, " /// ")) {
\r
12386 n++; *q = 0; // count choices, and null-terminate each of them
\r
12388 if(*q == '*') { // remember default, which is marked with * prefix
\r
12392 cps->comboList[cps->comboCnt++] = q;
\r
12394 cps->comboList[cps->comboCnt++] = NULL;
\r
12395 opt->max = n + 1;
\r
12396 opt->type = ComboBox;
\r
12397 } else if(p = strstr(opt->name, " -button")) {
\r
12398 opt->type = Button;
\r
12399 } else if(p = strstr(opt->name, " -save")) {
\r
12400 opt->type = SaveButton;
\r
12401 } else return FALSE;
\r
12402 *p = 0; // terminate option name
\r
12403 // now look if the command-line options define a setting for this engine option.
\r
12404 p = strstr(cps->optionSettings, opt->name);
\r
12405 if(p == cps->optionSettings || p[-1] == ',') {
\r
12406 sprintf(buf, "option %s", p);
\r
12407 if(p = strstr(buf, ",")) *p = 0;
\r
12408 strcat(buf, "\n");
\r
12409 SendToProgram(buf, cps);
\r
12415 FeatureDone(cps, val)
\r
12416 ChessProgramState* cps;
\r
12419 DelayedEventCallback cb = GetDelayedEvent();
\r
12420 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12421 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12422 CancelDelayedEvent();
\r
12423 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12425 cps->initDone = val;
\r
12428 /* Parse feature command from engine */
\r
12430 ParseFeatures(args, cps)
\r
12432 ChessProgramState *cps;
\r
12437 char buf[MSG_SIZ];
\r
12440 while (*p == ' ') p++;
\r
12441 if (*p == NULLCHAR) return;
\r
12443 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12444 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12445 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12446 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12447 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12448 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12449 /* Engine can disable reuse, but can't enable it if user said no */
\r
12450 if (!val) cps->reuse = FALSE;
\r
12453 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12454 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12455 if (gameMode == TwoMachinesPlay) {
\r
12456 DisplayTwoMachinesTitle();
\r
12458 DisplayTitle("");
\r
12462 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12463 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12464 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12465 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12466 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12467 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12468 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12469 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12470 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12471 if (IntFeature(&p, "done", &val, cps)) {
\r
12472 FeatureDone(cps, val);
\r
12475 /* Added by Tord: */
\r
12476 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12477 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12478 /* End of additions by Tord */
\r
12480 /* [HGM] added features: */
\r
12481 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12482 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12483 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12484 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12485 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12486 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12487 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12488 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12489 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12490 cps->nrOptions--;
\r
12491 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12492 DisplayError(buf, 0);
\r
12496 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12497 /* End of additions by HGM */
\r
12499 /* unknown feature: complain and skip */
\r
12501 while (*q && *q != '=') q++;
\r
12502 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12503 SendToProgram(buf, cps);
\r
12507 if (*p == '\"') {
\r
12509 while (*p && *p != '\"') p++;
\r
12510 if (*p == '\"') p++;
\r
12512 while (*p && *p != ' ') p++;
\r
12520 PeriodicUpdatesEvent(newState)
\r
12523 if (newState == appData.periodicUpdates)
\r
12526 appData.periodicUpdates=newState;
\r
12528 /* Display type changes, so update it now */
\r
12529 DisplayAnalysis();
\r
12531 /* Get the ball rolling again... */
\r
12533 AnalysisPeriodicEvent(1);
\r
12534 StartAnalysisClock();
\r
12539 PonderNextMoveEvent(newState)
\r
12542 if (newState == appData.ponderNextMove) return;
\r
12543 if (gameMode == EditPosition) EditPositionDone();
\r
12545 SendToProgram("hard\n", &first);
\r
12546 if (gameMode == TwoMachinesPlay) {
\r
12547 SendToProgram("hard\n", &second);
\r
12550 SendToProgram("easy\n", &first);
\r
12551 thinkOutput[0] = NULLCHAR;
\r
12552 if (gameMode == TwoMachinesPlay) {
\r
12553 SendToProgram("easy\n", &second);
\r
12556 appData.ponderNextMove = newState;
\r
12560 NewSettingEvent(option, command, value)
\r
12562 int option, value;
\r
12564 char buf[MSG_SIZ];
\r
12566 if (gameMode == EditPosition) EditPositionDone();
\r
12567 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12568 SendToProgram(buf, &first);
\r
12569 if (gameMode == TwoMachinesPlay) {
\r
12570 SendToProgram(buf, &second);
\r
12575 ShowThinkingEvent()
\r
12576 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12578 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12579 int newState = appData.showThinking
\r
12580 // [HGM] thinking: other features now need thinking output as well
\r
12581 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12583 if (oldState == newState) return;
\r
12584 oldState = newState;
\r
12585 if (gameMode == EditPosition) EditPositionDone();
\r
12587 SendToProgram("post\n", &first);
\r
12588 if (gameMode == TwoMachinesPlay) {
\r
12589 SendToProgram("post\n", &second);
\r
12592 SendToProgram("nopost\n", &first);
\r
12593 thinkOutput[0] = NULLCHAR;
\r
12594 if (gameMode == TwoMachinesPlay) {
\r
12595 SendToProgram("nopost\n", &second);
\r
12598 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12602 AskQuestionEvent(title, question, replyPrefix, which)
\r
12603 char *title; char *question; char *replyPrefix; char *which;
\r
12605 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12606 if (pr == NoProc) return;
\r
12607 AskQuestion(title, question, replyPrefix, pr);
\r
12611 DisplayMove(moveNumber)
\r
12614 char message[MSG_SIZ];
\r
12615 char res[MSG_SIZ];
\r
12616 char cpThinkOutput[MSG_SIZ];
\r
12618 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12620 if (moveNumber == forwardMostMove - 1 ||
\r
12621 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12623 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12625 if (strchr(cpThinkOutput, '\n')) {
\r
12626 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12629 *cpThinkOutput = NULLCHAR;
\r
12632 /* [AS] Hide thinking from human user */
\r
12633 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12634 *cpThinkOutput = NULLCHAR;
\r
12635 if( thinkOutput[0] != NULLCHAR ) {
\r
12638 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12639 cpThinkOutput[i] = '.';
\r
12641 cpThinkOutput[i] = NULLCHAR;
\r
12642 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12646 if (moveNumber == forwardMostMove - 1 &&
\r
12647 gameInfo.resultDetails != NULL) {
\r
12648 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12649 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12651 sprintf(res, " {%s} %s",
\r
12652 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12655 res[0] = NULLCHAR;
\r
12658 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12659 DisplayMessage(res, cpThinkOutput);
\r
12661 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12662 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12663 parseList[moveNumber], res);
\r
12664 DisplayMessage(message, cpThinkOutput);
\r
12669 DisplayAnalysisText(text)
\r
12672 char buf[MSG_SIZ];
\r
12674 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12675 || appData.icsEngineAnalyze) {
\r
12676 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12677 AnalysisPopUp(buf, text);
\r
12682 only_one_move(str)
\r
12685 while (*str && isspace(*str)) ++str;
\r
12686 while (*str && !isspace(*str)) ++str;
\r
12687 if (!*str) return 1;
\r
12688 while (*str && isspace(*str)) ++str;
\r
12689 if (!*str) return 1;
\r
12694 DisplayAnalysis()
\r
12696 char buf[MSG_SIZ];
\r
12697 char lst[MSG_SIZ / 2];
\r
12699 static char *xtra[] = { "", " (--)", " (++)" };
\r
12702 if (programStats.time == 0) {
\r
12703 programStats.time = 1;
\r
12706 if (programStats.got_only_move) {
\r
12707 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12709 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12711 nps = (u64ToDouble(programStats.nodes) /
\r
12712 ((double)programStats.time /100.0));
\r
12714 cs = programStats.time % 100;
\r
12715 s = programStats.time / 100;
\r
12716 h = (s / (60*60));
\r
12721 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12722 if (programStats.move_name[0] != NULLCHAR) {
\r
12723 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12724 programStats.depth,
\r
12725 programStats.nr_moves-programStats.moves_left,
\r
12726 programStats.nr_moves, programStats.move_name,
\r
12727 ((float)programStats.score)/100.0, lst,
\r
12728 only_one_move(lst)?
\r
12729 xtra[programStats.got_fail] : "",
\r
12730 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12732 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12733 programStats.depth,
\r
12734 programStats.nr_moves-programStats.moves_left,
\r
12735 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12737 only_one_move(lst)?
\r
12738 xtra[programStats.got_fail] : "",
\r
12739 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12742 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12743 programStats.depth,
\r
12744 ((float)programStats.score)/100.0,
\r
12746 only_one_move(lst)?
\r
12747 xtra[programStats.got_fail] : "",
\r
12748 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12751 DisplayAnalysisText(buf);
\r
12755 DisplayComment(moveNumber, text)
\r
12759 char title[MSG_SIZ];
\r
12760 char buf[8000]; // comment can be long!
\r
12761 int score, depth;
\r
12763 if( appData.autoDisplayComment ) {
\r
12764 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12765 strcpy(title, "Comment");
\r
12767 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12768 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12769 parseList[moveNumber]);
\r
12771 } else title[0] = 0;
\r
12773 // [HGM] PV info: display PV info together with (or as) comment
\r
12774 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12775 if(text == NULL) text = "";
\r
12776 score = pvInfoList[moveNumber].score;
\r
12777 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12778 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12779 CommentPopUp(title, buf);
\r
12781 if (text != NULL)
\r
12782 CommentPopUp(title, text);
\r
12785 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12786 * might be busy thinking or pondering. It can be omitted if your
\r
12787 * gnuchess is configured to stop thinking immediately on any user
\r
12788 * input. However, that gnuchess feature depends on the FIONREAD
\r
12789 * ioctl, which does not work properly on some flavors of Unix.
\r
12793 ChessProgramState *cps;
\r
12796 if (!cps->useSigint) return;
\r
12797 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12798 switch (gameMode) {
\r
12799 case MachinePlaysWhite:
\r
12800 case MachinePlaysBlack:
\r
12801 case TwoMachinesPlay:
\r
12802 case IcsPlayingWhite:
\r
12803 case IcsPlayingBlack:
\r
12804 case AnalyzeMode:
\r
12805 case AnalyzeFile:
\r
12806 /* Skip if we know it isn't thinking */
\r
12807 if (!cps->maybeThinking) return;
\r
12808 if (appData.debugMode)
\r
12809 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12810 InterruptChildProcess(cps->pr);
\r
12811 cps->maybeThinking = FALSE;
\r
12816 #endif /*ATTENTION*/
\r
12822 if (whiteTimeRemaining <= 0) {
\r
12823 if (!whiteFlag) {
\r
12824 whiteFlag = TRUE;
\r
12825 if (appData.icsActive) {
\r
12826 if (appData.autoCallFlag &&
\r
12827 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12828 SendToICS(ics_prefix);
\r
12829 SendToICS("flag\n");
\r
12833 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12835 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12836 if (appData.autoCallFlag) {
\r
12837 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12844 if (blackTimeRemaining <= 0) {
\r
12845 if (!blackFlag) {
\r
12846 blackFlag = TRUE;
\r
12847 if (appData.icsActive) {
\r
12848 if (appData.autoCallFlag &&
\r
12849 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12850 SendToICS(ics_prefix);
\r
12851 SendToICS("flag\n");
\r
12855 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12857 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12858 if (appData.autoCallFlag) {
\r
12859 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12870 CheckTimeControl()
\r
12872 if (!appData.clockMode || appData.icsActive ||
\r
12873 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12876 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12878 if ( !WhiteOnMove(forwardMostMove) )
\r
12879 /* White made time control */
\r
12880 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12881 /* [HGM] time odds: correct new time quota for time odds! */
\r
12882 / WhitePlayer()->timeOdds;
\r
12884 /* Black made time control */
\r
12885 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12886 / WhitePlayer()->other->timeOdds;
\r
12890 DisplayBothClocks()
\r
12892 int wom = gameMode == EditPosition ?
\r
12893 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12894 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12895 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12899 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12900 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12901 to use other calls if you don't. Clocks will be less accurate if
\r
12902 you have neither ftime nor gettimeofday.
\r
12905 /* VS 2008 requires the #include outside of the function */
\r
12906 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12907 #include <sys/timeb.h>
\r
12910 /* Get the current time as a TimeMark */
\r
12915 #if HAVE_GETTIMEOFDAY
\r
12917 struct timeval timeVal;
\r
12918 struct timezone timeZone;
\r
12920 gettimeofday(&timeVal, &timeZone);
\r
12921 tm->sec = (long) timeVal.tv_sec;
\r
12922 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12924 #else /*!HAVE_GETTIMEOFDAY*/
\r
12927 // include <sys/timeb.h> / moved to just above start of function
\r
12928 struct timeb timeB;
\r
12931 tm->sec = (long) timeB.time;
\r
12932 tm->ms = (int) timeB.millitm;
\r
12934 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12935 tm->sec = (long) time(NULL);
\r
12941 /* Return the difference in milliseconds between two
\r
12942 time marks. We assume the difference will fit in a long!
\r
12945 SubtractTimeMarks(tm2, tm1)
\r
12946 TimeMark *tm2, *tm1;
\r
12948 return 1000L*(tm2->sec - tm1->sec) +
\r
12949 (long) (tm2->ms - tm1->ms);
\r
12954 * Code to manage the game clocks.
\r
12956 * In tournament play, black starts the clock and then white makes a move.
\r
12957 * We give the human user a slight advantage if he is playing white---the
\r
12958 * clocks don't run until he makes his first move, so it takes zero time.
\r
12959 * Also, we don't account for network lag, so we could get out of sync
\r
12960 * with GNU Chess's clock -- but then, referees are always right.
\r
12963 static TimeMark tickStartTM;
\r
12964 static long intendedTickLength;
\r
12967 NextTickLength(timeRemaining)
\r
12968 long timeRemaining;
\r
12970 long nominalTickLength, nextTickLength;
\r
12972 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12973 nominalTickLength = 100L;
\r
12975 nominalTickLength = 1000L;
\r
12976 nextTickLength = timeRemaining % nominalTickLength;
\r
12977 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12979 return nextTickLength;
\r
12982 /* Adjust clock one minute up or down */
\r
12984 AdjustClock(Boolean which, int dir)
\r
12986 if(which) blackTimeRemaining += 60000*dir;
\r
12987 else whiteTimeRemaining += 60000*dir;
\r
12988 DisplayBothClocks();
\r
12991 /* Stop clocks and reset to a fresh time control */
\r
12995 (void) StopClockTimer();
\r
12996 if (appData.icsActive) {
\r
12997 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12998 } else { /* [HGM] correct new time quote for time odds */
\r
12999 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
13000 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
13002 if (whiteFlag || blackFlag) {
\r
13003 DisplayTitle("");
\r
13004 whiteFlag = blackFlag = FALSE;
\r
13006 DisplayBothClocks();
\r
13009 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13011 /* Decrement running clock by amount of time that has passed */
\r
13013 DecrementClocks()
\r
13015 long timeRemaining;
\r
13016 long lastTickLength, fudge;
\r
13019 if (!appData.clockMode) return;
\r
13020 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13022 GetTimeMark(&now);
\r
13024 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13026 /* Fudge if we woke up a little too soon */
\r
13027 fudge = intendedTickLength - lastTickLength;
\r
13028 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13030 if (WhiteOnMove(forwardMostMove)) {
\r
13031 if(whiteNPS >= 0) lastTickLength = 0;
\r
13032 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13033 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13034 WhiteOnMove(currentMove));
\r
13036 if(blackNPS >= 0) lastTickLength = 0;
\r
13037 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13038 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13039 !WhiteOnMove(currentMove));
\r
13042 if (CheckFlags()) return;
\r
13044 tickStartTM = now;
\r
13045 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13046 StartClockTimer(intendedTickLength);
\r
13048 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13049 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13050 * with increment) the time remaining has increased to a level above the
\r
13051 * threshold, reset the alarm so it can sound again.
\r
13054 if (appData.icsActive && appData.icsAlarm) {
\r
13056 /* make sure we are dealing with the user's clock */
\r
13057 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13058 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13061 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13062 alarmSounded = FALSE;
\r
13063 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13064 PlayAlarmSound();
\r
13065 alarmSounded = TRUE;
\r
13071 /* A player has just moved, so stop the previously running
\r
13072 clock and (if in clock mode) start the other one.
\r
13073 We redisplay both clocks in case we're in ICS mode, because
\r
13074 ICS gives us an update to both clocks after every move.
\r
13075 Note that this routine is called *after* forwardMostMove
\r
13076 is updated, so the last fractional tick must be subtracted
\r
13077 from the color that is *not* on move now.
\r
13082 long lastTickLength;
\r
13084 int flagged = FALSE;
\r
13086 GetTimeMark(&now);
\r
13088 if (StopClockTimer() && appData.clockMode) {
\r
13089 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13090 if (WhiteOnMove(forwardMostMove)) {
\r
13091 if(blackNPS >= 0) lastTickLength = 0;
\r
13092 blackTimeRemaining -= lastTickLength;
\r
13093 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13094 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13095 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13096 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13098 if(whiteNPS >= 0) lastTickLength = 0;
\r
13099 whiteTimeRemaining -= lastTickLength;
\r
13100 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13101 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13102 pvInfoList[forwardMostMove-1].time =
\r
13103 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13105 flagged = CheckFlags();
\r
13107 CheckTimeControl();
\r
13109 if (flagged || !appData.clockMode) return;
\r
13111 switch (gameMode) {
\r
13112 case MachinePlaysBlack:
\r
13113 case MachinePlaysWhite:
\r
13114 case BeginningOfGame:
\r
13115 if (pausing) return;
\r
13119 case PlayFromGameFile:
\r
13120 case IcsExamining:
\r
13127 tickStartTM = now;
\r
13128 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13129 whiteTimeRemaining : blackTimeRemaining);
\r
13130 StartClockTimer(intendedTickLength);
\r
13134 /* Stop both clocks */
\r
13138 long lastTickLength;
\r
13141 if (!StopClockTimer()) return;
\r
13142 if (!appData.clockMode) return;
\r
13144 GetTimeMark(&now);
\r
13146 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13147 if (WhiteOnMove(forwardMostMove)) {
\r
13148 if(whiteNPS >= 0) lastTickLength = 0;
\r
13149 whiteTimeRemaining -= lastTickLength;
\r
13150 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13152 if(blackNPS >= 0) lastTickLength = 0;
\r
13153 blackTimeRemaining -= lastTickLength;
\r
13154 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13159 /* Start clock of player on move. Time may have been reset, so
\r
13160 if clock is already running, stop and restart it. */
\r
13164 (void) StopClockTimer(); /* in case it was running already */
\r
13165 DisplayBothClocks();
\r
13166 if (CheckFlags()) return;
\r
13168 if (!appData.clockMode) return;
\r
13169 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13171 GetTimeMark(&tickStartTM);
\r
13172 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13173 whiteTimeRemaining : blackTimeRemaining);
\r
13175 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13176 whiteNPS = blackNPS = -1;
\r
13177 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13178 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13179 whiteNPS = first.nps;
\r
13180 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13181 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13182 blackNPS = first.nps;
\r
13183 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13184 whiteNPS = second.nps;
\r
13185 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13186 blackNPS = second.nps;
\r
13187 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13189 StartClockTimer(intendedTickLength);
\r
13196 long second, minute, hour, day;
\r
13198 static char buf[32];
\r
13200 if (ms > 0 && ms <= 9900) {
\r
13201 /* convert milliseconds to tenths, rounding up */
\r
13202 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13204 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13208 /* convert milliseconds to seconds, rounding up */
\r
13209 /* use floating point to avoid strangeness of integer division
\r
13210 with negative dividends on many machines */
\r
13211 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13213 if (second < 0) {
\r
13215 second = -second;
\r
13218 day = second / (60 * 60 * 24);
\r
13219 second = second % (60 * 60 * 24);
\r
13220 hour = second / (60 * 60);
\r
13221 second = second % (60 * 60);
\r
13222 minute = second / 60;
\r
13223 second = second % 60;
\r
13226 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13227 sign, day, hour, minute, second);
\r
13228 else if (hour > 0)
\r
13229 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13231 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13238 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13241 StrStr(string, match)
\r
13242 char *string, *match;
\r
13246 length = strlen(match);
\r
13248 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13249 if (!strncmp(match, string, length))
\r
13256 StrCaseStr(string, match)
\r
13257 char *string, *match;
\r
13259 int i, j, length;
\r
13261 length = strlen(match);
\r
13263 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13264 for (j = 0; j < length; j++) {
\r
13265 if (ToLower(match[j]) != ToLower(string[j]))
\r
13268 if (j == length) return string;
\r
13274 #ifndef _amigados
\r
13276 StrCaseCmp(s1, s2)
\r
13282 c1 = ToLower(*s1++);
\r
13283 c2 = ToLower(*s2++);
\r
13284 if (c1 > c2) return 1;
\r
13285 if (c1 < c2) return -1;
\r
13286 if (c1 == NULLCHAR) return 0;
\r
13295 return isupper(c) ? tolower(c) : c;
\r
13303 return islower(c) ? toupper(c) : c;
\r
13305 #endif /* !_amigados */
\r
13313 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13320 StrSavePtr(s, savePtr)
\r
13321 char *s, **savePtr;
\r
13326 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13327 strcpy(*savePtr, s);
\r
13329 return(*savePtr);
\r
13337 char buf[MSG_SIZ];
\r
13339 clock = time((time_t *)NULL);
\r
13340 tm = localtime(&clock);
\r
13341 sprintf(buf, "%04d.%02d.%02d",
\r
13342 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13343 return StrSave(buf);
\r
13348 PositionToFEN(move, useFEN960)
\r
13352 int i, j, fromX, fromY, toX, toY;
\r
13357 ChessSquare piece;
\r
13359 whiteToPlay = (gameMode == EditPosition) ?
\r
13360 !blackPlaysFirst : (move % 2 == 0);
\r
13363 /* Piece placement data */
\r
13364 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13366 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13367 if (boards[move][i][j] == EmptySquare) {
\r
13369 } else { ChessSquare piece = boards[move][i][j];
\r
13370 if (emptycount > 0) {
\r
13371 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13372 *p++ = '0' + emptycount;
\r
13373 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13376 if(PieceToChar(piece) == '+') {
\r
13377 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13379 piece = (ChessSquare)(DEMOTED piece);
\r
13381 *p++ = PieceToChar(piece);
\r
13382 if(p[-1] == '~') {
\r
13383 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13384 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13389 if (emptycount > 0) {
\r
13390 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13391 *p++ = '0' + emptycount;
\r
13392 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13399 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13400 if( gameInfo.holdingsWidth ) {
\r
13401 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13403 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13404 piece = boards[move][i][BOARD_WIDTH-1];
\r
13405 if( piece != EmptySquare )
\r
13406 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13407 *p++ = PieceToChar(piece);
\r
13409 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13410 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13411 if( piece != EmptySquare )
\r
13412 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13413 *p++ = PieceToChar(piece);
\r
13416 if( q == p ) *p++ = '-';
\r
13421 /* Active color */
\r
13422 *p++ = whiteToPlay ? 'w' : 'b';
\r
13425 if(nrCastlingRights) {
\r
13427 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13428 /* [HGM] write directly from rights */
\r
13429 if(castlingRights[move][2] >= 0 &&
\r
13430 castlingRights[move][0] >= 0 )
\r
13431 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13432 if(castlingRights[move][2] >= 0 &&
\r
13433 castlingRights[move][1] >= 0 )
\r
13434 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13435 if(castlingRights[move][5] >= 0 &&
\r
13436 castlingRights[move][3] >= 0 )
\r
13437 *p++ = castlingRights[move][3] + AAA;
\r
13438 if(castlingRights[move][5] >= 0 &&
\r
13439 castlingRights[move][4] >= 0 )
\r
13440 *p++ = castlingRights[move][4] + AAA;
\r
13443 /* [HGM] write true castling rights */
\r
13444 if( nrCastlingRights == 6 ) {
\r
13445 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13446 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13447 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13448 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13449 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13450 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13451 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13452 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13455 if (q == p) *p++ = '-'; /* No castling rights */
\r
13459 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13460 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13461 /* En passant target square */
\r
13462 if (move > backwardMostMove) {
\r
13463 fromX = moveList[move - 1][0] - AAA;
\r
13464 fromY = moveList[move - 1][1] - ONE;
\r
13465 toX = moveList[move - 1][2] - AAA;
\r
13466 toY = moveList[move - 1][3] - ONE;
\r
13467 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13468 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13469 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13471 /* 2-square pawn move just happened */
\r
13472 *p++ = toX + AAA;
\r
13473 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13483 /* [HGM] find reversible plies */
\r
13484 { int i = 0, j=move;
\r
13486 if (appData.debugMode) { int k;
\r
13487 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13488 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13489 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13493 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13494 if( j == backwardMostMove ) i += initialRulePlies;
\r
13495 sprintf(p, "%d ", i);
\r
13496 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13498 /* Fullmove number */
\r
13499 sprintf(p, "%d", (move / 2) + 1);
\r
13501 return StrSave(buf);
\r
13505 ParseFEN(board, blackPlaysFirst, fen)
\r
13507 int *blackPlaysFirst;
\r
13513 ChessSquare piece;
\r
13517 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13518 if(gameInfo.holdingsWidth) {
\r
13519 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13520 board[i][0] = EmptySquare; /* black holdings */
\r
13521 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13522 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13523 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13527 /* Piece placement data */
\r
13528 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13531 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13532 if (*p == '/') p++;
\r
13533 emptycount = gameInfo.boardWidth - j;
\r
13534 while (emptycount--)
\r
13535 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13537 #if(BOARD_SIZE >= 10)
\r
13538 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13539 p++; emptycount=10;
\r
13540 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13541 while (emptycount--)
\r
13542 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13544 } else if (isdigit(*p)) {
\r
13545 emptycount = *p++ - '0';
\r
13546 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13547 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13548 while (emptycount--)
\r
13549 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13550 } else if (*p == '+' || isalpha(*p)) {
\r
13551 if (j >= gameInfo.boardWidth) return FALSE;
\r
13553 piece = CharToPiece(*++p);
\r
13554 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13555 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13556 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13557 } else piece = CharToPiece(*p++);
\r
13559 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13560 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13561 piece = (ChessSquare) (PROMOTED piece);
\r
13562 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13565 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13571 while (*p == '/' || *p == ' ') p++;
\r
13573 /* [HGM] look for Crazyhouse holdings here */
\r
13574 while(*p==' ') p++;
\r
13575 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13576 if(*p == '[') p++;
\r
13577 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13578 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13579 /* if we would allow FEN reading to set board size, we would */
\r
13580 /* have to add holdings and shift the board read so far here */
\r
13581 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13583 if((int) piece >= (int) BlackPawn ) {
\r
13584 i = (int)piece - (int)BlackPawn;
\r
13585 i = PieceToNumber((ChessSquare)i);
\r
13586 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13587 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13588 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13590 i = (int)piece - (int)WhitePawn;
\r
13591 i = PieceToNumber((ChessSquare)i);
\r
13592 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13593 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13594 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13598 if(*p == ']') *p++;
\r
13601 while(*p == ' ') p++;
\r
13603 /* Active color */
\r
13606 *blackPlaysFirst = FALSE;
\r
13609 *blackPlaysFirst = TRUE;
\r
13615 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13616 /* return the extra info in global variiables */
\r
13618 /* set defaults in case FEN is incomplete */
\r
13619 FENepStatus = EP_UNKNOWN;
\r
13620 for(i=0; i<nrCastlingRights; i++ ) {
\r
13621 FENcastlingRights[i] =
\r
13622 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13623 } /* assume possible unless obviously impossible */
\r
13624 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13625 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13626 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13627 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13628 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13629 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13630 FENrulePlies = 0;
\r
13632 while(*p==' ') p++;
\r
13633 if(nrCastlingRights) {
\r
13634 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13635 /* castling indicator present, so default becomes no castlings */
\r
13636 for(i=0; i<nrCastlingRights; i++ ) {
\r
13637 FENcastlingRights[i] = -1;
\r
13640 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13641 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13642 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13643 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13644 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13646 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13647 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13648 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13652 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13653 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13654 FENcastlingRights[2] = whiteKingFile;
\r
13657 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13658 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13659 FENcastlingRights[2] = whiteKingFile;
\r
13662 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13663 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13664 FENcastlingRights[5] = blackKingFile;
\r
13667 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13668 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13669 FENcastlingRights[5] = blackKingFile;
\r
13672 default: /* FRC castlings */
\r
13673 if(c >= 'a') { /* black rights */
\r
13674 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13675 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13676 if(i == BOARD_RGHT) break;
\r
13677 FENcastlingRights[5] = i;
\r
13679 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13680 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13682 FENcastlingRights[3] = c;
\r
13684 FENcastlingRights[4] = c;
\r
13685 } else { /* white rights */
\r
13686 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13687 if(board[0][i] == WhiteKing) break;
\r
13688 if(i == BOARD_RGHT) break;
\r
13689 FENcastlingRights[2] = i;
\r
13690 c -= AAA - 'a' + 'A';
\r
13691 if(board[0][c] >= WhiteKing) break;
\r
13693 FENcastlingRights[0] = c;
\r
13695 FENcastlingRights[1] = c;
\r
13699 if (appData.debugMode) {
\r
13700 fprintf(debugFP, "FEN castling rights:");
\r
13701 for(i=0; i<nrCastlingRights; i++)
\r
13702 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13703 fprintf(debugFP, "\n");
\r
13706 while(*p==' ') p++;
\r
13709 /* read e.p. field in games that know e.p. capture */
\r
13710 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13711 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13713 p++; FENepStatus = EP_NONE;
\r
13715 char c = *p++ - AAA;
\r
13717 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13718 if(*p >= '0' && *p <='9') *p++;
\r
13724 if(sscanf(p, "%d", &i) == 1) {
\r
13725 FENrulePlies = i; /* 50-move ply counter */
\r
13726 /* (The move number is still ignored) */
\r
13733 EditPositionPasteFEN(char *fen)
\r
13735 if (fen != NULL) {
\r
13736 Board initial_position;
\r
13738 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13739 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13742 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13743 EditPositionEvent();
\r
13744 blackPlaysFirst = savedBlackPlaysFirst;
\r
13745 CopyBoard(boards[0], initial_position);
\r
13746 /* [HGM] copy FEN attributes as well */
\r
13748 initialRulePlies = FENrulePlies;
\r
13749 epStatus[0] = FENepStatus;
\r
13750 for( i=0; i<nrCastlingRights; i++ )
\r
13751 castlingRights[0][i] = FENcastlingRights[i];
\r
13753 EditPositionDone();
\r
13754 DisplayBothClocks();
\r
13755 DrawPosition(FALSE, boards[currentMove]);
\r