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
75 # include <stdlib.h>
\r
76 # include <string.h>
\r
77 #else /* not STDC_HEADERS */
\r
79 # include <string.h>
\r
80 # else /* not HAVE_STRING_H */
\r
81 # include <strings.h>
\r
82 # endif /* not HAVE_STRING_H */
\r
83 #endif /* not STDC_HEADERS */
\r
85 #if HAVE_SYS_FCNTL_H
\r
86 # include <sys/fcntl.h>
\r
87 #else /* not HAVE_SYS_FCNTL_H */
\r
90 # endif /* HAVE_FCNTL_H */
\r
91 #endif /* not HAVE_SYS_FCNTL_H */
\r
93 #if TIME_WITH_SYS_TIME
\r
94 # include <sys/time.h>
\r
97 # if HAVE_SYS_TIME_H
\r
98 # include <sys/time.h>
\r
104 #if defined(_amigados) && !defined(__GNUC__)
\r
106 int tz_minuteswest;
\r
109 extern int gettimeofday(struct timeval *, struct timezone *);
\r
113 # include <unistd.h>
\r
116 #include "common.h"
\r
117 #include "frontend.h"
\r
118 #include "backend.h"
\r
119 #include "parser.h"
\r
122 # include "zippy.h"
\r
124 #include "backendz.h"
\r
125 #include "gettext.h"
\r
128 # define _(s) gettext (s)
\r
129 # define N_(s) gettext_noop (s)
\r
136 /* A point in time */
\r
138 long sec; /* Assuming this is >= 32 bits */
\r
139 int ms; /* Assuming this is >= 16 bits */
\r
142 int establish P((void));
\r
143 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
144 char *buf, int count, int error));
\r
145 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
146 char *buf, int count, int error));
\r
147 void SendToICS P((char *s));
\r
148 void SendToICSDelayed P((char *s, long msdelay));
\r
149 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
150 int toX, int toY));
\r
151 void InitPosition P((int redraw));
\r
152 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
153 int AutoPlayOneMove P((void));
\r
154 int LoadGameOneMove P((ChessMove readAhead));
\r
155 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
156 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
157 int SavePositionToFile P((char *filename));
\r
158 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
160 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
161 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
162 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
163 /*char*/int promoChar));
\r
164 void BackwardInner P((int target));
\r
165 void ForwardInner P((int target));
\r
166 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
167 void EditPositionDone P((void));
\r
168 void PrintOpponents P((FILE *fp));
\r
169 void PrintPosition P((FILE *fp, int move));
\r
170 void StartChessProgram P((ChessProgramState *cps));
\r
171 void SendToProgram P((char *message, ChessProgramState *cps));
\r
172 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
173 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
174 char *buf, int count, int error));
\r
175 void SendTimeControl P((ChessProgramState *cps,
\r
176 int mps, long tc, int inc, int sd, int st));
\r
177 char *TimeControlTagValue P((void));
\r
178 void Attention P((ChessProgramState *cps));
\r
179 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
180 void ResurrectChessProgram P((void));
\r
181 void DisplayComment P((int moveNumber, char *text));
\r
182 void DisplayMove P((int moveNumber));
\r
183 void DisplayAnalysis P((void));
\r
185 void ParseGameHistory P((char *game));
\r
186 void ParseBoard12 P((char *string));
\r
187 void StartClocks P((void));
\r
188 void SwitchClocks P((void));
\r
189 void StopClocks P((void));
\r
190 void ResetClocks P((void));
\r
191 char *PGNDate P((void));
\r
192 void SetGameInfo P((void));
\r
193 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
194 int RegisterMove P((void));
\r
195 void MakeRegisteredMove P((void));
\r
196 void TruncateGame P((void));
\r
197 int looking_at P((char *, int *, char *));
\r
198 void CopyPlayerNameIntoFileName P((char **, char *));
\r
199 char *SavePart P((char *));
\r
200 int SaveGameOldStyle P((FILE *));
\r
201 int SaveGamePGN P((FILE *));
\r
202 void GetTimeMark P((TimeMark *));
\r
203 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
204 int CheckFlags P((void));
\r
205 long NextTickLength P((long));
\r
206 void CheckTimeControl P((void));
\r
207 void show_bytes P((FILE *, char *, int));
\r
208 int string_to_rating P((char *str));
\r
209 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
210 void InitBackEnd3 P((void));
\r
211 void FeatureDone P((ChessProgramState* cps, int val));
\r
212 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
215 extern void ConsoleCreate();
\r
218 ChessProgramState *WhitePlayer();
\r
219 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
220 int VerifyDisplayMode P(());
\r
222 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
223 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
224 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
225 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
226 extern char installDir[MSG_SIZ];
\r
228 extern int tinyLayout, smallLayout;
\r
229 ChessProgramStats programStats;
\r
230 static int exiting = 0; /* [HGM] moved to top */
\r
231 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;
\r
232 extern int startedFromPositionFile;
\r
233 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
234 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
235 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
236 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
237 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
238 int opponentKibitzes;
\r
240 /* States for ics_getting_history */
\r
242 #define H_REQUESTED 1
\r
243 #define H_GOT_REQ_HEADER 2
\r
244 #define H_GOT_UNREQ_HEADER 3
\r
245 #define H_GETTING_MOVES 4
\r
246 #define H_GOT_UNWANTED_HEADER 5
\r
248 /* whosays values for GameEnds */
\r
250 #define GE_ENGINE 1
\r
251 #define GE_PLAYER 2
\r
253 #define GE_XBOARD 4
\r
254 #define GE_ENGINE1 5
\r
255 #define GE_ENGINE2 6
\r
257 /* Maximum number of games in a cmail message */
\r
258 #define CMAIL_MAX_GAMES 20
\r
260 /* Different types of move when calling RegisterMove */
\r
261 #define CMAIL_MOVE 0
\r
262 #define CMAIL_RESIGN 1
\r
263 #define CMAIL_DRAW 2
\r
264 #define CMAIL_ACCEPT 3
\r
266 /* Different types of result to remember for each game */
\r
267 #define CMAIL_NOT_RESULT 0
\r
268 #define CMAIL_OLD_RESULT 1
\r
269 #define CMAIL_NEW_RESULT 2
\r
271 /* Telnet protocol constants */
\r
272 #define TN_WILL 0373
\r
273 #define TN_WONT 0374
\r
275 #define TN_DONT 0376
\r
276 #define TN_IAC 0377
\r
277 #define TN_ECHO 0001
\r
278 #define TN_SGA 0003
\r
282 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
284 assert( dst != NULL );
\r
285 assert( src != NULL );
\r
286 assert( count > 0 );
\r
288 strncpy( dst, src, count );
\r
289 dst[ count-1 ] = '\0';
\r
293 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
297 assert( dst != NULL );
\r
298 assert( src != NULL );
\r
299 assert( count > 0 );
\r
301 dst_len = strlen(dst);
\r
303 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
305 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
310 /* Some compiler can't cast u64 to double
\r
311 * This function do the job for us:
\r
313 * We use the highest bit for cast, this only
\r
314 * works if the highest bit is not
\r
315 * in use (This should not happen)
\r
317 * We used this for all compiler
\r
320 u64ToDouble(u64 value)
\r
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
324 r = (double)(s64)tmp;
\r
325 if (value & u64Const(0x8000000000000000))
\r
326 r += 9.2233720368547758080e18; /* 2^63 */
\r
330 /* Fake up flags for now, as we aren't keeping track of castling
\r
331 availability yet. [HGM] Change of logic: the flag now only
\r
332 indicates the type of castlings allowed by the rule of the game.
\r
333 The actual rights themselves are maintained in the array
\r
334 castlingRights, as part of the game history, and are not probed
\r
340 int flags = F_ALL_CASTLE_OK;
\r
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
342 switch (gameInfo.variant) {
\r
343 case VariantSuicide:
\r
344 flags &= ~F_ALL_CASTLE_OK;
\r
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
346 flags |= F_IGNORE_CHECK;
\r
348 case VariantAtomic:
\r
349 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
351 case VariantKriegspiel:
\r
352 flags |= F_KRIEGSPIEL_CAPTURE;
\r
354 case VariantCapaRandom:
\r
355 case VariantFischeRandom:
\r
356 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
357 case VariantNoCastle:
\r
358 case VariantShatranj:
\r
359 case VariantCourier:
\r
360 flags &= ~F_ALL_CASTLE_OK;
\r
368 FILE *gameFileFP, *debugFP;
\r
371 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
372 into a fixed-size buffer. Because of this, we must be prepared to
\r
373 receive strings as long as the size of the input buffer, which is currently
\r
374 set to 4K for Windows and 8K for the rest.
\r
375 So, we must either allocate sufficiently large buffers here, or
\r
376 reduce the size of the input buffer in the input reading part.
\r
379 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
380 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
381 char thinkOutput1[MSG_SIZ*10];
\r
383 ChessProgramState first, second;
\r
385 /* premove variables */
\r
386 int premoveToX = 0;
\r
387 int premoveToY = 0;
\r
388 int premoveFromX = 0;
\r
389 int premoveFromY = 0;
\r
390 int premovePromoChar = 0;
\r
391 int gotPremove = 0;
\r
392 Boolean alarmSounded;
\r
393 /* end premove variables */
\r
395 char *ics_prefix = "$";
\r
396 int ics_type = ICS_GENERIC;
\r
398 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
399 int pauseExamForwardMostMove = 0;
\r
400 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
401 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
402 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
403 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
404 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
405 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
406 int whiteFlag = FALSE, blackFlag = FALSE;
\r
407 int userOfferedDraw = FALSE;
\r
408 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
409 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
410 int cmailMoveType[CMAIL_MAX_GAMES];
\r
411 long ics_clock_paused = 0;
\r
412 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
413 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
414 GameMode gameMode = BeginningOfGame;
\r
415 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
416 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
417 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
418 int hiddenThinkOutputState = 0; /* [AS] */
\r
419 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
420 int adjudicateLossPlies = 6;
\r
421 char white_holding[64], black_holding[64];
\r
422 TimeMark lastNodeCountTime;
\r
423 long lastNodeCount=0;
\r
424 int have_sent_ICS_logon = 0;
\r
425 int movesPerSession;
\r
426 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
427 long timeControl_2; /* [AS] Allow separate time controls */
\r
428 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
429 long timeRemaining[2][MAX_MOVES];
\r
431 TimeMark programStartTime;
\r
432 char ics_handle[MSG_SIZ];
\r
433 int have_set_title = 0;
\r
435 /* animateTraining preserves the state of appData.animate
\r
436 * when Training mode is activated. This allows the
\r
437 * response to be animated when appData.animate == TRUE and
\r
438 * appData.animateDragging == TRUE.
\r
440 Boolean animateTraining;
\r
446 Board boards[MAX_MOVES];
\r
447 /* [HGM] Following 7 needed for accurate legality tests: */
\r
448 char epStatus[MAX_MOVES];
\r
449 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
450 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
451 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
452 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
453 int initialRulePlies, FENrulePlies;
\r
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
457 int shuffleOpenings;
\r
459 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
460 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
461 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
462 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
463 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
466 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
467 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
468 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
469 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
470 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
473 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
475 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
476 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
477 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
480 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
481 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
482 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
483 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
484 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
487 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
488 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
489 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
490 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
491 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
495 #if (BOARD_SIZE>=10)
\r
496 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
497 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
498 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
499 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
500 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
503 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
504 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
505 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
506 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
507 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
510 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
511 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
512 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
513 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
514 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
517 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
518 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
519 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
520 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
521 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
524 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
525 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
526 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
527 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
528 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
532 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
534 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
535 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
536 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
539 #define GothicArray CapablancaArray
\r
543 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
545 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
546 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
547 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
550 #define FalconArray CapablancaArray
\r
553 #else // !(BOARD_SIZE>=10)
\r
554 #define XiangqiPosition FIDEArray
\r
555 #define CapablancaArray FIDEArray
\r
556 #define GothicArray FIDEArray
\r
557 #define GreatArray FIDEArray
\r
558 #endif // !(BOARD_SIZE>=10)
\r
560 #if (BOARD_SIZE>=12)
\r
561 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
562 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
563 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
564 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
565 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
567 #else // !(BOARD_SIZE>=12)
\r
568 #define CourierArray CapablancaArray
\r
569 #endif // !(BOARD_SIZE>=12)
\r
572 Board initialPosition;
\r
575 /* Convert str to a rating. Checks for special cases of "----",
\r
577 "++++", etc. Also strips ()'s */
\r
579 string_to_rating(str)
\r
582 while(*str && !isdigit(*str)) ++str;
\r
584 return 0; /* One of the special "no rating" cases */
\r
590 ClearProgramStats()
\r
592 /* Init programStats */
\r
593 programStats.movelist[0] = 0;
\r
594 programStats.depth = 0;
\r
595 programStats.nr_moves = 0;
\r
596 programStats.moves_left = 0;
\r
597 programStats.nodes = 0;
\r
598 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
599 programStats.score = 0;
\r
600 programStats.got_only_move = 0;
\r
601 programStats.got_fail = 0;
\r
602 programStats.line_is_book = 0;
\r
608 int matched, min, sec;
\r
610 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
612 GetTimeMark(&programStartTime);
\r
614 ClearProgramStats();
\r
615 programStats.ok_to_send = 1;
\r
616 programStats.seen_stat = 0;
\r
619 * Initialize game list
\r
621 ListNew(&gameList);
\r
625 * Internet chess server status
\r
627 if (appData.icsActive) {
\r
628 appData.matchMode = FALSE;
\r
629 appData.matchGames = 0;
\r
631 appData.noChessProgram = !appData.zippyPlay;
\r
633 appData.zippyPlay = FALSE;
\r
634 appData.zippyTalk = FALSE;
\r
635 appData.noChessProgram = TRUE;
\r
637 if (*appData.icsHelper != NULLCHAR) {
\r
638 appData.useTelnet = TRUE;
\r
639 appData.telnetProgram = appData.icsHelper;
\r
642 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
645 /* [AS] Initialize pv info list [HGM] and game state */
\r
649 for( i=0; i<MAX_MOVES; i++ ) {
\r
650 pvInfoList[i].depth = -1;
\r
651 epStatus[i]=EP_NONE;
\r
652 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
657 * Parse timeControl resource
\r
659 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
660 appData.movesPerSession)) {
\r
662 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
663 DisplayFatalError(buf, 0, 2);
\r
667 * Parse searchTime resource
\r
669 if (*appData.searchTime != NULLCHAR) {
\r
670 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
671 if (matched == 1) {
\r
672 searchTime = min * 60;
\r
673 } else if (matched == 2) {
\r
674 searchTime = min * 60 + sec;
\r
677 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
678 DisplayFatalError(buf, 0, 2);
\r
682 /* [AS] Adjudication threshold */
\r
683 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
685 first.which = "first";
\r
686 second.which = "second";
\r
687 first.maybeThinking = second.maybeThinking = FALSE;
\r
688 first.pr = second.pr = NoProc;
\r
689 first.isr = second.isr = NULL;
\r
690 first.sendTime = second.sendTime = 2;
\r
691 first.sendDrawOffers = 1;
\r
692 if (appData.firstPlaysBlack) {
\r
693 first.twoMachinesColor = "black\n";
\r
694 second.twoMachinesColor = "white\n";
\r
696 first.twoMachinesColor = "white\n";
\r
697 second.twoMachinesColor = "black\n";
\r
699 first.program = appData.firstChessProgram;
\r
700 second.program = appData.secondChessProgram;
\r
701 first.host = appData.firstHost;
\r
702 second.host = appData.secondHost;
\r
703 first.dir = appData.firstDirectory;
\r
704 second.dir = appData.secondDirectory;
\r
705 first.other = &second;
\r
706 second.other = &first;
\r
707 first.initString = appData.initString;
\r
708 second.initString = appData.secondInitString;
\r
709 first.computerString = appData.firstComputerString;
\r
710 second.computerString = appData.secondComputerString;
\r
711 first.useSigint = second.useSigint = TRUE;
\r
712 first.useSigterm = second.useSigterm = TRUE;
\r
713 first.reuse = appData.reuseFirst;
\r
714 second.reuse = appData.reuseSecond;
\r
715 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
716 second.nps = appData.secondNPS;
\r
717 first.useSetboard = second.useSetboard = FALSE;
\r
718 first.useSAN = second.useSAN = FALSE;
\r
719 first.usePing = second.usePing = FALSE;
\r
720 first.lastPing = second.lastPing = 0;
\r
721 first.lastPong = second.lastPong = 0;
\r
722 first.usePlayother = second.usePlayother = FALSE;
\r
723 first.useColors = second.useColors = TRUE;
\r
724 first.useUsermove = second.useUsermove = FALSE;
\r
725 first.sendICS = second.sendICS = FALSE;
\r
726 first.sendName = second.sendName = appData.icsActive;
\r
727 first.sdKludge = second.sdKludge = FALSE;
\r
728 first.stKludge = second.stKludge = FALSE;
\r
729 TidyProgramName(first.program, first.host, first.tidy);
\r
730 TidyProgramName(second.program, second.host, second.tidy);
\r
731 first.matchWins = second.matchWins = 0;
\r
732 strcpy(first.variants, appData.variant);
\r
733 strcpy(second.variants, appData.variant);
\r
734 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
735 first.analyzing = second.analyzing = FALSE;
\r
736 first.initDone = second.initDone = FALSE;
\r
738 /* New features added by Tord: */
\r
739 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
740 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
741 /* End of new features added by Tord. */
\r
743 /* [HGM] time odds: set factor for each machine */
\r
744 first.timeOdds = appData.firstTimeOdds;
\r
745 second.timeOdds = appData.secondTimeOdds;
\r
747 if(appData.timeOddsMode) {
\r
748 norm = first.timeOdds;
\r
749 if(norm > second.timeOdds) norm = second.timeOdds;
\r
751 first.timeOdds /= norm;
\r
752 second.timeOdds /= norm;
\r
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
756 first.accumulateTC = appData.firstAccumulateTC;
\r
757 second.accumulateTC = appData.secondAccumulateTC;
\r
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
761 first.debug = second.debug = FALSE;
\r
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
764 /* [HGM] options */
\r
765 first.optionSettings = appData.firstOptions;
\r
766 second.optionSettings = appData.secondOptions;
\r
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
770 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
771 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
775 if (appData.firstProtocolVersion > PROTOVER ||
\r
776 appData.firstProtocolVersion < 1) {
\r
778 sprintf(buf, _("protocol version %d not supported"),
\r
779 appData.firstProtocolVersion);
\r
780 DisplayFatalError(buf, 0, 2);
\r
782 first.protocolVersion = appData.firstProtocolVersion;
\r
785 if (appData.secondProtocolVersion > PROTOVER ||
\r
786 appData.secondProtocolVersion < 1) {
\r
788 sprintf(buf, _("protocol version %d not supported"),
\r
789 appData.secondProtocolVersion);
\r
790 DisplayFatalError(buf, 0, 2);
\r
792 second.protocolVersion = appData.secondProtocolVersion;
\r
795 if (appData.icsActive) {
\r
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
798 appData.clockMode = FALSE;
\r
799 first.sendTime = second.sendTime = 0;
\r
803 /* Override some settings from environment variables, for backward
\r
804 compatibility. Unfortunately it's not feasible to have the env
\r
805 vars just set defaults, at least in xboard. Ugh.
\r
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
812 if (appData.noChessProgram) {
\r
813 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
814 + strlen(PATCHLEVEL));
\r
815 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
820 while (*q != ' ' && *q != NULLCHAR) q++;
\r
822 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
823 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
824 + strlen(PATCHLEVEL) + (q - p));
\r
825 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
826 strncat(programVersion, p, q - p);
\r
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
829 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
830 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
831 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
835 if (!appData.icsActive) {
\r
837 /* Check for variants that are supported only in ICS mode,
\r
838 or not at all. Some that are accepted here nevertheless
\r
839 have bugs; see comments below.
\r
841 VariantClass variant = StringToVariant(appData.variant);
\r
843 case VariantBughouse: /* need four players and two boards */
\r
844 case VariantKriegspiel: /* need to hide pieces and move details */
\r
845 /* case VariantFischeRandom: (Fabien: moved below) */
\r
846 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
847 DisplayFatalError(buf, 0, 2);
\r
850 case VariantUnknown:
\r
851 case VariantLoadable:
\r
861 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
862 DisplayFatalError(buf, 0, 2);
\r
865 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
866 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
867 case VariantGothic: /* [HGM] should work */
\r
868 case VariantCapablanca: /* [HGM] should work */
\r
869 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
870 case VariantShogi: /* [HGM] drops not tested for legality */
\r
871 case VariantKnightmate: /* [HGM] should work */
\r
872 case VariantCylinder: /* [HGM] untested */
\r
873 case VariantFalcon: /* [HGM] untested */
\r
874 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
875 offboard interposition not understood */
\r
876 case VariantNormal: /* definitely works! */
\r
877 case VariantWildCastle: /* pieces not automatically shuffled */
\r
878 case VariantNoCastle: /* pieces not automatically shuffled */
\r
879 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
880 case VariantLosers: /* should work except for win condition,
\r
881 and doesn't know captures are mandatory */
\r
882 case VariantSuicide: /* should work except for win condition,
\r
883 and doesn't know captures are mandatory */
\r
884 case VariantGiveaway: /* should work except for win condition,
\r
885 and doesn't know captures are mandatory */
\r
886 case VariantTwoKings: /* should work */
\r
887 case VariantAtomic: /* should work except for win condition */
\r
888 case Variant3Check: /* should work except for win condition */
\r
889 case VariantShatranj: /* should work except for all win conditions */
\r
890 case VariantBerolina: /* might work if TestLegality is off */
\r
891 case VariantCapaRandom: /* should work */
\r
892 case VariantJanus: /* should work */
\r
893 case VariantSuper: /* experimental */
\r
894 case VariantGreat: /* experimental, requires legality testing to be off */
\r
899 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
900 InitEngineUCI( installDir, &second );
\r
903 int NextIntegerFromString( char ** str, long * value )
\r
908 while( *s == ' ' || *s == '\t' ) {
\r
914 if( *s >= '0' && *s <= '9' ) {
\r
915 while( *s >= '0' && *s <= '9' ) {
\r
916 *value = *value * 10 + (*s - '0');
\r
928 int NextTimeControlFromString( char ** str, long * value )
\r
931 int result = NextIntegerFromString( str, &temp );
\r
933 if( result == 0 ) {
\r
934 *value = temp * 60; /* Minutes */
\r
935 if( **str == ':' ) {
\r
937 result = NextIntegerFromString( str, &temp );
\r
938 *value += temp; /* Seconds */
\r
945 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
946 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
947 int result = -1; long temp, temp2;
\r
949 if(**str != '+') return -1; // old params remain in force!
\r
951 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
954 /* time only: incremental or sudden-death time control */
\r
955 if(**str == '+') { /* increment follows; read it */
\r
957 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
958 *inc = temp2 * 1000;
\r
960 *moves = 0; *tc = temp * 1000;
\r
962 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
964 (*str)++; /* classical time control */
\r
965 result = NextTimeControlFromString( str, &temp2);
\r
968 *tc = temp2 * 1000;
\r
974 int GetTimeQuota(int movenr)
\r
975 { /* [HGM] get time to add from the multi-session time-control string */
\r
976 int moves=1; /* kludge to force reading of first session */
\r
977 long time, increment;
\r
978 char *s = fullTimeControlString;
\r
980 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
982 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
983 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
984 if(movenr == -1) return time; /* last move before new session */
\r
985 if(!moves) return increment; /* current session is incremental */
\r
986 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
987 } while(movenr >= -1); /* try again for next session */
\r
989 return 0; // no new time quota on this move
\r
993 ParseTimeControl(tc, ti, mps)
\r
999 int matched, min, sec;
\r
1001 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1002 if (matched == 1) {
\r
1003 timeControl = min * 60 * 1000;
\r
1004 } else if (matched == 2) {
\r
1005 timeControl = (min * 60 + sec) * 1000;
\r
1012 char buf[MSG_SIZ];
\r
1014 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1017 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1018 else sprintf(buf, "+%s+%d", tc, ti);
\r
1021 sprintf(buf, "+%d/%s", mps, tc);
\r
1022 else sprintf(buf, "+%s", tc);
\r
1024 fullTimeControlString = StrSave(buf);
\r
1026 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1030 if( *tc == '/' ) {
\r
1031 /* Parse second time control */
\r
1034 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1042 timeControl_2 = tc2 * 1000;
\r
1045 timeControl_2 = 0;
\r
1052 timeControl = tc1 * 1000;
\r
1056 timeIncrement = ti * 1000; /* convert to ms */
\r
1057 movesPerSession = 0;
\r
1059 timeIncrement = 0;
\r
1060 movesPerSession = mps;
\r
1068 if (appData.debugMode) {
\r
1069 fprintf(debugFP, "%s\n", programVersion);
\r
1072 if (appData.matchGames > 0) {
\r
1073 appData.matchMode = TRUE;
\r
1074 } else if (appData.matchMode) {
\r
1075 appData.matchGames = 1;
\r
1077 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1078 appData.matchGames = appData.sameColorGames;
\r
1079 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1080 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1081 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1083 Reset(TRUE, FALSE);
\r
1084 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1087 /* kludge: allow timeout for initial "feature" commands */
\r
1089 DisplayMessage("", _("Starting chess program"));
\r
1090 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1095 InitBackEnd3 P((void))
\r
1097 GameMode initialMode;
\r
1098 char buf[MSG_SIZ];
\r
1101 InitChessProgram(&first, startedFromSetupPosition);
\r
1104 if (appData.icsActive) {
\r
1106 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1109 err = establish();
\r
1111 if (*appData.icsCommPort != NULLCHAR) {
\r
1112 sprintf(buf, _("Could not open comm port %s"),
\r
1113 appData.icsCommPort);
\r
1115 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1116 appData.icsHost, appData.icsPort);
\r
1118 DisplayFatalError(buf, err, 1);
\r
1123 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1125 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1126 } else if (appData.noChessProgram) {
\r
1132 if (*appData.cmailGameName != NULLCHAR) {
\r
1134 OpenLoopback(&cmailPR);
\r
1136 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1140 DisplayMessage("", "");
\r
1141 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1142 initialMode = BeginningOfGame;
\r
1143 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1144 initialMode = TwoMachinesPlay;
\r
1145 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1146 initialMode = AnalyzeFile;
\r
1147 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1148 initialMode = AnalyzeMode;
\r
1149 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1150 initialMode = MachinePlaysWhite;
\r
1151 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1152 initialMode = MachinePlaysBlack;
\r
1153 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1154 initialMode = EditGame;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1156 initialMode = EditPosition;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1158 initialMode = Training;
\r
1160 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1161 DisplayFatalError(buf, 0, 2);
\r
1165 if (appData.matchMode) {
\r
1166 /* Set up machine vs. machine match */
\r
1167 if (appData.noChessProgram) {
\r
1168 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1174 if (*appData.loadGameFile != NULLCHAR) {
\r
1175 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1176 if(index<0) lastIndex = index = 1;
\r
1177 if (!LoadGameFromFile(appData.loadGameFile,
\r
1179 appData.loadGameFile, FALSE)) {
\r
1180 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1184 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1185 if(index<0) lastIndex = index = 1;
\r
1186 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1188 appData.loadPositionFile)) {
\r
1189 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1193 TwoMachinesEvent();
\r
1194 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1195 /* Set up cmail mode */
\r
1196 ReloadCmailMsgEvent(TRUE);
\r
1198 /* Set up other modes */
\r
1199 if (initialMode == AnalyzeFile) {
\r
1200 if (*appData.loadGameFile == NULLCHAR) {
\r
1201 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1205 if (*appData.loadGameFile != NULLCHAR) {
\r
1206 (void) LoadGameFromFile(appData.loadGameFile,
\r
1207 appData.loadGameIndex,
\r
1208 appData.loadGameFile, TRUE);
\r
1209 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1210 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1211 appData.loadPositionIndex,
\r
1212 appData.loadPositionFile);
\r
1213 /* [HGM] try to make self-starting even after FEN load */
\r
1214 /* to allow automatic setup of fairy variants with wtm */
\r
1215 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1216 gameMode = BeginningOfGame;
\r
1217 setboardSpoiledMachineBlack = 1;
\r
1219 /* [HGM] loadPos: make that every new game uses the setup */
\r
1220 /* from file as long as we do not switch variant */
\r
1221 if(!blackPlaysFirst) { int i;
\r
1222 startedFromPositionFile = TRUE;
\r
1223 CopyBoard(filePosition, boards[0]);
\r
1224 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1227 if (initialMode == AnalyzeMode) {
\r
1228 if (appData.noChessProgram) {
\r
1229 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1232 if (appData.icsActive) {
\r
1233 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1236 AnalyzeModeEvent();
\r
1237 } else if (initialMode == AnalyzeFile) {
\r
1238 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1239 ShowThinkingEvent();
\r
1240 AnalyzeFileEvent();
\r
1241 AnalysisPeriodicEvent(1);
\r
1242 } else if (initialMode == MachinePlaysWhite) {
\r
1243 if (appData.noChessProgram) {
\r
1244 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1248 if (appData.icsActive) {
\r
1249 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1253 MachineWhiteEvent();
\r
1254 } else if (initialMode == MachinePlaysBlack) {
\r
1255 if (appData.noChessProgram) {
\r
1256 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1260 if (appData.icsActive) {
\r
1261 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1265 MachineBlackEvent();
\r
1266 } else if (initialMode == TwoMachinesPlay) {
\r
1267 if (appData.noChessProgram) {
\r
1268 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1272 if (appData.icsActive) {
\r
1273 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1277 TwoMachinesEvent();
\r
1278 } else if (initialMode == EditGame) {
\r
1280 } else if (initialMode == EditPosition) {
\r
1281 EditPositionEvent();
\r
1282 } else if (initialMode == Training) {
\r
1283 if (*appData.loadGameFile == NULLCHAR) {
\r
1284 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1293 * Establish will establish a contact to a remote host.port.
\r
1294 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1295 * used to talk to the host.
\r
1296 * Returns 0 if okay, error code if not.
\r
1301 char buf[MSG_SIZ];
\r
1303 if (*appData.icsCommPort != NULLCHAR) {
\r
1304 /* Talk to the host through a serial comm port */
\r
1305 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1307 } else if (*appData.gateway != NULLCHAR) {
\r
1308 if (*appData.remoteShell == NULLCHAR) {
\r
1309 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1310 sprintf(buf, "%s %s %s",
\r
1311 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1312 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1315 /* Use the rsh program to run telnet program on a gateway host */
\r
1316 if (*appData.remoteUser == NULLCHAR) {
\r
1317 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1318 appData.gateway, appData.telnetProgram,
\r
1319 appData.icsHost, appData.icsPort);
\r
1321 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1322 appData.remoteShell, appData.gateway,
\r
1323 appData.remoteUser, appData.telnetProgram,
\r
1324 appData.icsHost, appData.icsPort);
\r
1326 return StartChildProcess(buf, "", &icsPR);
\r
1329 } else if (appData.useTelnet) {
\r
1330 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1333 /* TCP socket interface differs somewhat between
\r
1334 Unix and NT; handle details in the front end.
\r
1336 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1341 show_bytes(fp, buf, count)
\r
1347 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1348 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1357 /* Returns an errno value */
\r
1359 OutputMaybeTelnet(pr, message, count, outError)
\r
1365 char buf[8192], *p, *q, *buflim;
\r
1366 int left, newcount, outcount;
\r
1368 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1369 *appData.gateway != NULLCHAR) {
\r
1370 if (appData.debugMode) {
\r
1371 fprintf(debugFP, ">ICS: ");
\r
1372 show_bytes(debugFP, message, count);
\r
1373 fprintf(debugFP, "\n");
\r
1375 return OutputToProcess(pr, message, count, outError);
\r
1378 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1384 if (q >= buflim) {
\r
1385 if (appData.debugMode) {
\r
1386 fprintf(debugFP, ">ICS: ");
\r
1387 show_bytes(debugFP, buf, newcount);
\r
1388 fprintf(debugFP, "\n");
\r
1390 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1391 if (outcount < newcount) return -1; /* to be sure */
\r
1398 } else if (((unsigned char) *p) == TN_IAC) {
\r
1399 *q++ = (char) TN_IAC;
\r
1406 if (appData.debugMode) {
\r
1407 fprintf(debugFP, ">ICS: ");
\r
1408 show_bytes(debugFP, buf, newcount);
\r
1409 fprintf(debugFP, "\n");
\r
1411 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1412 if (outcount < newcount) return -1; /* to be sure */
\r
1417 read_from_player(isr, closure, message, count, error)
\r
1418 InputSourceRef isr;
\r
1424 int outError, outCount;
\r
1425 static int gotEof = 0;
\r
1427 /* Pass data read from player on to ICS */
\r
1430 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1431 if (outCount < count) {
\r
1432 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1434 } else if (count < 0) {
\r
1435 RemoveInputSource(isr);
\r
1436 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1437 } else if (gotEof++ > 0) {
\r
1438 RemoveInputSource(isr);
\r
1439 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1447 int count, outCount, outError;
\r
1449 if (icsPR == NULL) return;
\r
1451 count = strlen(s);
\r
1452 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1453 if (outCount < count) {
\r
1454 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1458 /* This is used for sending logon scripts to the ICS. Sending
\r
1459 without a delay causes problems when using timestamp on ICC
\r
1460 (at least on my machine). */
\r
1462 SendToICSDelayed(s,msdelay)
\r
1466 int count, outCount, outError;
\r
1468 if (icsPR == NULL) return;
\r
1470 count = strlen(s);
\r
1471 if (appData.debugMode) {
\r
1472 fprintf(debugFP, ">ICS: ");
\r
1473 show_bytes(debugFP, s, count);
\r
1474 fprintf(debugFP, "\n");
\r
1476 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1478 if (outCount < count) {
\r
1479 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1484 /* Remove all highlighting escape sequences in s
\r
1485 Also deletes any suffix starting with '('
\r
1488 StripHighlightAndTitle(s)
\r
1491 static char retbuf[MSG_SIZ];
\r
1494 while (*s != NULLCHAR) {
\r
1495 while (*s == '\033') {
\r
1496 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1497 if (*s != NULLCHAR) s++;
\r
1499 while (*s != NULLCHAR && *s != '\033') {
\r
1500 if (*s == '(' || *s == '[') {
\r
1511 /* Remove all highlighting escape sequences in s */
\r
1516 static char retbuf[MSG_SIZ];
\r
1519 while (*s != NULLCHAR) {
\r
1520 while (*s == '\033') {
\r
1521 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1522 if (*s != NULLCHAR) s++;
\r
1524 while (*s != NULLCHAR && *s != '\033') {
\r
1532 char *variantNames[] = VARIANT_NAMES;
\r
1537 return variantNames[v];
\r
1541 /* Identify a variant from the strings the chess servers use or the
\r
1542 PGN Variant tag names we use. */
\r
1544 StringToVariant(e)
\r
1549 VariantClass v = VariantNormal;
\r
1550 int i, found = FALSE;
\r
1551 char buf[MSG_SIZ];
\r
1555 /* [HGM] skip over optional board-size prefixes */
\r
1556 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1557 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1558 while( *e++ != '_');
\r
1561 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1562 if (StrCaseStr(e, variantNames[i])) {
\r
1563 v = (VariantClass) i;
\r
1570 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1571 || StrCaseStr(e, "wild/fr")
\r
1572 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1573 v = VariantFischeRandom;
\r
1574 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1575 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1577 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1578 if (isdigit(*p)) {
\r
1584 case 0: /* FICS only, actually */
\r
1586 /* Castling legal even if K starts on d-file */
\r
1587 v = VariantWildCastle;
\r
1592 /* Castling illegal even if K & R happen to start in
\r
1593 normal positions. */
\r
1594 v = VariantNoCastle;
\r
1607 /* Castling legal iff K & R start in normal positions */
\r
1608 v = VariantNormal;
\r
1613 /* Special wilds for position setup; unclear what to do here */
\r
1614 v = VariantLoadable;
\r
1617 /* Bizarre ICC game */
\r
1618 v = VariantTwoKings;
\r
1621 v = VariantKriegspiel;
\r
1624 v = VariantLosers;
\r
1627 v = VariantFischeRandom;
\r
1630 v = VariantCrazyhouse;
\r
1633 v = VariantBughouse;
\r
1636 v = Variant3Check;
\r
1639 /* Not quite the same as FICS suicide! */
\r
1640 v = VariantGiveaway;
\r
1643 v = VariantAtomic;
\r
1646 v = VariantShatranj;
\r
1649 /* Temporary names for future ICC types. The name *will* change in
\r
1650 the next xboard/WinBoard release after ICC defines it. */
\r
1679 v = VariantXiangqi;
\r
1682 v = VariantCourier;
\r
1685 v = VariantGothic;
\r
1688 v = VariantCapablanca;
\r
1691 v = VariantKnightmate;
\r
1697 v = VariantCylinder;
\r
1700 v = VariantFalcon;
\r
1703 v = VariantCapaRandom;
\r
1706 v = VariantBerolina;
\r
1718 /* Found "wild" or "w" in the string but no number;
\r
1719 must assume it's normal chess. */
\r
1720 v = VariantNormal;
\r
1723 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1724 DisplayError(buf, 0);
\r
1725 v = VariantUnknown;
\r
1730 if (appData.debugMode) {
\r
1731 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1732 e, wnum, VariantName(v));
\r
1737 static int leftover_start = 0, leftover_len = 0;
\r
1738 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1740 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1741 advance *index beyond it, and set leftover_start to the new value of
\r
1742 *index; else return FALSE. If pattern contains the character '*', it
\r
1743 matches any sequence of characters not containing '\r', '\n', or the
\r
1744 character following the '*' (if any), and the matched sequence(s) are
\r
1745 copied into star_match.
\r
1748 looking_at(buf, index, pattern)
\r
1753 char *bufp = &buf[*index], *patternp = pattern;
\r
1754 int star_count = 0;
\r
1755 char *matchp = star_match[0];
\r
1758 if (*patternp == NULLCHAR) {
\r
1759 *index = leftover_start = bufp - buf;
\r
1760 *matchp = NULLCHAR;
\r
1763 if (*bufp == NULLCHAR) return FALSE;
\r
1764 if (*patternp == '*') {
\r
1765 if (*bufp == *(patternp + 1)) {
\r
1766 *matchp = NULLCHAR;
\r
1767 matchp = star_match[++star_count];
\r
1771 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1773 if (*patternp == NULLCHAR)
\r
1778 *matchp++ = *bufp++;
\r
1782 if (*patternp != *bufp) return FALSE;
\r
1789 SendToPlayer(data, length)
\r
1793 int error, outCount;
\r
1794 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1795 if (outCount < length) {
\r
1796 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1801 PackHolding(packed, holding)
\r
1805 char *p = holding;
\r
1807 int runlength = 0;
\r
1813 switch (runlength) {
\r
1824 sprintf(q, "%d", runlength);
\r
1836 /* Telnet protocol requests from the front end */
\r
1838 TelnetRequest(ddww, option)
\r
1839 unsigned char ddww, option;
\r
1841 unsigned char msg[3];
\r
1842 int outCount, outError;
\r
1844 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1846 if (appData.debugMode) {
\r
1847 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1863 sprintf(buf1, "%d", ddww);
\r
1868 optionStr = "ECHO";
\r
1872 sprintf(buf2, "%d", option);
\r
1875 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1880 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1881 if (outCount < 3) {
\r
1882 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1889 if (!appData.icsActive) return;
\r
1890 TelnetRequest(TN_DO, TN_ECHO);
\r
1896 if (!appData.icsActive) return;
\r
1897 TelnetRequest(TN_DONT, TN_ECHO);
\r
1901 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1903 /* put the holdings sent to us by the server on the board holdings area */
\r
1904 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1906 ChessSquare piece;
\r
1908 if(gameInfo.holdingsWidth < 2) return;
\r
1910 if( (int)lowestPiece >= BlackPawn ) {
\r
1911 holdingsColumn = 0;
\r
1913 holdingsStartRow = BOARD_HEIGHT-1;
\r
1916 holdingsColumn = BOARD_WIDTH-1;
\r
1917 countsColumn = BOARD_WIDTH-2;
\r
1918 holdingsStartRow = 0;
\r
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1923 board[i][holdingsColumn] = EmptySquare;
\r
1924 board[i][countsColumn] = (ChessSquare) 0;
\r
1926 while( (p=*holdings++) != NULLCHAR ) {
\r
1927 piece = CharToPiece( ToUpper(p) );
\r
1928 if(piece == EmptySquare) continue;
\r
1929 /*j = (int) piece - (int) WhitePawn;*/
\r
1930 j = PieceToNumber(piece);
\r
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1932 if(j < 0) continue; /* should not happen */
\r
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1935 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1942 VariantSwitch(Board board, VariantClass newVariant)
\r
1944 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1945 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1946 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1948 startedFromPositionFile = FALSE;
\r
1949 if(gameInfo.variant == newVariant) return;
\r
1951 /* [HGM] This routine is called each time an assignment is made to
\r
1952 * gameInfo.variant during a game, to make sure the board sizes
\r
1953 * are set to match the new variant. If that means adding or deleting
\r
1954 * holdings, we shift the playing board accordingly
\r
1955 * This kludge is needed because in ICS observe mode, we get boards
\r
1956 * of an ongoing game without knowing the variant, and learn about the
\r
1957 * latter only later. This can be because of the move list we requested,
\r
1958 * in which case the game history is refilled from the beginning anyway,
\r
1959 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1960 * case we want to add those holdings to the already received position.
\r
1964 if (appData.debugMode) {
\r
1965 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1966 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1967 setbuf(debugFP, NULL);
\r
1969 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1970 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1971 switch(newVariant) {
\r
1972 case VariantShogi:
\r
1973 newWidth = 9; newHeight = 9;
\r
1974 gameInfo.holdingsSize = 7;
\r
1975 case VariantBughouse:
\r
1976 case VariantCrazyhouse:
\r
1977 newHoldingsWidth = 2; break;
\r
1979 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1982 if(newWidth != gameInfo.boardWidth ||
\r
1983 newHeight != gameInfo.boardHeight ||
\r
1984 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1986 /* shift position to new playing area, if needed */
\r
1987 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1988 for(i=0; i<BOARD_HEIGHT; i++)
\r
1989 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1990 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1992 for(i=0; i<newHeight; i++) {
\r
1993 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1994 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1996 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1997 for(i=0; i<BOARD_HEIGHT; i++)
\r
1998 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1999 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2003 gameInfo.boardWidth = newWidth;
\r
2004 gameInfo.boardHeight = newHeight;
\r
2005 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2006 gameInfo.variant = newVariant;
\r
2007 InitDrawingSizes(-2, 0);
\r
2009 /* [HGM] The following should definitely be solved in a better way */
\r
2011 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2012 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2013 saveEP = epStatus[0];
\r
2015 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2017 epStatus[0] = saveEP;
\r
2018 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2019 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2021 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2023 forwardMostMove = oldForwardMostMove;
\r
2024 backwardMostMove = oldBackwardMostMove;
\r
2025 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2028 static int loggedOn = FALSE;
\r
2030 /*-- Game start info cache: --*/
\r
2032 char gs_kind[MSG_SIZ];
\r
2033 static char player1Name[128] = "";
\r
2034 static char player2Name[128] = "";
\r
2035 static int player1Rating = -1;
\r
2036 static int player2Rating = -1;
\r
2037 /*----------------------------*/
\r
2039 ColorClass curColor = ColorNormal;
\r
2040 int suppressKibitz = 0;
\r
2043 read_from_ics(isr, closure, data, count, error)
\r
2044 InputSourceRef isr;
\r
2050 #define BUF_SIZE 8192
\r
2051 #define STARTED_NONE 0
\r
2052 #define STARTED_MOVES 1
\r
2053 #define STARTED_BOARD 2
\r
2054 #define STARTED_OBSERVE 3
\r
2055 #define STARTED_HOLDINGS 4
\r
2056 #define STARTED_CHATTER 5
\r
2057 #define STARTED_COMMENT 6
\r
2058 #define STARTED_MOVES_NOHIDE 7
\r
2060 static int started = STARTED_NONE;
\r
2061 static char parse[20000];
\r
2062 static int parse_pos = 0;
\r
2063 static char buf[BUF_SIZE + 1];
\r
2064 static int firstTime = TRUE, intfSet = FALSE;
\r
2065 static ColorClass prevColor = ColorNormal;
\r
2066 static int savingComment = FALSE;
\r
2072 int backup; /* [DM] For zippy color lines */
\r
2075 if (appData.debugMode) {
\r
2077 fprintf(debugFP, "<ICS: ");
\r
2078 show_bytes(debugFP, data, count);
\r
2079 fprintf(debugFP, "\n");
\r
2083 if (appData.debugMode) { int f = forwardMostMove;
\r
2084 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2085 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2088 /* If last read ended with a partial line that we couldn't parse,
\r
2089 prepend it to the new read and try again. */
\r
2090 if (leftover_len > 0) {
\r
2091 for (i=0; i<leftover_len; i++)
\r
2092 buf[i] = buf[leftover_start + i];
\r
2095 /* Copy in new characters, removing nulls and \r's */
\r
2096 buf_len = leftover_len;
\r
2097 for (i = 0; i < count; i++) {
\r
2098 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2099 buf[buf_len++] = data[i];
\r
2100 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2101 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2102 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2105 buf[buf_len] = NULLCHAR;
\r
2106 next_out = leftover_len;
\r
2107 leftover_start = 0;
\r
2110 while (i < buf_len) {
\r
2111 /* Deal with part of the TELNET option negotiation
\r
2112 protocol. We refuse to do anything beyond the
\r
2113 defaults, except that we allow the WILL ECHO option,
\r
2114 which ICS uses to turn off password echoing when we are
\r
2115 directly connected to it. We reject this option
\r
2116 if localLineEditing mode is on (always on in xboard)
\r
2117 and we are talking to port 23, which might be a real
\r
2118 telnet server that will try to keep WILL ECHO on permanently.
\r
2120 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2121 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2122 unsigned char option;
\r
2124 switch ((unsigned char) buf[++i]) {
\r
2126 if (appData.debugMode)
\r
2127 fprintf(debugFP, "\n<WILL ");
\r
2128 switch (option = (unsigned char) buf[++i]) {
\r
2130 if (appData.debugMode)
\r
2131 fprintf(debugFP, "ECHO ");
\r
2132 /* Reply only if this is a change, according
\r
2133 to the protocol rules. */
\r
2134 if (remoteEchoOption) break;
\r
2135 if (appData.localLineEditing &&
\r
2136 atoi(appData.icsPort) == TN_PORT) {
\r
2137 TelnetRequest(TN_DONT, TN_ECHO);
\r
2140 TelnetRequest(TN_DO, TN_ECHO);
\r
2141 remoteEchoOption = TRUE;
\r
2145 if (appData.debugMode)
\r
2146 fprintf(debugFP, "%d ", option);
\r
2147 /* Whatever this is, we don't want it. */
\r
2148 TelnetRequest(TN_DONT, option);
\r
2153 if (appData.debugMode)
\r
2154 fprintf(debugFP, "\n<WONT ");
\r
2155 switch (option = (unsigned char) buf[++i]) {
\r
2157 if (appData.debugMode)
\r
2158 fprintf(debugFP, "ECHO ");
\r
2159 /* Reply only if this is a change, according
\r
2160 to the protocol rules. */
\r
2161 if (!remoteEchoOption) break;
\r
2163 TelnetRequest(TN_DONT, TN_ECHO);
\r
2164 remoteEchoOption = FALSE;
\r
2167 if (appData.debugMode)
\r
2168 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2169 /* Whatever this is, it must already be turned
\r
2170 off, because we never agree to turn on
\r
2171 anything non-default, so according to the
\r
2172 protocol rules, we don't reply. */
\r
2177 if (appData.debugMode)
\r
2178 fprintf(debugFP, "\n<DO ");
\r
2179 switch (option = (unsigned char) buf[++i]) {
\r
2181 /* Whatever this is, we refuse to do it. */
\r
2182 if (appData.debugMode)
\r
2183 fprintf(debugFP, "%d ", option);
\r
2184 TelnetRequest(TN_WONT, option);
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "\n<DONT ");
\r
2191 switch (option = (unsigned char) buf[++i]) {
\r
2193 if (appData.debugMode)
\r
2194 fprintf(debugFP, "%d ", option);
\r
2195 /* Whatever this is, we are already not doing
\r
2196 it, because we never agree to do anything
\r
2197 non-default, so according to the protocol
\r
2198 rules, we don't reply. */
\r
2203 if (appData.debugMode)
\r
2204 fprintf(debugFP, "\n<IAC ");
\r
2205 /* Doubled IAC; pass it through */
\r
2209 if (appData.debugMode)
\r
2210 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2211 /* Drop all other telnet commands on the floor */
\r
2214 if (oldi > next_out)
\r
2215 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2216 if (++i > next_out)
\r
2221 /* OK, this at least will *usually* work */
\r
2222 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2226 if (loggedOn && !intfSet) {
\r
2227 if (ics_type == ICS_ICC) {
\r
2229 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2232 } else if (ics_type == ICS_CHESSNET) {
\r
2233 sprintf(str, "/style 12\n");
\r
2235 strcpy(str, "alias $ @\n$set interface ");
\r
2236 strcat(str, programVersion);
\r
2237 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2239 strcat(str, "$iset nohighlight 1\n");
\r
2241 strcat(str, "$iset lock 1\n$style 12\n");
\r
2247 if (started == STARTED_COMMENT) {
\r
2248 /* Accumulate characters in comment */
\r
2249 parse[parse_pos++] = buf[i];
\r
2250 if (buf[i] == '\n') {
\r
2251 parse[parse_pos] = NULLCHAR;
\r
2252 if(!suppressKibitz) // [HGM] kibitz
\r
2253 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2254 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2255 int nrDigit = 0, nrAlph = 0, i;
\r
2256 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2257 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2258 parse[parse_pos] = NULLCHAR;
\r
2259 // try to be smart: if it does not look like search info, it should go to
\r
2260 // ICS interaction window after all, not to engine-output window.
\r
2261 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2262 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2263 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2264 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2266 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2267 OutputKibitz(suppressKibitz, parse);
\r
2269 char tmp[MSG_SIZ];
\r
2270 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2271 SendToPlayer(tmp, strlen(tmp));
\r
2274 started = STARTED_NONE;
\r
2276 /* Don't match patterns against characters in chatter */
\r
2281 if (started == STARTED_CHATTER) {
\r
2282 if (buf[i] != '\n') {
\r
2283 /* Don't match patterns against characters in chatter */
\r
2287 started = STARTED_NONE;
\r
2290 /* Kludge to deal with rcmd protocol */
\r
2291 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2292 DisplayFatalError(&buf[1], 0, 1);
\r
2295 firstTime = FALSE;
\r
2298 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2299 ics_type = ICS_ICC;
\r
2301 if (appData.debugMode)
\r
2302 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2305 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2306 ics_type = ICS_FICS;
\r
2308 if (appData.debugMode)
\r
2309 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2312 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2313 ics_type = ICS_CHESSNET;
\r
2315 if (appData.debugMode)
\r
2316 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2321 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2322 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2323 looking_at(buf, &i, "will be \"*\""))) {
\r
2324 strcpy(ics_handle, star_match[0]);
\r
2328 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2329 char buf[MSG_SIZ];
\r
2330 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2331 DisplayIcsInteractionTitle(buf);
\r
2332 have_set_title = TRUE;
\r
2335 /* skip finger notes */
\r
2336 if (started == STARTED_NONE &&
\r
2337 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2338 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2339 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2340 started = STARTED_CHATTER;
\r
2345 /* skip formula vars */
\r
2346 if (started == STARTED_NONE &&
\r
2347 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2348 started = STARTED_CHATTER;
\r
2354 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2355 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2356 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2357 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2358 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2359 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2360 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2361 suppressKibitz = TRUE;
\r
2362 if((StrStr(star_match[0], gameInfo.white) == star_match[0])
\r
2363 && (gameMode == IcsPlayingWhite) ||
\r
2364 (StrStr(star_match[0], gameInfo.black) == star_match[0])
\r
2365 && (gameMode == IcsPlayingBlack) ) // opponent kibitz
\r
2366 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2368 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2369 parse_pos = 0; parse[0] = NULLCHAR;
\r
2370 savingComment = TRUE;
\r
2371 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2372 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2376 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2377 started = STARTED_CHATTER;
\r
2378 suppressKibitz = TRUE;
\r
2380 } // [HGM] kibitz: end of patch
\r
2382 if (appData.zippyTalk || appData.zippyPlay) {
\r
2383 /* [DM] Backup address for color zippy lines */
\r
2387 if (loggedOn == TRUE)
\r
2388 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2389 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2391 if (ZippyControl(buf, &i) ||
\r
2392 ZippyConverse(buf, &i) ||
\r
2393 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2395 if (!appData.colorize) continue;
\r
2399 } // [DM] 'else { ' deleted
\r
2400 if (/* Don't color "message" or "messages" output */
\r
2401 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2402 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2403 looking_at(buf, &i, "--* (*:*): ") ||
\r
2404 /* Regular tells and says */
\r
2405 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2406 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2407 looking_at(buf, &i, "* says: ") ||
\r
2408 /* Message notifications (same color as tells) */
\r
2409 looking_at(buf, &i, "* has left a message ") ||
\r
2410 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2411 /* Whispers and kibitzes */
\r
2412 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2413 looking_at(buf, &i, "* kibitzes: ") ||
\r
2414 /* Channel tells */
\r
2415 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2417 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2418 /* Avoid "tells you:" spoofs in channels */
\r
2421 if (star_match[0][0] == NULLCHAR ||
\r
2422 strchr(star_match[0], ' ') ||
\r
2423 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2424 /* Reject bogus matches */
\r
2427 if (appData.colorize) {
\r
2428 if (oldi > next_out) {
\r
2429 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2434 Colorize(ColorTell, FALSE);
\r
2435 curColor = ColorTell;
\r
2438 Colorize(ColorKibitz, FALSE);
\r
2439 curColor = ColorKibitz;
\r
2442 p = strrchr(star_match[1], '(');
\r
2444 p = star_match[1];
\r
2448 if (atoi(p) == 1) {
\r
2449 Colorize(ColorChannel1, FALSE);
\r
2450 curColor = ColorChannel1;
\r
2452 Colorize(ColorChannel, FALSE);
\r
2453 curColor = ColorChannel;
\r
2457 curColor = ColorNormal;
\r
2461 if (started == STARTED_NONE && appData.autoComment &&
\r
2462 (gameMode == IcsObserving ||
\r
2463 gameMode == IcsPlayingWhite ||
\r
2464 gameMode == IcsPlayingBlack)) {
\r
2465 parse_pos = i - oldi;
\r
2466 memcpy(parse, &buf[oldi], parse_pos);
\r
2467 parse[parse_pos] = NULLCHAR;
\r
2468 started = STARTED_COMMENT;
\r
2469 savingComment = TRUE;
\r
2471 started = STARTED_CHATTER;
\r
2472 savingComment = FALSE;
\r
2479 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2480 looking_at(buf, &i, "* c-shouts: ")) {
\r
2481 if (appData.colorize) {
\r
2482 if (oldi > next_out) {
\r
2483 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2486 Colorize(ColorSShout, FALSE);
\r
2487 curColor = ColorSShout;
\r
2490 started = STARTED_CHATTER;
\r
2494 if (looking_at(buf, &i, "--->")) {
\r
2499 if (looking_at(buf, &i, "* shouts: ") ||
\r
2500 looking_at(buf, &i, "--> ")) {
\r
2501 if (appData.colorize) {
\r
2502 if (oldi > next_out) {
\r
2503 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2506 Colorize(ColorShout, FALSE);
\r
2507 curColor = ColorShout;
\r
2510 started = STARTED_CHATTER;
\r
2514 if (looking_at( buf, &i, "Challenge:")) {
\r
2515 if (appData.colorize) {
\r
2516 if (oldi > next_out) {
\r
2517 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2520 Colorize(ColorChallenge, FALSE);
\r
2521 curColor = ColorChallenge;
\r
2527 if (looking_at(buf, &i, "* offers you") ||
\r
2528 looking_at(buf, &i, "* offers to be") ||
\r
2529 looking_at(buf, &i, "* would like to") ||
\r
2530 looking_at(buf, &i, "* requests to") ||
\r
2531 looking_at(buf, &i, "Your opponent offers") ||
\r
2532 looking_at(buf, &i, "Your opponent requests")) {
\r
2534 if (appData.colorize) {
\r
2535 if (oldi > next_out) {
\r
2536 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2539 Colorize(ColorRequest, FALSE);
\r
2540 curColor = ColorRequest;
\r
2545 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2546 if (appData.colorize) {
\r
2547 if (oldi > next_out) {
\r
2548 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2551 Colorize(ColorSeek, FALSE);
\r
2552 curColor = ColorSeek;
\r
2557 if (looking_at(buf, &i, "\\ ")) {
\r
2558 if (prevColor != ColorNormal) {
\r
2559 if (oldi > next_out) {
\r
2560 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2563 Colorize(prevColor, TRUE);
\r
2564 curColor = prevColor;
\r
2566 if (savingComment) {
\r
2567 parse_pos = i - oldi;
\r
2568 memcpy(parse, &buf[oldi], parse_pos);
\r
2569 parse[parse_pos] = NULLCHAR;
\r
2570 started = STARTED_COMMENT;
\r
2572 started = STARTED_CHATTER;
\r
2577 if (looking_at(buf, &i, "Black Strength :") ||
\r
2578 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2579 looking_at(buf, &i, "<10>") ||
\r
2580 looking_at(buf, &i, "#@#")) {
\r
2581 /* Wrong board style */
\r
2583 SendToICS(ics_prefix);
\r
2584 SendToICS("set style 12\n");
\r
2585 SendToICS(ics_prefix);
\r
2586 SendToICS("refresh\n");
\r
2590 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2592 have_sent_ICS_logon = 1;
\r
2596 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2597 (looking_at(buf, &i, "\n<12> ") ||
\r
2598 looking_at(buf, &i, "<12> "))) {
\r
2600 if (oldi > next_out) {
\r
2601 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2604 started = STARTED_BOARD;
\r
2609 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2610 looking_at(buf, &i, "<b1> ")) {
\r
2611 if (oldi > next_out) {
\r
2612 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2615 started = STARTED_HOLDINGS;
\r
2620 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2622 /* Header for a move list -- first line */
\r
2624 switch (ics_getting_history) {
\r
2626 switch (gameMode) {
\r
2628 case BeginningOfGame:
\r
2629 /* User typed "moves" or "oldmoves" while we
\r
2630 were idle. Pretend we asked for these
\r
2631 moves and soak them up so user can step
\r
2632 through them and/or save them.
\r
2634 Reset(FALSE, TRUE);
\r
2635 gameMode = IcsObserving;
\r
2638 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2640 case EditGame: /*?*/
\r
2641 case EditPosition: /*?*/
\r
2642 /* Should above feature work in these modes too? */
\r
2643 /* For now it doesn't */
\r
2644 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2647 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2652 /* Is this the right one? */
\r
2653 if (gameInfo.white && gameInfo.black &&
\r
2654 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2655 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2657 ics_getting_history = H_GOT_REQ_HEADER;
\r
2660 case H_GOT_REQ_HEADER:
\r
2661 case H_GOT_UNREQ_HEADER:
\r
2662 case H_GOT_UNWANTED_HEADER:
\r
2663 case H_GETTING_MOVES:
\r
2664 /* Should not happen */
\r
2665 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2666 ics_getting_history = H_FALSE;
\r
2670 /* Save player ratings into gameInfo if needed */
\r
2671 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2672 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2673 (gameInfo.whiteRating == -1 ||
\r
2674 gameInfo.blackRating == -1)) {
\r
2676 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2677 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2678 if (appData.debugMode)
\r
2679 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2680 gameInfo.whiteRating, gameInfo.blackRating);
\r
2685 if (looking_at(buf, &i,
\r
2686 "* * match, initial time: * minute*, increment: * second")) {
\r
2687 /* Header for a move list -- second line */
\r
2688 /* Initial board will follow if this is a wild game */
\r
2689 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2690 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2691 gameInfo.event = StrSave(str);
\r
2692 /* [HGM] we switched variant. Translate boards if needed. */
\r
2693 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2697 if (looking_at(buf, &i, "Move ")) {
\r
2698 /* Beginning of a move list */
\r
2699 switch (ics_getting_history) {
\r
2701 /* Normally should not happen */
\r
2702 /* Maybe user hit reset while we were parsing */
\r
2705 /* Happens if we are ignoring a move list that is not
\r
2706 * the one we just requested. Common if the user
\r
2707 * tries to observe two games without turning off
\r
2710 case H_GETTING_MOVES:
\r
2711 /* Should not happen */
\r
2712 DisplayError(_("Error gathering move list: nested"), 0);
\r
2713 ics_getting_history = H_FALSE;
\r
2715 case H_GOT_REQ_HEADER:
\r
2716 ics_getting_history = H_GETTING_MOVES;
\r
2717 started = STARTED_MOVES;
\r
2719 if (oldi > next_out) {
\r
2720 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2723 case H_GOT_UNREQ_HEADER:
\r
2724 ics_getting_history = H_GETTING_MOVES;
\r
2725 started = STARTED_MOVES_NOHIDE;
\r
2728 case H_GOT_UNWANTED_HEADER:
\r
2729 ics_getting_history = H_FALSE;
\r
2735 if (looking_at(buf, &i, "% ") ||
\r
2736 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2737 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2738 savingComment = FALSE;
\r
2739 switch (started) {
\r
2740 case STARTED_MOVES:
\r
2741 case STARTED_MOVES_NOHIDE:
\r
2742 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2743 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2744 ParseGameHistory(parse);
\r
2746 if (appData.zippyPlay && first.initDone) {
\r
2747 FeedMovesToProgram(&first, forwardMostMove);
\r
2748 if (gameMode == IcsPlayingWhite) {
\r
2749 if (WhiteOnMove(forwardMostMove)) {
\r
2750 if (first.sendTime) {
\r
2751 if (first.useColors) {
\r
2752 SendToProgram("black\n", &first);
\r
2754 SendTimeRemaining(&first, TRUE);
\r
2757 if (first.useColors) {
\r
2758 SendToProgram("white\ngo\n", &first);
\r
2760 SendToProgram("go\n", &first);
\r
2763 if (first.useColors) {
\r
2764 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2766 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2768 first.maybeThinking = TRUE;
\r
2770 if (first.usePlayother) {
\r
2771 if (first.sendTime) {
\r
2772 SendTimeRemaining(&first, TRUE);
\r
2774 SendToProgram("playother\n", &first);
\r
2775 firstMove = FALSE;
\r
2780 } else if (gameMode == IcsPlayingBlack) {
\r
2781 if (!WhiteOnMove(forwardMostMove)) {
\r
2782 if (first.sendTime) {
\r
2783 if (first.useColors) {
\r
2784 SendToProgram("white\n", &first);
\r
2786 SendTimeRemaining(&first, FALSE);
\r
2789 if (first.useColors) {
\r
2790 SendToProgram("black\ngo\n", &first);
\r
2792 SendToProgram("go\n", &first);
\r
2795 if (first.useColors) {
\r
2796 SendToProgram("black\n", &first);
\r
2798 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2800 first.maybeThinking = TRUE;
\r
2802 if (first.usePlayother) {
\r
2803 if (first.sendTime) {
\r
2804 SendTimeRemaining(&first, FALSE);
\r
2806 SendToProgram("playother\n", &first);
\r
2807 firstMove = FALSE;
\r
2815 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2816 /* Moves came from oldmoves or moves command
\r
2817 while we weren't doing anything else.
\r
2819 currentMove = forwardMostMove;
\r
2820 ClearHighlights();/*!!could figure this out*/
\r
2821 flipView = appData.flipView;
\r
2822 DrawPosition(FALSE, boards[currentMove]);
\r
2823 DisplayBothClocks();
\r
2824 sprintf(str, "%s vs. %s",
\r
2825 gameInfo.white, gameInfo.black);
\r
2826 DisplayTitle(str);
\r
2827 gameMode = IcsIdle;
\r
2829 /* Moves were history of an active game */
\r
2830 if (gameInfo.resultDetails != NULL) {
\r
2831 free(gameInfo.resultDetails);
\r
2832 gameInfo.resultDetails = NULL;
\r
2835 HistorySet(parseList, backwardMostMove,
\r
2836 forwardMostMove, currentMove-1);
\r
2837 DisplayMove(currentMove - 1);
\r
2838 if (started == STARTED_MOVES) next_out = i;
\r
2839 started = STARTED_NONE;
\r
2840 ics_getting_history = H_FALSE;
\r
2843 case STARTED_OBSERVE:
\r
2844 started = STARTED_NONE;
\r
2845 SendToICS(ics_prefix);
\r
2846 SendToICS("refresh\n");
\r
2852 if(bookHit) { // [HGM] book: simulate book reply
\r
2853 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2855 programStats.depth = programStats.nodes = programStats.time =
\r
2856 programStats.score = programStats.got_only_move = 0;
\r
2857 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2859 strcpy(bookMove, "move ");
\r
2860 strcat(bookMove, bookHit);
\r
2861 HandleMachineMove(bookMove, &first);
\r
2866 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2867 started == STARTED_HOLDINGS ||
\r
2868 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2869 /* Accumulate characters in move list or board */
\r
2870 parse[parse_pos++] = buf[i];
\r
2873 /* Start of game messages. Mostly we detect start of game
\r
2874 when the first board image arrives. On some versions
\r
2875 of the ICS, though, we need to do a "refresh" after starting
\r
2876 to observe in order to get the current board right away. */
\r
2877 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2878 started = STARTED_OBSERVE;
\r
2882 /* Handle auto-observe */
\r
2883 if (appData.autoObserve &&
\r
2884 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2885 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2887 /* Choose the player that was highlighted, if any. */
\r
2888 if (star_match[0][0] == '\033' ||
\r
2889 star_match[1][0] != '\033') {
\r
2890 player = star_match[0];
\r
2892 player = star_match[2];
\r
2894 sprintf(str, "%sobserve %s\n",
\r
2895 ics_prefix, StripHighlightAndTitle(player));
\r
2898 /* Save ratings from notify string */
\r
2899 strcpy(player1Name, star_match[0]);
\r
2900 player1Rating = string_to_rating(star_match[1]);
\r
2901 strcpy(player2Name, star_match[2]);
\r
2902 player2Rating = string_to_rating(star_match[3]);
\r
2904 if (appData.debugMode)
\r
2906 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2907 player1Name, player1Rating,
\r
2908 player2Name, player2Rating);
\r
2913 /* Deal with automatic examine mode after a game,
\r
2914 and with IcsObserving -> IcsExamining transition */
\r
2915 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2916 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2918 int gamenum = atoi(star_match[0]);
\r
2919 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2920 gamenum == ics_gamenum) {
\r
2921 /* We were already playing or observing this game;
\r
2922 no need to refetch history */
\r
2923 gameMode = IcsExamining;
\r
2925 pauseExamForwardMostMove = forwardMostMove;
\r
2926 } else if (currentMove < forwardMostMove) {
\r
2927 ForwardInner(forwardMostMove);
\r
2930 /* I don't think this case really can happen */
\r
2931 SendToICS(ics_prefix);
\r
2932 SendToICS("refresh\n");
\r
2937 /* Error messages */
\r
2938 if (ics_user_moved) {
\r
2939 if (looking_at(buf, &i, "Illegal move") ||
\r
2940 looking_at(buf, &i, "Not a legal move") ||
\r
2941 looking_at(buf, &i, "Your king is in check") ||
\r
2942 looking_at(buf, &i, "It isn't your turn") ||
\r
2943 looking_at(buf, &i, "It is not your move")) {
\r
2944 /* Illegal move */
\r
2945 ics_user_moved = 0;
\r
2946 if (forwardMostMove > backwardMostMove) {
\r
2947 currentMove = --forwardMostMove;
\r
2948 DisplayMove(currentMove - 1); /* before DMError */
\r
2949 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2950 DrawPosition(FALSE, boards[currentMove]);
\r
2952 DisplayBothClocks();
\r
2958 if (looking_at(buf, &i, "still have time") ||
\r
2959 looking_at(buf, &i, "not out of time") ||
\r
2960 looking_at(buf, &i, "either player is out of time") ||
\r
2961 looking_at(buf, &i, "has timeseal; checking")) {
\r
2962 /* We must have called his flag a little too soon */
\r
2963 whiteFlag = blackFlag = FALSE;
\r
2967 if (looking_at(buf, &i, "added * seconds to") ||
\r
2968 looking_at(buf, &i, "seconds were added to")) {
\r
2969 /* Update the clocks */
\r
2970 SendToICS(ics_prefix);
\r
2971 SendToICS("refresh\n");
\r
2975 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2976 ics_clock_paused = TRUE;
\r
2981 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2982 ics_clock_paused = FALSE;
\r
2987 /* Grab player ratings from the Creating: message.
\r
2988 Note we have to check for the special case when
\r
2989 the ICS inserts things like [white] or [black]. */
\r
2990 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2991 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2993 0 player 1 name (not necessarily white)
\r
2995 2 empty, white, or black (IGNORED)
\r
2996 3 player 2 name (not necessarily black)
\r
2999 The names/ratings are sorted out when the game
\r
3000 actually starts (below).
\r
3002 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3003 player1Rating = string_to_rating(star_match[1]);
\r
3004 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3005 player2Rating = string_to_rating(star_match[4]);
\r
3007 if (appData.debugMode)
\r
3009 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3010 player1Name, player1Rating,
\r
3011 player2Name, player2Rating);
\r
3016 /* Improved generic start/end-of-game messages */
\r
3017 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3018 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3019 /* If tkind == 0: */
\r
3020 /* star_match[0] is the game number */
\r
3021 /* [1] is the white player's name */
\r
3022 /* [2] is the black player's name */
\r
3023 /* For end-of-game: */
\r
3024 /* [3] is the reason for the game end */
\r
3025 /* [4] is a PGN end game-token, preceded by " " */
\r
3026 /* For start-of-game: */
\r
3027 /* [3] begins with "Creating" or "Continuing" */
\r
3028 /* [4] is " *" or empty (don't care). */
\r
3029 int gamenum = atoi(star_match[0]);
\r
3030 char *whitename, *blackname, *why, *endtoken;
\r
3031 ChessMove endtype = (ChessMove) 0;
\r
3034 whitename = star_match[1];
\r
3035 blackname = star_match[2];
\r
3036 why = star_match[3];
\r
3037 endtoken = star_match[4];
\r
3039 whitename = star_match[1];
\r
3040 blackname = star_match[3];
\r
3041 why = star_match[5];
\r
3042 endtoken = star_match[6];
\r
3045 /* Game start messages */
\r
3046 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3047 strncmp(why, "Continuing ", 11) == 0) {
\r
3048 gs_gamenum = gamenum;
\r
3049 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3051 if (appData.zippyPlay) {
\r
3052 ZippyGameStart(whitename, blackname);
\r
3058 /* Game end messages */
\r
3059 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3060 ics_gamenum != gamenum) {
\r
3063 while (endtoken[0] == ' ') endtoken++;
\r
3064 switch (endtoken[0]) {
\r
3067 endtype = GameUnfinished;
\r
3070 endtype = BlackWins;
\r
3073 if (endtoken[1] == '/')
\r
3074 endtype = GameIsDrawn;
\r
3076 endtype = WhiteWins;
\r
3079 GameEnds(endtype, why, GE_ICS);
\r
3081 if (appData.zippyPlay && first.initDone) {
\r
3082 ZippyGameEnd(endtype, why);
\r
3083 if (first.pr == NULL) {
\r
3084 /* Start the next process early so that we'll
\r
3085 be ready for the next challenge */
\r
3086 StartChessProgram(&first);
\r
3088 /* Send "new" early, in case this command takes
\r
3089 a long time to finish, so that we'll be ready
\r
3090 for the next challenge. */
\r
3091 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3092 Reset(TRUE, TRUE);
\r
3098 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3099 looking_at(buf, &i, "no longer observing game *") ||
\r
3100 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3101 if (gameMode == IcsObserving &&
\r
3102 atoi(star_match[0]) == ics_gamenum)
\r
3104 /* icsEngineAnalyze */
\r
3105 if (appData.icsEngineAnalyze) {
\r
3106 ExitAnalyzeMode();
\r
3110 gameMode = IcsIdle;
\r
3112 ics_user_moved = FALSE;
\r
3117 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3118 if (gameMode == IcsExamining &&
\r
3119 atoi(star_match[0]) == ics_gamenum)
\r
3121 gameMode = IcsIdle;
\r
3123 ics_user_moved = FALSE;
\r
3128 /* Advance leftover_start past any newlines we find,
\r
3129 so only partial lines can get reparsed */
\r
3130 if (looking_at(buf, &i, "\n")) {
\r
3131 prevColor = curColor;
\r
3132 if (curColor != ColorNormal) {
\r
3133 if (oldi > next_out) {
\r
3134 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3137 Colorize(ColorNormal, FALSE);
\r
3138 curColor = ColorNormal;
\r
3140 if (started == STARTED_BOARD) {
\r
3141 started = STARTED_NONE;
\r
3142 parse[parse_pos] = NULLCHAR;
\r
3143 ParseBoard12(parse);
\r
3144 ics_user_moved = 0;
\r
3146 /* Send premove here */
\r
3147 if (appData.premove) {
\r
3148 char str[MSG_SIZ];
\r
3149 if (currentMove == 0 &&
\r
3150 gameMode == IcsPlayingWhite &&
\r
3151 appData.premoveWhite) {
\r
3152 sprintf(str, "%s%s\n", ics_prefix,
\r
3153 appData.premoveWhiteText);
\r
3154 if (appData.debugMode)
\r
3155 fprintf(debugFP, "Sending premove:\n");
\r
3157 } else if (currentMove == 1 &&
\r
3158 gameMode == IcsPlayingBlack &&
\r
3159 appData.premoveBlack) {
\r
3160 sprintf(str, "%s%s\n", ics_prefix,
\r
3161 appData.premoveBlackText);
\r
3162 if (appData.debugMode)
\r
3163 fprintf(debugFP, "Sending premove:\n");
\r
3165 } else if (gotPremove) {
\r
3167 ClearPremoveHighlights();
\r
3168 if (appData.debugMode)
\r
3169 fprintf(debugFP, "Sending premove:\n");
\r
3170 UserMoveEvent(premoveFromX, premoveFromY,
\r
3171 premoveToX, premoveToY,
\r
3172 premovePromoChar);
\r
3176 /* Usually suppress following prompt */
\r
3177 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3178 if (looking_at(buf, &i, "*% ")) {
\r
3179 savingComment = FALSE;
\r
3183 } else if (started == STARTED_HOLDINGS) {
\r
3185 char new_piece[MSG_SIZ];
\r
3186 started = STARTED_NONE;
\r
3187 parse[parse_pos] = NULLCHAR;
\r
3188 if (appData.debugMode)
\r
3189 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3190 parse, currentMove);
\r
3191 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3192 gamenum == ics_gamenum) {
\r
3193 if (gameInfo.variant == VariantNormal) {
\r
3194 /* [HGM] We seem to switch variant during a game!
\r
3195 * Presumably no holdings were displayed, so we have
\r
3196 * to move the position two files to the right to
\r
3197 * create room for them!
\r
3199 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3200 /* Get a move list just to see the header, which
\r
3201 will tell us whether this is really bug or zh */
\r
3202 if (ics_getting_history == H_FALSE) {
\r
3203 ics_getting_history = H_REQUESTED;
\r
3204 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3208 new_piece[0] = NULLCHAR;
\r
3209 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3210 &gamenum, white_holding, black_holding,
\r
3212 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3213 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3214 /* [HGM] copy holdings to board holdings area */
\r
3215 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3216 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3218 if (appData.zippyPlay && first.initDone) {
\r
3219 ZippyHoldings(white_holding, black_holding,
\r
3223 if (tinyLayout || smallLayout) {
\r
3224 char wh[16], bh[16];
\r
3225 PackHolding(wh, white_holding);
\r
3226 PackHolding(bh, black_holding);
\r
3227 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3228 gameInfo.white, gameInfo.black);
\r
3230 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3231 gameInfo.white, white_holding,
\r
3232 gameInfo.black, black_holding);
\r
3235 DrawPosition(FALSE, boards[currentMove]);
\r
3236 DisplayTitle(str);
\r
3238 /* Suppress following prompt */
\r
3239 if (looking_at(buf, &i, "*% ")) {
\r
3240 savingComment = FALSE;
\r
3247 i++; /* skip unparsed character and loop back */
\r
3250 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3251 started != STARTED_HOLDINGS && i > next_out) {
\r
3252 SendToPlayer(&buf[next_out], i - next_out);
\r
3255 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3257 leftover_len = buf_len - leftover_start;
\r
3258 /* if buffer ends with something we couldn't parse,
\r
3259 reparse it after appending the next read */
\r
3261 } else if (count == 0) {
\r
3262 RemoveInputSource(isr);
\r
3263 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3265 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3270 /* Board style 12 looks like this:
\r
3272 <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
3274 * The "<12> " is stripped before it gets to this routine. The two
\r
3275 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3276 * some chess servers may not have them, or may have only the first.
\r
3277 * Additional trailing fields may be added in the future.
\r
3280 #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
3282 #define RELATION_OBSERVING_PLAYED 0
\r
3283 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3284 #define RELATION_PLAYING_MYMOVE 1
\r
3285 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3286 #define RELATION_EXAMINING 2
\r
3287 #define RELATION_ISOLATED_BOARD -3
\r
3288 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3291 ParseBoard12(string)
\r
3294 GameMode newGameMode;
\r
3295 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3296 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3297 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3298 char to_play, board_chars[200];
\r
3299 char move_str[500], str[500], elapsed_time[500];
\r
3300 char black[32], white[32];
\r
3302 int prevMove = currentMove;
\r
3304 ChessMove moveType;
\r
3305 int fromX, fromY, toX, toY;
\r
3307 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3308 char *bookHit = NULL; // [HGM] book
\r
3310 fromX = fromY = toX = toY = -1;
\r
3314 if (appData.debugMode)
\r
3315 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3317 move_str[0] = NULLCHAR;
\r
3318 elapsed_time[0] = NULLCHAR;
\r
3319 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3321 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3322 if(string[i] == ' ') { ranks++; files = 0; }
\r
3326 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3327 board_chars[i] = '\0';
\r
3330 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3331 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3332 &gamenum, white, black, &relation, &basetime, &increment,
\r
3333 &white_stren, &black_stren, &white_time, &black_time,
\r
3334 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3338 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3339 DisplayError(str, 0);
\r
3343 /* Convert the move number to internal form */
\r
3344 moveNum = (moveNum - 1) * 2;
\r
3345 if (to_play == 'B') moveNum++;
\r
3346 if (moveNum >= MAX_MOVES) {
\r
3347 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3352 switch (relation) {
\r
3353 case RELATION_OBSERVING_PLAYED:
\r
3354 case RELATION_OBSERVING_STATIC:
\r
3355 if (gamenum == -1) {
\r
3356 /* Old ICC buglet */
\r
3357 relation = RELATION_OBSERVING_STATIC;
\r
3359 newGameMode = IcsObserving;
\r
3361 case RELATION_PLAYING_MYMOVE:
\r
3362 case RELATION_PLAYING_NOTMYMOVE:
\r
3364 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3365 IcsPlayingWhite : IcsPlayingBlack;
\r
3367 case RELATION_EXAMINING:
\r
3368 newGameMode = IcsExamining;
\r
3370 case RELATION_ISOLATED_BOARD:
\r
3372 /* Just display this board. If user was doing something else,
\r
3373 we will forget about it until the next board comes. */
\r
3374 newGameMode = IcsIdle;
\r
3376 case RELATION_STARTING_POSITION:
\r
3377 newGameMode = gameMode;
\r
3381 /* Modify behavior for initial board display on move listing
\r
3384 switch (ics_getting_history) {
\r
3388 case H_GOT_REQ_HEADER:
\r
3389 case H_GOT_UNREQ_HEADER:
\r
3390 /* This is the initial position of the current game */
\r
3391 gamenum = ics_gamenum;
\r
3392 moveNum = 0; /* old ICS bug workaround */
\r
3393 if (to_play == 'B') {
\r
3394 startedFromSetupPosition = TRUE;
\r
3395 blackPlaysFirst = TRUE;
\r
3397 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3398 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3399 if (currentMove == 0) currentMove = 1;
\r
3401 newGameMode = gameMode;
\r
3402 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3404 case H_GOT_UNWANTED_HEADER:
\r
3405 /* This is an initial board that we don't want */
\r
3407 case H_GETTING_MOVES:
\r
3408 /* Should not happen */
\r
3409 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3410 ics_getting_history = H_FALSE;
\r
3414 /* Take action if this is the first board of a new game, or of a
\r
3415 different game than is currently being displayed. */
\r
3416 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3417 relation == RELATION_ISOLATED_BOARD) {
\r
3419 /* Forget the old game and get the history (if any) of the new one */
\r
3420 if (gameMode != BeginningOfGame) {
\r
3421 Reset(FALSE, TRUE);
\r
3424 if (appData.autoRaiseBoard) BoardToTop();
\r
3426 if (gamenum == -1) {
\r
3427 newGameMode = IcsIdle;
\r
3428 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3429 appData.getMoveList) {
\r
3430 /* Need to get game history */
\r
3431 ics_getting_history = H_REQUESTED;
\r
3432 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3436 /* Initially flip the board to have black on the bottom if playing
\r
3437 black or if the ICS flip flag is set, but let the user change
\r
3438 it with the Flip View button. */
\r
3439 flipView = appData.autoFlipView ?
\r
3440 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3443 /* Done with values from previous mode; copy in new ones */
\r
3444 gameMode = newGameMode;
\r
3446 ics_gamenum = gamenum;
\r
3447 if (gamenum == gs_gamenum) {
\r
3448 int klen = strlen(gs_kind);
\r
3449 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3450 sprintf(str, "ICS %s", gs_kind);
\r
3451 gameInfo.event = StrSave(str);
\r
3453 gameInfo.event = StrSave("ICS game");
\r
3455 gameInfo.site = StrSave(appData.icsHost);
\r
3456 gameInfo.date = PGNDate();
\r
3457 gameInfo.round = StrSave("-");
\r
3458 gameInfo.white = StrSave(white);
\r
3459 gameInfo.black = StrSave(black);
\r
3460 timeControl = basetime * 60 * 1000;
\r
3461 timeControl_2 = 0;
\r
3462 timeIncrement = increment * 1000;
\r
3463 movesPerSession = 0;
\r
3464 gameInfo.timeControl = TimeControlTagValue();
\r
3465 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3466 if (appData.debugMode) {
\r
3467 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3468 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3469 setbuf(debugFP, NULL);
\r
3472 gameInfo.outOfBook = NULL;
\r
3474 /* Do we have the ratings? */
\r
3475 if (strcmp(player1Name, white) == 0 &&
\r
3476 strcmp(player2Name, black) == 0) {
\r
3477 if (appData.debugMode)
\r
3478 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3479 player1Rating, player2Rating);
\r
3480 gameInfo.whiteRating = player1Rating;
\r
3481 gameInfo.blackRating = player2Rating;
\r
3482 } else if (strcmp(player2Name, white) == 0 &&
\r
3483 strcmp(player1Name, black) == 0) {
\r
3484 if (appData.debugMode)
\r
3485 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3486 player2Rating, player1Rating);
\r
3487 gameInfo.whiteRating = player2Rating;
\r
3488 gameInfo.blackRating = player1Rating;
\r
3490 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3492 /* Silence shouts if requested */
\r
3493 if (appData.quietPlay &&
\r
3494 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3495 SendToICS(ics_prefix);
\r
3496 SendToICS("set shout 0\n");
\r
3500 /* Deal with midgame name changes */
\r
3502 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3503 if (gameInfo.white) free(gameInfo.white);
\r
3504 gameInfo.white = StrSave(white);
\r
3506 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3507 if (gameInfo.black) free(gameInfo.black);
\r
3508 gameInfo.black = StrSave(black);
\r
3512 /* Throw away game result if anything actually changes in examine mode */
\r
3513 if (gameMode == IcsExamining && !newGame) {
\r
3514 gameInfo.result = GameUnfinished;
\r
3515 if (gameInfo.resultDetails != NULL) {
\r
3516 free(gameInfo.resultDetails);
\r
3517 gameInfo.resultDetails = NULL;
\r
3521 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3522 in if they are in a different variation than we are. */
\r
3523 if (pauseExamInvalid) return;
\r
3524 if (pausing && gameMode == IcsExamining) {
\r
3525 if (moveNum <= pauseExamForwardMostMove) {
\r
3526 pauseExamInvalid = TRUE;
\r
3527 forwardMostMove = pauseExamForwardMostMove;
\r
3532 if (appData.debugMode) {
\r
3533 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3535 /* Parse the board */
\r
3536 for (k = 0; k < ranks; k++) {
\r
3537 for (j = 0; j < files; j++)
\r
3538 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3539 if(gameInfo.holdingsWidth > 1) {
\r
3540 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3541 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3544 CopyBoard(boards[moveNum], board);
\r
3545 if (moveNum == 0) {
\r
3546 startedFromSetupPosition =
\r
3547 !CompareBoards(board, initialPosition);
\r
3548 if(startedFromSetupPosition)
\r
3549 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3552 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3553 to make it also work for FRC opening positions. Note that board12
\r
3554 is really defective for later FRC positions, as it has no way to
\r
3555 indicate which Rook can castle if they are on the same side of King.
\r
3556 For the initial position we grant rights to the outermost Rooks,
\r
3557 and remember thos rights, and we then copy them on positions
\r
3558 later in an FRC game. This means WB might not recognize castlings with
\r
3559 Rooks that have moved back to their original position as illegal,
\r
3560 but in ICS mode that is not its job anyway.
\r
3562 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3563 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3565 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3566 if(board[0][i] == WhiteRook) j = i;
\r
3567 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3568 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3569 if(board[0][i] == WhiteRook) j = i;
\r
3570 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3571 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3572 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3573 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3574 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3575 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3576 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3578 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3579 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3580 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3581 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3582 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3583 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3585 r = castlingRights[moveNum][0] = initialRights[0];
\r
3586 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3587 r = castlingRights[moveNum][1] = initialRights[1];
\r
3588 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3589 r = castlingRights[moveNum][3] = initialRights[3];
\r
3590 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3591 r = castlingRights[moveNum][4] = initialRights[4];
\r
3592 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3593 /* wildcastle kludge: always assume King has rights */
\r
3594 r = castlingRights[moveNum][2] = initialRights[2];
\r
3595 r = castlingRights[moveNum][5] = initialRights[5];
\r
3597 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3598 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3601 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3602 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3603 /* This was an initial position from a move list, not
\r
3604 the current position */
\r
3608 /* Update currentMove and known move number limits */
\r
3609 newMove = newGame || moveNum > forwardMostMove;
\r
3611 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3612 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3613 takeback = forwardMostMove - moveNum;
\r
3614 for (i = 0; i < takeback; i++) {
\r
3615 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3616 SendToProgram("undo\n", &first);
\r
3621 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3622 if (gameMode == IcsExamining && moveNum == 0) {
\r
3623 /* Workaround for ICS limitation: we are not told the wild
\r
3624 type when starting to examine a game. But if we ask for
\r
3625 the move list, the move list header will tell us */
\r
3626 ics_getting_history = H_REQUESTED;
\r
3627 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3630 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3631 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3632 forwardMostMove = moveNum;
\r
3633 if (!pausing || currentMove > forwardMostMove)
\r
3634 currentMove = forwardMostMove;
\r
3636 /* New part of history that is not contiguous with old part */
\r
3637 if (pausing && gameMode == IcsExamining) {
\r
3638 pauseExamInvalid = TRUE;
\r
3639 forwardMostMove = pauseExamForwardMostMove;
\r
3642 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3643 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3644 ics_getting_history = H_REQUESTED;
\r
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3650 /* Update the clocks */
\r
3651 if (strchr(elapsed_time, '.')) {
\r
3652 /* Time is in ms */
\r
3653 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3654 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3656 /* Time is in seconds */
\r
3657 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3658 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3663 if (appData.zippyPlay && newGame &&
\r
3664 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3665 gameMode != IcsExamining)
\r
3666 ZippyFirstBoard(moveNum, basetime, increment);
\r
3669 /* Put the move on the move list, first converting
\r
3670 to canonical algebraic form. */
\r
3671 if (moveNum > 0) {
\r
3672 if (appData.debugMode) {
\r
3673 if (appData.debugMode) { int f = forwardMostMove;
\r
3674 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3675 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3677 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3678 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3679 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3680 setbuf(debugFP, NULL);
\r
3682 if (moveNum <= backwardMostMove) {
\r
3683 /* We don't know what the board looked like before
\r
3684 this move. Punt. */
\r
3685 strcpy(parseList[moveNum - 1], move_str);
\r
3686 strcat(parseList[moveNum - 1], " ");
\r
3687 strcat(parseList[moveNum - 1], elapsed_time);
\r
3688 moveList[moveNum - 1][0] = NULLCHAR;
\r
3689 } else if (strcmp(move_str, "none") == 0) {
\r
3690 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3691 /* Again, we don't know what the board looked like;
\r
3692 this is really the start of the game. */
\r
3693 parseList[moveNum - 1][0] = NULLCHAR;
\r
3694 moveList[moveNum - 1][0] = NULLCHAR;
\r
3695 backwardMostMove = moveNum;
\r
3696 startedFromSetupPosition = TRUE;
\r
3697 fromX = fromY = toX = toY = -1;
\r
3699 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3700 // So we parse the long-algebraic move string in stead of the SAN move
\r
3701 int valid; char buf[MSG_SIZ], *prom;
\r
3703 // str looks something like "Q/a1-a2"; kill the slash
\r
3704 if(str[1] == '/')
\r
3705 sprintf(buf, "%c%s", str[0], str+2);
\r
3706 else strcpy(buf, str); // might be castling
\r
3707 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3708 strcat(buf, prom); // long move lacks promo specification!
\r
3709 if(!appData.testLegality) {
\r
3710 if(appData.debugMode)
\r
3711 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3712 strcpy(move_str, buf);
\r
3714 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3715 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3716 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3717 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3718 // end of long SAN patch
\r
3720 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3721 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3722 fromY, fromX, toY, toX, promoChar,
\r
3723 parseList[moveNum-1]);
\r
3724 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3725 castlingRights[moveNum]) ) {
\r
3727 case MT_STALEMATE:
\r
3731 if(gameInfo.variant != VariantShogi)
\r
3732 strcat(parseList[moveNum - 1], "+");
\r
3734 case MT_CHECKMATE:
\r
3735 strcat(parseList[moveNum - 1], "#");
\r
3738 strcat(parseList[moveNum - 1], " ");
\r
3739 strcat(parseList[moveNum - 1], elapsed_time);
\r
3740 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3741 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3742 strcat(moveList[moveNum - 1], "\n");
\r
3744 /* Move from ICS was illegal!? Punt. */
\r
3745 if (appData.debugMode) {
\r
3746 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3747 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3750 if (appData.testLegality && appData.debugMode) {
\r
3751 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3752 DisplayError(str, 0);
\r
3755 strcpy(parseList[moveNum - 1], move_str);
\r
3756 strcat(parseList[moveNum - 1], " ");
\r
3757 strcat(parseList[moveNum - 1], elapsed_time);
\r
3758 moveList[moveNum - 1][0] = NULLCHAR;
\r
3759 fromX = fromY = toX = toY = -1;
\r
3762 if (appData.debugMode) {
\r
3763 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3764 setbuf(debugFP, NULL);
\r
3768 /* Send move to chess program (BEFORE animating it). */
\r
3769 if (appData.zippyPlay && !newGame && newMove &&
\r
3770 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3772 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3773 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3774 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3775 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3777 DisplayError(str, 0);
\r
3779 if (first.sendTime) {
\r
3780 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3782 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3783 if (firstMove && !bookHit) {
\r
3784 firstMove = FALSE;
\r
3785 if (first.useColors) {
\r
3786 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3788 "black\ngo\n", &first);
\r
3790 SendToProgram("go\n", &first);
\r
3792 first.maybeThinking = TRUE;
\r
3795 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3796 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3797 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3798 DisplayError(str, 0);
\r
3800 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3801 SendMoveToProgram(moveNum - 1, &first);
\r
3808 if (moveNum > 0 && !gotPremove) {
\r
3809 /* If move comes from a remote source, animate it. If it
\r
3810 isn't remote, it will have already been animated. */
\r
3811 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3812 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3814 if (!pausing && appData.highlightLastMove) {
\r
3815 SetHighlights(fromX, fromY, toX, toY);
\r
3819 /* Start the clocks */
\r
3820 whiteFlag = blackFlag = FALSE;
\r
3821 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3822 if (ticking == 0) {
\r
3823 ics_clock_paused = TRUE;
\r
3825 } else if (ticking == 1) {
\r
3826 ics_clock_paused = FALSE;
\r
3828 if (gameMode == IcsIdle ||
\r
3829 relation == RELATION_OBSERVING_STATIC ||
\r
3830 relation == RELATION_EXAMINING ||
\r
3832 DisplayBothClocks();
\r
3836 /* Display opponents and material strengths */
\r
3837 if (gameInfo.variant != VariantBughouse &&
\r
3838 gameInfo.variant != VariantCrazyhouse) {
\r
3839 if (tinyLayout || smallLayout) {
\r
3840 if(gameInfo.variant == VariantNormal)
\r
3841 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3842 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3843 basetime, increment);
\r
3845 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3846 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3847 basetime, increment, (int) gameInfo.variant);
\r
3849 if(gameInfo.variant == VariantNormal)
\r
3850 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3851 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3852 basetime, increment);
\r
3854 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3855 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3856 basetime, increment, VariantName(gameInfo.variant));
\r
3858 DisplayTitle(str);
\r
3859 if (appData.debugMode) {
\r
3860 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3865 /* Display the board */
\r
3868 if (appData.premove)
\r
3869 if (!gotPremove ||
\r
3870 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3871 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3872 ClearPremoveHighlights();
\r
3874 DrawPosition(FALSE, boards[currentMove]);
\r
3875 DisplayMove(moveNum - 1);
\r
3876 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3882 if(bookHit) { // [HGM] book: simulate book reply
\r
3883 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3885 programStats.depth = programStats.nodes = programStats.time =
\r
3886 programStats.score = programStats.got_only_move = 0;
\r
3887 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3889 strcpy(bookMove, "move ");
\r
3890 strcat(bookMove, bookHit);
\r
3891 HandleMachineMove(bookMove, &first);
\r
3897 GetMoveListEvent()
\r
3899 char buf[MSG_SIZ];
\r
3900 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3901 ics_getting_history = H_REQUESTED;
\r
3902 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3908 AnalysisPeriodicEvent(force)
\r
3911 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3912 && !force) || !appData.periodicUpdates)
\r
3915 /* Send . command to Crafty to collect stats */
\r
3916 SendToProgram(".\n", &first);
\r
3918 /* Don't send another until we get a response (this makes
\r
3919 us stop sending to old Crafty's which don't understand
\r
3920 the "." command (sending illegal cmds resets node count & time,
\r
3921 which looks bad)) */
\r
3922 programStats.ok_to_send = 0;
\r
3926 SendMoveToProgram(moveNum, cps)
\r
3928 ChessProgramState *cps;
\r
3930 char buf[MSG_SIZ];
\r
3932 if (cps->useUsermove) {
\r
3933 SendToProgram("usermove ", cps);
\r
3935 if (cps->useSAN) {
\r
3937 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3938 int len = space - parseList[moveNum];
\r
3939 memcpy(buf, parseList[moveNum], len);
\r
3940 buf[len++] = '\n';
\r
3941 buf[len] = NULLCHAR;
\r
3943 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3945 SendToProgram(buf, cps);
\r
3947 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3948 AlphaRank(moveList[moveNum], 4);
\r
3949 SendToProgram(moveList[moveNum], cps);
\r
3950 AlphaRank(moveList[moveNum], 4); // and back
\r
3952 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3953 * the engine. It would be nice to have a better way to identify castle
\r
3955 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3956 && cps->useOOCastle) {
\r
3957 int fromX = moveList[moveNum][0] - AAA;
\r
3958 int fromY = moveList[moveNum][1] - ONE;
\r
3959 int toX = moveList[moveNum][2] - AAA;
\r
3960 int toY = moveList[moveNum][3] - ONE;
\r
3961 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3962 && boards[moveNum][toY][toX] == WhiteRook)
\r
3963 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3964 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3965 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3966 else SendToProgram("O-O-O\n", cps);
\r
3968 else SendToProgram(moveList[moveNum], cps);
\r
3970 else SendToProgram(moveList[moveNum], cps);
\r
3971 /* End of additions by Tord */
\r
3974 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3975 /* Send 'go' if we are in a mode where machine should play. */
\r
3976 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3977 (gameMode == TwoMachinesPlay ||
\r
3979 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3981 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3982 SendToProgram("go\n", cps);
\r
3983 if (appData.debugMode) {
\r
3984 fprintf(debugFP, "(extra)\n");
\r
3987 setboardSpoiledMachineBlack = 0;
\r
3991 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3992 ChessMove moveType;
\r
3993 int fromX, fromY, toX, toY;
\r
3995 char user_move[MSG_SIZ];
\r
3997 switch (moveType) {
\r
3999 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4000 (int)moveType, fromX, fromY, toX, toY);
\r
4001 DisplayError(user_move + strlen("say "), 0);
\r
4003 case WhiteKingSideCastle:
\r
4004 case BlackKingSideCastle:
\r
4005 case WhiteQueenSideCastleWild:
\r
4006 case BlackQueenSideCastleWild:
\r
4008 case WhiteHSideCastleFR:
\r
4009 case BlackHSideCastleFR:
\r
4011 sprintf(user_move, "o-o\n");
\r
4013 case WhiteQueenSideCastle:
\r
4014 case BlackQueenSideCastle:
\r
4015 case WhiteKingSideCastleWild:
\r
4016 case BlackKingSideCastleWild:
\r
4018 case WhiteASideCastleFR:
\r
4019 case BlackASideCastleFR:
\r
4021 sprintf(user_move, "o-o-o\n");
\r
4023 case WhitePromotionQueen:
\r
4024 case BlackPromotionQueen:
\r
4025 case WhitePromotionRook:
\r
4026 case BlackPromotionRook:
\r
4027 case WhitePromotionBishop:
\r
4028 case BlackPromotionBishop:
\r
4029 case WhitePromotionKnight:
\r
4030 case BlackPromotionKnight:
\r
4031 case WhitePromotionKing:
\r
4032 case BlackPromotionKing:
\r
4033 case WhitePromotionChancellor:
\r
4034 case BlackPromotionChancellor:
\r
4035 case WhitePromotionArchbishop:
\r
4036 case BlackPromotionArchbishop:
\r
4037 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4038 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4039 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4040 PieceToChar(WhiteFerz));
\r
4041 else if(gameInfo.variant == VariantGreat)
\r
4042 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4043 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4044 PieceToChar(WhiteMan));
\r
4046 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4047 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4048 PieceToChar(PromoPiece(moveType)));
\r
4052 sprintf(user_move, "%c@%c%c\n",
\r
4053 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4054 AAA + toX, ONE + toY);
\r
4057 case WhiteCapturesEnPassant:
\r
4058 case BlackCapturesEnPassant:
\r
4059 case IllegalMove: /* could be a variant we don't quite understand */
\r
4060 sprintf(user_move, "%c%c%c%c\n",
\r
4061 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4064 SendToICS(user_move);
\r
4068 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4069 int rf, ff, rt, ft;
\r
4073 if (rf == DROP_RANK) {
\r
4074 sprintf(move, "%c@%c%c\n",
\r
4075 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4077 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4078 sprintf(move, "%c%c%c%c\n",
\r
4079 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4081 sprintf(move, "%c%c%c%c%c\n",
\r
4082 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4088 ProcessICSInitScript(f)
\r
4091 char buf[MSG_SIZ];
\r
4093 while (fgets(buf, MSG_SIZ, f)) {
\r
4094 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4101 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4103 AlphaRank(char *move, int n)
\r
4105 char *p = move, c; int x, y;
\r
4107 if (appData.debugMode) {
\r
4108 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4111 if(move[1]=='*' &&
\r
4112 move[2]>='0' && move[2]<='9' &&
\r
4113 move[3]>='a' && move[3]<='x' ) {
\r
4115 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4116 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4118 if(move[0]>='0' && move[0]<='9' &&
\r
4119 move[1]>='a' && move[1]<='x' &&
\r
4120 move[2]>='0' && move[2]<='9' &&
\r
4121 move[3]>='a' && move[3]<='x' ) {
\r
4122 /* input move, Shogi -> normal */
\r
4123 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4124 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4125 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4126 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4128 if(move[1]=='@' &&
\r
4129 move[3]>='0' && move[3]<='9' &&
\r
4130 move[2]>='a' && move[2]<='x' ) {
\r
4132 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4133 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4136 move[0]>='a' && move[0]<='x' &&
\r
4137 move[3]>='0' && move[3]<='9' &&
\r
4138 move[2]>='a' && move[2]<='x' ) {
\r
4139 /* output move, normal -> Shogi */
\r
4140 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4141 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4142 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4143 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4144 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4146 if (appData.debugMode) {
\r
4147 fprintf(debugFP, " out = '%s'\n", move);
\r
4151 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4153 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4156 ChessMove *moveType;
\r
4157 int *fromX, *fromY, *toX, *toY;
\r
4160 if (appData.debugMode) {
\r
4161 fprintf(debugFP, "move to parse: %s\n", move);
\r
4163 *moveType = yylexstr(moveNum, move);
\r
4165 switch (*moveType) {
\r
4166 case WhitePromotionChancellor:
\r
4167 case BlackPromotionChancellor:
\r
4168 case WhitePromotionArchbishop:
\r
4169 case BlackPromotionArchbishop:
\r
4170 case WhitePromotionQueen:
\r
4171 case BlackPromotionQueen:
\r
4172 case WhitePromotionRook:
\r
4173 case BlackPromotionRook:
\r
4174 case WhitePromotionBishop:
\r
4175 case BlackPromotionBishop:
\r
4176 case WhitePromotionKnight:
\r
4177 case BlackPromotionKnight:
\r
4178 case WhitePromotionKing:
\r
4179 case BlackPromotionKing:
\r
4181 case WhiteCapturesEnPassant:
\r
4182 case BlackCapturesEnPassant:
\r
4183 case WhiteKingSideCastle:
\r
4184 case WhiteQueenSideCastle:
\r
4185 case BlackKingSideCastle:
\r
4186 case BlackQueenSideCastle:
\r
4187 case WhiteKingSideCastleWild:
\r
4188 case WhiteQueenSideCastleWild:
\r
4189 case BlackKingSideCastleWild:
\r
4190 case BlackQueenSideCastleWild:
\r
4191 /* Code added by Tord: */
\r
4192 case WhiteHSideCastleFR:
\r
4193 case WhiteASideCastleFR:
\r
4194 case BlackHSideCastleFR:
\r
4195 case BlackASideCastleFR:
\r
4196 /* End of code added by Tord */
\r
4197 case IllegalMove: /* bug or odd chess variant */
\r
4198 *fromX = currentMoveString[0] - AAA;
\r
4199 *fromY = currentMoveString[1] - ONE;
\r
4200 *toX = currentMoveString[2] - AAA;
\r
4201 *toY = currentMoveString[3] - ONE;
\r
4202 *promoChar = currentMoveString[4];
\r
4203 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4204 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4205 if (appData.debugMode) {
\r
4206 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4208 *fromX = *fromY = *toX = *toY = 0;
\r
4211 if (appData.testLegality) {
\r
4212 return (*moveType != IllegalMove);
\r
4214 return !(fromX == fromY && toX == toY);
\r
4219 *fromX = *moveType == WhiteDrop ?
\r
4220 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4221 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4222 *fromY = DROP_RANK;
\r
4223 *toX = currentMoveString[2] - AAA;
\r
4224 *toY = currentMoveString[3] - ONE;
\r
4225 *promoChar = NULLCHAR;
\r
4228 case AmbiguousMove:
\r
4229 case ImpossibleMove:
\r
4230 case (ChessMove) 0: /* end of file */
\r
4239 if (appData.debugMode) {
\r
4240 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4243 *fromX = *fromY = *toX = *toY = 0;
\r
4244 *promoChar = NULLCHAR;
\r
4249 /* [AS] FRC game initialization */
\r
4250 static int FindEmptySquare( Board board, int n )
\r
4255 while( board[0][i] != EmptySquare ) i++;
\r
4266 static void ShuffleFRC( Board board )
\r
4272 for( i=0; i<8; i++ ) {
\r
4273 board[0][i] = EmptySquare;
\r
4276 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4277 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4278 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4279 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4280 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4281 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4282 initialRights[1] = initialRights[4] =
\r
4283 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4284 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4285 initialRights[2] = initialRights[5] =
\r
4286 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4287 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4288 initialRights[0] = initialRights[3] =
\r
4289 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4291 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4292 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4296 static unsigned char FRC_KnightTable[10] = {
\r
4297 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4300 static void SetupFRC( Board board, int pos_index )
\r
4303 unsigned char knights;
\r
4305 /* Bring the position index into a safe range (just in case...) */
\r
4306 if( pos_index < 0 ) pos_index = 0;
\r
4310 /* Clear the board */
\r
4311 for( i=0; i<8; i++ ) {
\r
4312 board[0][i] = EmptySquare;
\r
4315 /* Place bishops and queen */
\r
4316 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4319 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4322 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4325 /* Place knigths */
\r
4326 knights = FRC_KnightTable[ pos_index ];
\r
4328 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4329 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4331 /* Place rooks and king */
\r
4332 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4333 initialRights[1] = initialRights[4] =
\r
4334 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4335 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4336 initialRights[2] = initialRights[5] =
\r
4337 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4338 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4339 initialRights[0] = initialRights[3] =
\r
4340 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4342 /* Mirror piece placement for black */
\r
4343 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4344 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4348 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4349 // All positions will have equal probability, but the current method will not provide a unique
\r
4350 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4355 int squaresLeft[4];
\r
4356 int piecesLeft[(int)BlackPawn];
\r
4357 long long int seed, nrOfShuffles;
\r
4359 void GetPositionNumber()
\r
4360 { // sets global variable seed
\r
4363 seed = appData.defaultFrcPosition;
\r
4364 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4365 srandom(time(0));
\r
4366 for(i=0; i<50; i++) seed += random();
\r
4367 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4368 if(seed<0) seed = -seed;
\r
4372 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4373 // put the piece on the (n-1)-th empty squares of the given shade
\r
4377 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4378 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4379 board[rank][i] = (ChessSquare) pieceType;
\r
4380 squaresLeft[(i-BOARD_LEFT&1) + 1]--;
\r
4381 squaresLeft[ANY]--;
\r
4382 piecesLeft[pieceType]--;
\r
4390 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4391 // calculate where the next piece goes, (any empty square), and put it there
\r
4395 i = seed % squaresLeft[shade];
\r
4396 nrOfShuffles *= squaresLeft[shade];
\r
4397 seed /= squaresLeft[shade];
\r
4398 put(board, pieceType, rank, i, shade);
\r
4401 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4402 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4404 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4406 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4407 i = seed % k; // pick one
\r
4408 nrOfShuffles *= k;
\r
4410 while(i >= j) i -= j--;
\r
4411 j = n - 1 - j; i += j;
\r
4412 put(board, pieceType, rank, j, ANY);
\r
4413 put(board, pieceType, rank, i, ANY);
\r
4416 void SetUpShuffle(Board board, int number)
\r
4418 int i, p, first=1;
\r
4420 GetPositionNumber(); nrOfShuffles = 1;
\r
4422 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4423 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4424 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4426 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4428 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4429 p = (int) board[0][i];
\r
4430 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4431 board[0][i] = EmptySquare;
\r
4434 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4435 // shuffles restricted to allow normal castling put KRR first
\r
4436 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4437 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4438 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4439 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4440 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4441 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4442 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4443 put(board, WhiteRook, 0, 0, ANY);
\r
4444 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4447 if((BOARD_RGHT-BOARD_LEFT & 1) == 0)
\r
4448 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4449 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4450 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4451 while(piecesLeft[p] >= 2) {
\r
4452 AddOnePiece(board, p, 0, LITE);
\r
4453 AddOnePiece(board, p, 0, DARK);
\r
4455 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4458 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4459 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4460 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4461 if(p == (int)WhiteRook) continue;
\r
4462 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4463 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4466 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4468 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4469 // Last King gets castling rights
\r
4470 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4471 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4472 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4475 while(piecesLeft[(int)WhiteKing]) {
\r
4476 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4477 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4482 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4483 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4486 // Only Rooks can be left; simply place them all
\r
4487 while(piecesLeft[(int)WhiteRook]) {
\r
4488 i = put(board, WhiteRook, 0, 0, ANY);
\r
4489 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4492 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4494 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4497 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4498 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4501 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4506 int SetCharTable( char *table, const char * map )
\r
4507 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4508 /* Basically a safe strcpy that uses the last character as King */
\r
4510 int result = FALSE; int NrPieces;
\r
4512 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4513 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4514 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4516 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4517 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4518 table[i] = map[i];
\r
4519 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4521 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4522 table[(int) BlackKing] = map[NrPieces-1];
\r
4530 void Prelude(Board board)
\r
4531 { // [HGM] superchess: random selection of exo-pieces
\r
4532 int i, j, k; ChessSquare p;
\r
4533 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4535 GetPositionNumber(); // use FRC position number
\r
4537 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4538 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4539 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4540 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4543 j = seed%4; seed /= 4;
\r
4544 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4545 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4546 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4547 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4548 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4549 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4550 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4551 j = seed%3; seed /= 3;
\r
4552 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4553 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4554 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4555 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4556 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4557 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4558 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4559 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4560 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4561 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4562 put(board, exoPieces[0], 0, 0, ANY);
\r
4563 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4567 InitPosition(redraw)
\r
4570 ChessSquare (* pieces)[BOARD_SIZE];
\r
4571 int i, j, pawnRow, overrule,
\r
4572 oldx = gameInfo.boardWidth,
\r
4573 oldy = gameInfo.boardHeight,
\r
4574 oldh = gameInfo.holdingsWidth,
\r
4575 oldv = gameInfo.variant;
\r
4577 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4578 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4580 /* [AS] Initialize pv info list [HGM] and game status */
\r
4582 for( i=0; i<MAX_MOVES; i++ ) {
\r
4583 pvInfoList[i].depth = 0;
\r
4584 epStatus[i]=EP_NONE;
\r
4585 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4588 initialRulePlies = 0; /* 50-move counter start */
\r
4590 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4591 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4595 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4596 /* the initialized data only consist of the two backranks. The switch */
\r
4597 /* selects which one we will use, which is than copied to the Board */
\r
4598 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4599 /* empty squares. This initial position is then copied to boards[0], */
\r
4600 /* possibly after shuffling, so that it remains available. */
\r
4602 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4603 gameInfo.boardWidth = 8;
\r
4604 gameInfo.boardHeight = 8;
\r
4605 gameInfo.holdingsSize = 0;
\r
4606 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4607 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4608 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4610 switch (gameInfo.variant) {
\r
4611 case VariantFischeRandom:
\r
4612 shuffleOpenings = TRUE;
\r
4614 pieces = FIDEArray;
\r
4616 case VariantShatranj:
\r
4617 pieces = ShatranjArray;
\r
4618 nrCastlingRights = 0;
\r
4619 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4621 case VariantTwoKings:
\r
4622 pieces = twoKingsArray;
\r
4623 nrCastlingRights = 8; /* add rights for second King */
\r
4624 castlingRights[0][6] = initialRights[2] = 5;
\r
4625 castlingRights[0][7] = initialRights[5] = 5;
\r
4626 castlingRank[6] = 0;
\r
4627 castlingRank[7] = BOARD_HEIGHT-1;
\r
4629 case VariantCapaRandom:
\r
4630 shuffleOpenings = TRUE;
\r
4631 case VariantCapablanca:
\r
4632 pieces = CapablancaArray;
\r
4633 gameInfo.boardWidth = 10;
\r
4634 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4636 case VariantGothic:
\r
4637 pieces = GothicArray;
\r
4638 gameInfo.boardWidth = 10;
\r
4639 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4641 case VariantJanus:
\r
4642 pieces = JanusArray;
\r
4643 gameInfo.boardWidth = 10;
\r
4644 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4645 nrCastlingRights = 6;
\r
4646 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4647 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4648 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;
\r
4649 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4650 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4651 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;
\r
4653 case VariantFalcon:
\r
4654 pieces = FalconArray;
\r
4655 gameInfo.boardWidth = 10;
\r
4656 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4658 case VariantXiangqi:
\r
4659 pieces = XiangqiArray;
\r
4660 gameInfo.boardWidth = 9;
\r
4661 gameInfo.boardHeight = 10;
\r
4662 nrCastlingRights = 0;
\r
4663 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4665 case VariantShogi:
\r
4666 pieces = ShogiArray;
\r
4667 gameInfo.boardWidth = 9;
\r
4668 gameInfo.boardHeight = 9;
\r
4669 gameInfo.holdingsSize = 7;
\r
4670 nrCastlingRights = 0;
\r
4671 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4673 case VariantCourier:
\r
4674 pieces = CourierArray;
\r
4675 gameInfo.boardWidth = 12;
\r
4676 nrCastlingRights = 0;
\r
4677 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4678 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4680 case VariantKnightmate:
\r
4681 pieces = KnightmateArray;
\r
4682 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4684 case VariantFairy:
\r
4685 pieces = fairyArray;
\r
4686 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4688 case VariantGreat:
\r
4689 pieces = GreatArray;
\r
4690 gameInfo.boardWidth = 10;
\r
4691 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4692 gameInfo.holdingsSize = 8;
\r
4694 case VariantSuper:
\r
4695 pieces = FIDEArray;
\r
4696 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4697 gameInfo.holdingsSize = 8;
\r
4698 startedFromSetupPosition = TRUE;
\r
4700 case VariantCrazyhouse:
\r
4701 case VariantBughouse:
\r
4702 pieces = FIDEArray;
\r
4703 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4704 gameInfo.holdingsSize = 5;
\r
4706 case VariantWildCastle:
\r
4707 pieces = FIDEArray;
\r
4708 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4709 shuffleOpenings = 1;
\r
4711 case VariantNoCastle:
\r
4712 pieces = FIDEArray;
\r
4713 nrCastlingRights = 0;
\r
4714 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4715 /* !!?unconstrained back-rank shuffle */
\r
4716 shuffleOpenings = 1;
\r
4721 if(appData.NrFiles >= 0) {
\r
4722 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4723 gameInfo.boardWidth = appData.NrFiles;
\r
4725 if(appData.NrRanks >= 0) {
\r
4726 gameInfo.boardHeight = appData.NrRanks;
\r
4728 if(appData.holdingsSize >= 0) {
\r
4729 i = appData.holdingsSize;
\r
4730 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4731 gameInfo.holdingsSize = i;
\r
4733 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4734 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4735 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4737 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4738 if(pawnRow < 1) pawnRow = 1;
\r
4740 /* User pieceToChar list overrules defaults */
\r
4741 if(appData.pieceToCharTable != NULL)
\r
4742 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4744 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4746 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4747 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4748 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4749 initialPosition[i][j] = s;
\r
4751 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4752 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4753 initialPosition[pawnRow][j] = WhitePawn;
\r
4754 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4755 if(gameInfo.variant == VariantXiangqi) {
\r
4757 initialPosition[pawnRow][j] =
\r
4758 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4759 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4760 initialPosition[2][j] = WhiteCannon;
\r
4761 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4765 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4767 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4770 initialPosition[1][j] = WhiteBishop;
\r
4771 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4773 initialPosition[1][j] = WhiteRook;
\r
4774 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4777 if( nrCastlingRights == -1) {
\r
4778 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4779 /* This sets default castling rights from none to normal corners */
\r
4780 /* Variants with other castling rights must set them themselves above */
\r
4781 nrCastlingRights = 6;
\r
4783 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4784 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4785 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4786 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4787 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4788 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4791 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4792 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4793 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;
\r
4794 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;
\r
4795 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4796 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4799 if(gameInfo.variant == VariantFischeRandom) {
\r
4800 if( appData.defaultFrcPosition < 0 ) {
\r
4801 ShuffleFRC( initialPosition );
\r
4804 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4806 startedFromSetupPosition = TRUE;
\r
4809 if (appData.debugMode) {
\r
4810 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4812 if(shuffleOpenings) {
\r
4813 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4814 startedFromSetupPosition = TRUE;
\r
4817 if(startedFromPositionFile) {
\r
4818 /* [HGM] loadPos: use PositionFile for every new game */
\r
4819 CopyBoard(initialPosition, filePosition);
\r
4820 for(i=0; i<nrCastlingRights; i++)
\r
4821 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4822 startedFromSetupPosition = TRUE;
\r
4825 CopyBoard(boards[0], initialPosition);
\r
4827 if(oldx != gameInfo.boardWidth ||
\r
4828 oldy != gameInfo.boardHeight ||
\r
4829 oldh != gameInfo.holdingsWidth
\r
4831 || oldv == VariantGothic || // For licensing popups
\r
4832 gameInfo.variant == VariantGothic
\r
4835 || oldv == VariantFalcon ||
\r
4836 gameInfo.variant == VariantFalcon
\r
4839 InitDrawingSizes(-2 ,0);
\r
4842 DrawPosition(TRUE, boards[currentMove]);
\r
4846 SendBoard(cps, moveNum)
\r
4847 ChessProgramState *cps;
\r
4850 char message[MSG_SIZ];
\r
4852 if (cps->useSetboard) {
\r
4853 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4854 sprintf(message, "setboard %s\n", fen);
\r
4855 SendToProgram(message, cps);
\r
4861 /* Kludge to set black to move, avoiding the troublesome and now
\r
4862 * deprecated "black" command.
\r
4864 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4866 SendToProgram("edit\n", cps);
\r
4867 SendToProgram("#\n", cps);
\r
4868 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4869 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4870 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4871 if ((int) *bp < (int) BlackPawn) {
\r
4872 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4873 AAA + j, ONE + i);
\r
4874 if(message[0] == '+' || message[0] == '~') {
\r
4875 sprintf(message, "%c%c%c+\n",
\r
4876 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4877 AAA + j, ONE + i);
\r
4879 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4880 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4881 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4883 SendToProgram(message, cps);
\r
4888 SendToProgram("c\n", cps);
\r
4889 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4890 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4891 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4892 if (((int) *bp != (int) EmptySquare)
\r
4893 && ((int) *bp >= (int) BlackPawn)) {
\r
4894 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4895 AAA + j, ONE + i);
\r
4896 if(message[0] == '+' || message[0] == '~') {
\r
4897 sprintf(message, "%c%c%c+\n",
\r
4898 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4899 AAA + j, ONE + i);
\r
4901 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4902 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4903 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4905 SendToProgram(message, cps);
\r
4910 SendToProgram(".\n", cps);
\r
4912 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4916 IsPromotion(fromX, fromY, toX, toY)
\r
4917 int fromX, fromY, toX, toY;
\r
4919 /* [HGM] add Shogi promotions */
\r
4920 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4921 ChessSquare piece;
\r
4923 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4924 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4925 /* [HGM] Note to self: line above also weeds out drops */
\r
4926 piece = boards[currentMove][fromY][fromX];
\r
4927 if(gameInfo.variant == VariantShogi) {
\r
4928 promotionZoneSize = 3;
\r
4929 highestPromotingPiece = (int)WhiteKing;
\r
4930 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4931 and if in normal chess we then allow promotion to King, why not
\r
4932 allow promotion of other piece in Shogi? */
\r
4934 if((int)piece >= BlackPawn) {
\r
4935 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4937 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4939 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4940 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4942 return ( (int)piece <= highestPromotingPiece );
\r
4946 InPalace(row, column)
\r
4948 { /* [HGM] for Xiangqi */
\r
4949 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4950 column < (BOARD_WIDTH + 4)/2 &&
\r
4951 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4956 PieceForSquare (x, y)
\r
4960 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4963 return boards[currentMove][y][x];
\r
4967 OKToStartUserMove(x, y)
\r
4970 ChessSquare from_piece;
\r
4973 if (matchMode) return FALSE;
\r
4974 if (gameMode == EditPosition) return TRUE;
\r
4976 if (x >= 0 && y >= 0)
\r
4977 from_piece = boards[currentMove][y][x];
\r
4979 from_piece = EmptySquare;
\r
4981 if (from_piece == EmptySquare) return FALSE;
\r
4983 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4984 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4986 switch (gameMode) {
\r
4987 case PlayFromGameFile:
\r
4989 case TwoMachinesPlay:
\r
4993 case IcsObserving:
\r
4997 case MachinePlaysWhite:
\r
4998 case IcsPlayingBlack:
\r
4999 if (appData.zippyPlay) return FALSE;
\r
5000 if (white_piece) {
\r
5001 DisplayMoveError(_("You are playing Black"));
\r
5006 case MachinePlaysBlack:
\r
5007 case IcsPlayingWhite:
\r
5008 if (appData.zippyPlay) return FALSE;
\r
5009 if (!white_piece) {
\r
5010 DisplayMoveError(_("You are playing White"));
\r
5016 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5017 DisplayMoveError(_("It is White's turn"));
\r
5020 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5021 DisplayMoveError(_("It is Black's turn"));
\r
5024 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
5025 /* Editing correspondence game history */
\r
5026 /* Could disallow this or prompt for confirmation */
\r
5027 cmailOldMove = -1;
\r
5029 if (currentMove < forwardMostMove) {
\r
5030 /* Discarding moves */
\r
5031 /* Could prompt for confirmation here,
\r
5032 but I don't think that's such a good idea */
\r
5033 forwardMostMove = currentMove;
\r
5037 case BeginningOfGame:
\r
5038 if (appData.icsActive) return FALSE;
\r
5039 if (!appData.noChessProgram) {
\r
5040 if (!white_piece) {
\r
5041 DisplayMoveError(_("You are playing White"));
\r
5048 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5049 DisplayMoveError(_("It is White's turn"));
\r
5052 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5053 DisplayMoveError(_("It is Black's turn"));
\r
5059 case IcsExamining:
\r
5062 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
5063 && gameMode != AnalyzeFile && gameMode != Training) {
\r
5064 DisplayMoveError(_("Displayed position is not current"));
\r
5070 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
5071 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
5072 int lastLoadGameUseList = FALSE;
\r
5073 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
5074 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
5078 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
5079 int fromX, fromY, toX, toY;
\r
5082 ChessMove moveType;
\r
5083 ChessSquare pdown, pup;
\r
5085 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
5086 if ((fromX == toX) && (fromY == toY)) {
\r
5087 return ImpossibleMove;
\r
5090 /* [HGM] suppress all moves into holdings area and guard band */
\r
5091 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5092 return ImpossibleMove;
\r
5094 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5095 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5096 pdown = boards[currentMove][fromY][fromX];
\r
5097 pup = boards[currentMove][toY][toX];
\r
5098 if ( gameMode != EditPosition &&
\r
5099 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5100 WhitePawn <= pup && pup < BlackPawn ||
\r
5101 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5102 BlackPawn <= pup && pup < EmptySquare
\r
5103 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5104 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5105 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5107 return ImpossibleMove;
\r
5109 /* Check if the user is playing in turn. This is complicated because we
\r
5110 let the user "pick up" a piece before it is his turn. So the piece he
\r
5111 tried to pick up may have been captured by the time he puts it down!
\r
5112 Therefore we use the color the user is supposed to be playing in this
\r
5113 test, not the color of the piece that is currently on the starting
\r
5114 square---except in EditGame mode, where the user is playing both
\r
5115 sides; fortunately there the capture race can't happen. (It can
\r
5116 now happen in IcsExamining mode, but that's just too bad. The user
\r
5117 will get a somewhat confusing message in that case.)
\r
5120 switch (gameMode) {
\r
5121 case PlayFromGameFile:
\r
5123 case TwoMachinesPlay:
\r
5125 case IcsObserving:
\r
5127 /* We switched into a game mode where moves are not accepted,
\r
5128 perhaps while the mouse button was down. */
\r
5129 return ImpossibleMove;
\r
5131 case MachinePlaysWhite:
\r
5132 /* User is moving for Black */
\r
5133 if (WhiteOnMove(currentMove)) {
\r
5134 DisplayMoveError(_("It is White's turn"));
\r
5135 return ImpossibleMove;
\r
5139 case MachinePlaysBlack:
\r
5140 /* User is moving for White */
\r
5141 if (!WhiteOnMove(currentMove)) {
\r
5142 DisplayMoveError(_("It is Black's turn"));
\r
5143 return ImpossibleMove;
\r
5148 case IcsExamining:
\r
5149 case BeginningOfGame:
\r
5152 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5153 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5154 /* User is moving for Black */
\r
5155 if (WhiteOnMove(currentMove)) {
\r
5156 DisplayMoveError(_("It is White's turn"));
\r
5157 return ImpossibleMove;
\r
5160 /* User is moving for White */
\r
5161 if (!WhiteOnMove(currentMove)) {
\r
5162 DisplayMoveError(_("It is Black's turn"));
\r
5163 return ImpossibleMove;
\r
5168 case IcsPlayingBlack:
\r
5169 /* User is moving for Black */
\r
5170 if (WhiteOnMove(currentMove)) {
\r
5171 if (!appData.premove) {
\r
5172 DisplayMoveError(_("It is White's turn"));
\r
5173 } else if (toX >= 0 && toY >= 0) {
\r
5176 premoveFromX = fromX;
\r
5177 premoveFromY = fromY;
\r
5178 premovePromoChar = promoChar;
\r
5180 if (appData.debugMode)
\r
5181 fprintf(debugFP, "Got premove: fromX %d,"
\r
5182 "fromY %d, toX %d, toY %d\n",
\r
5183 fromX, fromY, toX, toY);
\r
5185 return ImpossibleMove;
\r
5189 case IcsPlayingWhite:
\r
5190 /* User is moving for White */
\r
5191 if (!WhiteOnMove(currentMove)) {
\r
5192 if (!appData.premove) {
\r
5193 DisplayMoveError(_("It is Black's turn"));
\r
5194 } else if (toX >= 0 && toY >= 0) {
\r
5197 premoveFromX = fromX;
\r
5198 premoveFromY = fromY;
\r
5199 premovePromoChar = promoChar;
\r
5201 if (appData.debugMode)
\r
5202 fprintf(debugFP, "Got premove: fromX %d,"
\r
5203 "fromY %d, toX %d, toY %d\n",
\r
5204 fromX, fromY, toX, toY);
\r
5206 return ImpossibleMove;
\r
5213 case EditPosition:
\r
5214 /* EditPosition, empty square, or different color piece;
\r
5215 click-click move is possible */
\r
5216 if (toX == -2 || toY == -2) {
\r
5217 boards[0][fromY][fromX] = EmptySquare;
\r
5218 return AmbiguousMove;
\r
5219 } else if (toX >= 0 && toY >= 0) {
\r
5220 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5221 boards[0][fromY][fromX] = EmptySquare;
\r
5222 return AmbiguousMove;
\r
5224 return ImpossibleMove;
\r
5227 /* [HGM] If move started in holdings, it means a drop */
\r
5228 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5229 if( pup != EmptySquare ) return ImpossibleMove;
\r
5230 if(appData.testLegality) {
\r
5231 /* it would be more logical if LegalityTest() also figured out
\r
5232 * which drops are legal. For now we forbid pawns on back rank.
\r
5233 * Shogi is on its own here...
\r
5235 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5236 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5237 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5239 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5242 userOfferedDraw = FALSE;
\r
5244 /* [HGM] always test for legality, to get promotion info */
\r
5245 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5246 epStatus[currentMove], castlingRights[currentMove],
\r
5247 fromY, fromX, toY, toX, promoChar);
\r
5249 /* [HGM] but possibly ignore an IllegalMove result */
\r
5250 if (appData.testLegality) {
\r
5251 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5252 DisplayMoveError(_("Illegal move"));
\r
5253 return ImpossibleMove;
\r
5256 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5258 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5259 function is made into one that returns an OK move type if FinishMove
\r
5260 should be called. This to give the calling driver routine the
\r
5261 opportunity to finish the userMove input with a promotion popup,
\r
5262 without bothering the user with this for invalid or illegal moves */
\r
5264 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5267 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5269 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5270 ChessMove moveType;
\r
5271 int fromX, fromY, toX, toY;
\r
5272 /*char*/int promoChar;
\r
5274 char *bookHit = 0;
\r
5275 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5276 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5277 // [HGM] superchess: suppress promotions to non-available piece
\r
5278 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5279 if(WhiteOnMove(currentMove)) {
\r
5280 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5282 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5286 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5287 move type in caller when we know the move is a legal promotion */
\r
5288 if(moveType == NormalMove && promoChar)
\r
5289 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5290 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5291 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5292 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5293 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5294 fromX = boards[currentMove][fromY][fromX];
\r
5295 fromY = DROP_RANK;
\r
5298 /* [HGM] <popupFix> The following if has been moved here from
\r
5299 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5300 piece drops in training games?), and because it can only be
\r
5301 performed after it is known to what we promote. */
\r
5302 if (gameMode == Training) {
\r
5303 /* compare the move played on the board to the next move in the
\r
5304 * game. If they match, display the move and the opponent's response.
\r
5305 * If they don't match, display an error message.
\r
5309 CopyBoard(testBoard, boards[currentMove]);
\r
5310 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5312 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5313 ForwardInner(currentMove+1);
\r
5315 /* Autoplay the opponent's response.
\r
5316 * if appData.animate was TRUE when Training mode was entered,
\r
5317 * the response will be animated.
\r
5319 saveAnimate = appData.animate;
\r
5320 appData.animate = animateTraining;
\r
5321 ForwardInner(currentMove+1);
\r
5322 appData.animate = saveAnimate;
\r
5324 /* check for the end of the game */
\r
5325 if (currentMove >= forwardMostMove) {
\r
5326 gameMode = PlayFromGameFile;
\r
5328 SetTrainingModeOff();
\r
5329 DisplayInformation(_("End of game"));
\r
5332 DisplayError(_("Incorrect move"), 0);
\r
5337 /* Ok, now we know that the move is good, so we can kill
\r
5338 the previous line in Analysis Mode */
\r
5339 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5340 forwardMostMove = currentMove;
\r
5343 /* If we need the chess program but it's dead, restart it */
\r
5344 ResurrectChessProgram();
\r
5346 /* A user move restarts a paused game*/
\r
5350 thinkOutput[0] = NULLCHAR;
\r
5352 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5354 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5355 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5356 // [HGM] superchess: take promotion piece out of holdings
\r
5357 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5358 if(WhiteOnMove(forwardMostMove-1)) {
\r
5359 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5360 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5362 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5363 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5367 if (gameMode == BeginningOfGame) {
\r
5368 if (appData.noChessProgram) {
\r
5369 gameMode = EditGame;
\r
5372 char buf[MSG_SIZ];
\r
5373 gameMode = MachinePlaysBlack;
\r
5376 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5377 DisplayTitle(buf);
\r
5378 if (first.sendName) {
\r
5379 sprintf(buf, "name %s\n", gameInfo.white);
\r
5380 SendToProgram(buf, &first);
\r
5386 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5387 /* Relay move to ICS or chess engine */
\r
5388 if (appData.icsActive) {
\r
5389 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5390 gameMode == IcsExamining) {
\r
5391 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5392 ics_user_moved = 1;
\r
5395 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5396 gameMode == MachinePlaysWhite ||
\r
5397 gameMode == MachinePlaysBlack)) {
\r
5398 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5400 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5401 // [HGM] book: if program might be playing, let it use book
\r
5402 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5403 first.maybeThinking = TRUE;
\r
5404 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5405 if (currentMove == cmailOldMove + 1) {
\r
5406 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5410 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5412 switch (gameMode) {
\r
5414 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5415 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5419 case MT_CHECKMATE:
\r
5420 if (WhiteOnMove(currentMove)) {
\r
5421 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5423 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5426 case MT_STALEMATE:
\r
5427 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5432 case MachinePlaysBlack:
\r
5433 case MachinePlaysWhite:
\r
5434 /* disable certain menu options while machine is thinking */
\r
5435 SetMachineThinkingEnables();
\r
5442 if(bookHit) { // [HGM] book: simulate book reply
\r
5443 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5445 programStats.depth = programStats.nodes = programStats.time =
\r
5446 programStats.score = programStats.got_only_move = 0;
\r
5447 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5449 strcpy(bookMove, "move ");
\r
5450 strcat(bookMove, bookHit);
\r
5451 HandleMachineMove(bookMove, &first);
\r
5457 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5458 int fromX, fromY, toX, toY;
\r
5461 /* [HGM] This routine was added to allow calling of its two logical
\r
5462 parts from other modules in the old way. Before, UserMoveEvent()
\r
5463 automatically called FinishMove() if the move was OK, and returned
\r
5464 otherwise. I separated the two, in order to make it possible to
\r
5465 slip a promotion popup in between. But that it always needs two
\r
5466 calls, to the first part, (now called UserMoveTest() ), and to
\r
5467 FinishMove if the first part succeeded. Calls that do not need
\r
5468 to do anything in between, can call this routine the old way.
\r
5470 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5471 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5472 if(moveType != ImpossibleMove)
\r
5473 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5476 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5478 char * hint = lastHint;
\r
5479 FrontEndProgramStats stats;
\r
5481 stats.which = cps == &first ? 0 : 1;
\r
5482 stats.depth = cpstats->depth;
\r
5483 stats.nodes = cpstats->nodes;
\r
5484 stats.score = cpstats->score;
\r
5485 stats.time = cpstats->time;
\r
5486 stats.pv = cpstats->movelist;
\r
5487 stats.hint = lastHint;
\r
5488 stats.an_move_index = 0;
\r
5489 stats.an_move_count = 0;
\r
5491 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5492 stats.hint = cpstats->move_name;
\r
5493 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5494 stats.an_move_count = cpstats->nr_moves;
\r
5497 SetProgramStats( &stats );
\r
5500 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5501 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5502 char *bookHit = NULL;
\r
5504 //first determine if the incoming move brings opponent into his book
\r
5505 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5506 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5507 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5508 if(bookHit != NULL && !cps->bookSuspend) {
\r
5509 // make sure opponent is not going to reply after receiving move to book position
\r
5510 SendToProgram("force\n", cps);
\r
5511 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5513 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5514 // now arrange restart after book miss
\r
5516 // after a book hit we never send 'go', and the code after the call to this routine
\r
5517 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5518 char buf[MSG_SIZ];
\r
5519 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5520 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5521 SendToProgram(buf, cps);
\r
5522 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5523 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5524 SendToProgram("go\n", cps);
\r
5525 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5526 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5527 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5528 SendToProgram("go\n", cps);
\r
5529 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5531 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5534 char *savedMessage;
\r
5535 ChessProgramState *savedState;
\r
5536 void DeferredBookMove(void)
\r
5538 if(savedState->lastPing != savedState->lastPong)
\r
5539 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5541 HandleMachineMove(savedMessage, savedState);
\r
5545 HandleMachineMove(message, cps)
\r
5547 ChessProgramState *cps;
\r
5549 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5550 char realname[MSG_SIZ];
\r
5551 int fromX, fromY, toX, toY;
\r
5552 ChessMove moveType;
\r
5558 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5560 * Kludge to ignore BEL characters
\r
5562 while (*message == '\007') message++;
\r
5565 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5567 if(cps->debug && *message == '#') return;
\r
5570 * Look for book output
\r
5572 if (cps == &first && bookRequested) {
\r
5573 if (message[0] == '\t' || message[0] == ' ') {
\r
5574 /* Part of the book output is here; append it */
\r
5575 strcat(bookOutput, message);
\r
5576 strcat(bookOutput, " \n");
\r
5578 } else if (bookOutput[0] != NULLCHAR) {
\r
5579 /* All of book output has arrived; display it */
\r
5580 char *p = bookOutput;
\r
5581 while (*p != NULLCHAR) {
\r
5582 if (*p == '\t') *p = ' ';
\r
5585 DisplayInformation(bookOutput);
\r
5586 bookRequested = FALSE;
\r
5587 /* Fall through to parse the current output */
\r
5592 * Look for machine move.
\r
5594 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5595 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5597 /* This method is only useful on engines that support ping */
\r
5598 if (cps->lastPing != cps->lastPong) {
\r
5599 if (gameMode == BeginningOfGame) {
\r
5600 /* Extra move from before last new; ignore */
\r
5601 if (appData.debugMode) {
\r
5602 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5605 if (appData.debugMode) {
\r
5606 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5607 cps->which, gameMode);
\r
5610 SendToProgram("undo\n", cps);
\r
5615 switch (gameMode) {
\r
5616 case BeginningOfGame:
\r
5617 /* Extra move from before last reset; ignore */
\r
5618 if (appData.debugMode) {
\r
5619 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5626 /* Extra move after we tried to stop. The mode test is
\r
5627 not a reliable way of detecting this problem, but it's
\r
5628 the best we can do on engines that don't support ping.
\r
5630 if (appData.debugMode) {
\r
5631 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5632 cps->which, gameMode);
\r
5634 SendToProgram("undo\n", cps);
\r
5637 case MachinePlaysWhite:
\r
5638 case IcsPlayingWhite:
\r
5639 machineWhite = TRUE;
\r
5642 case MachinePlaysBlack:
\r
5643 case IcsPlayingBlack:
\r
5644 machineWhite = FALSE;
\r
5647 case TwoMachinesPlay:
\r
5648 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5651 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5652 if (appData.debugMode) {
\r
5654 "Ignoring move out of turn by %s, gameMode %d"
\r
5655 ", forwardMost %d\n",
\r
5656 cps->which, gameMode, forwardMostMove);
\r
5661 if (appData.debugMode) { int f = forwardMostMove;
\r
5662 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5663 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5665 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5666 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5667 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5668 /* Machine move could not be parsed; ignore it. */
\r
5669 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5670 machineMove, cps->which);
\r
5671 DisplayError(buf1, 0);
\r
5672 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5673 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5674 if (gameMode == TwoMachinesPlay) {
\r
5675 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5681 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5682 /* So we have to redo legality test with true e.p. status here, */
\r
5683 /* to make sure an illegal e.p. capture does not slip through, */
\r
5684 /* to cause a forfeit on a justified illegal-move complaint */
\r
5685 /* of the opponent. */
\r
5686 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5687 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5689 ChessMove moveType;
\r
5690 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5691 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5692 fromY, fromX, toY, toX, promoChar);
\r
5693 if (appData.debugMode) {
\r
5695 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5696 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5697 fprintf(debugFP, "castling rights\n");
\r
5699 if(moveType == IllegalMove) {
\r
5700 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5701 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5702 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5704 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5705 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5706 when they shouldn't (like TSCP-Gothic) */
\r
5707 switch(moveType) {
\r
5708 case WhiteASideCastleFR:
\r
5709 case BlackASideCastleFR:
\r
5711 currentMoveString[2]++;
\r
5713 case WhiteHSideCastleFR:
\r
5714 case BlackHSideCastleFR:
\r
5716 currentMoveString[2]--;
\r
5720 hintRequested = FALSE;
\r
5721 lastHint[0] = NULLCHAR;
\r
5722 bookRequested = FALSE;
\r
5723 /* Program may be pondering now */
\r
5724 cps->maybeThinking = TRUE;
\r
5725 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5726 if (cps->offeredDraw) cps->offeredDraw--;
\r
5729 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5731 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5732 ics_user_moved = 1;
\r
5733 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5734 char buf[3*MSG_SIZ];
\r
5736 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5737 programStats.depth,
\r
5738 programStats.score / 100.,
\r
5739 programStats.time / 100.,
\r
5740 (double) programStats.nodes,
\r
5741 programStats.nodes / (10*abs(programStats.time) + 1.),
\r
5742 programStats.movelist);
\r
5747 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5748 strcpy(machineMove, currentMoveString);
\r
5749 strcat(machineMove, "\n");
\r
5750 strcpy(moveList[forwardMostMove], machineMove);
\r
5752 /* [AS] Save move info and clear stats for next move */
\r
5753 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5754 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5755 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5756 ClearProgramStats();
\r
5757 thinkOutput[0] = NULLCHAR;
\r
5758 hiddenThinkOutputState = 0;
\r
5760 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5762 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5763 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5766 while( count < adjudicateLossPlies ) {
\r
5767 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5770 score = -score; /* Flip score for winning side */
\r
5773 if( score > adjudicateLossThreshold ) {
\r
5780 if( count >= adjudicateLossPlies ) {
\r
5781 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5783 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5784 "Xboard adjudication",
\r
5791 if( gameMode == TwoMachinesPlay ) {
\r
5792 // [HGM] some adjudications useful with buggy engines
\r
5793 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5794 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5796 if(appData.testLegality)
\r
5797 // don't wait for engine to announce game end if we can judge ourselves
\r
5798 switch (MateTest(boards[forwardMostMove],
\r
5799 PosFlags(forwardMostMove), epFile,
\r
5800 castlingRights[forwardMostMove]) ) {
\r
5805 case MT_STALEMATE:
\r
5806 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5807 if(appData.checkMates) {
\r
5808 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5809 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5810 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5814 case MT_CHECKMATE:
\r
5815 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5816 if(appData.checkMates) {
\r
5817 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5818 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5819 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5820 "Xboard adjudication: Checkmate",
\r
5826 if( appData.testLegality )
\r
5827 { /* [HGM] Some more adjudications for obstinate engines */
\r
5828 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5829 NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,
\r
5830 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5831 static int moveCount = 6;
\r
5833 /* First absolutely insufficient mating material. Count what is on board. */
\r
5834 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5835 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5839 { /* count B,N,R and other of each side */
\r
5843 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5844 bishopsColor |= 1 << ((i^j)&1);
\r
5849 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5850 bishopsColor |= 1 << ((i^j)&1);
\r
5860 case EmptySquare:
\r
5865 PawnAdvance += m; NrPawns++;
\r
5867 NrPieces += (p != EmptySquare);
\r
5868 NrW += ((int)p < (int)BlackPawn);
\r
5869 if(gameInfo.variant == VariantXiangqi &&
\r
5870 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5871 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5872 NrW -= ((int)p < (int)BlackPawn);
\r
5876 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5877 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5878 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5879 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5881 /* always flag draws, for judging claims */
\r
5882 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5884 if(appData.materialDraws) {
\r
5885 /* but only adjudicate them if adjudication enabled */
\r
5886 SendToProgram("force\n", cps->other); // suppress reply
\r
5887 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5888 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5889 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5894 /* Shatranj baring rule */
\r
5895 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5898 if(--bare < 0 && appData.checkMates) {
\r
5899 /* but only adjudicate them if adjudication enabled */
\r
5900 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5901 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5902 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5903 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5908 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5909 if(NrPieces == 4 &&
\r
5910 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5911 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5912 || NrWN==2 || NrBN==2 /* KNNK */
\r
5913 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5915 if(--moveCount < 0 && appData.trivialDraws)
\r
5916 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5917 SendToProgram("force\n", cps->other); // suppress reply
\r
5918 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5919 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5920 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5923 } else moveCount = 6;
\r
5927 if (appData.debugMode) { int i;
\r
5928 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5929 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5930 appData.drawRepeats);
\r
5931 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5932 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5936 /* Check for rep-draws */
\r
5938 for(k = forwardMostMove-2;
\r
5939 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5940 epStatus[k] < EP_UNKNOWN &&
\r
5941 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5945 if (appData.debugMode) {
\r
5946 fprintf(debugFP, " loop\n");
\r
5949 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5951 if (appData.debugMode) {
\r
5952 fprintf(debugFP, "match\n");
\r
5955 /* compare castling rights */
\r
5956 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5957 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5958 rights++; /* King lost rights, while rook still had them */
\r
5959 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5960 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5961 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5962 rights++; /* but at least one rook lost them */
\r
5964 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5965 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5967 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5968 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5969 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5973 if (appData.debugMode) {
\r
5974 for(i=0; i<nrCastlingRights; i++)
\r
5975 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5978 if (appData.debugMode) {
\r
5979 fprintf(debugFP, " %d %d\n", rights, k);
\r
5982 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5983 && appData.drawRepeats > 1) {
\r
5984 /* adjudicate after user-specified nr of repeats */
\r
5985 SendToProgram("force\n", cps->other); // suppress reply
\r
5986 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5987 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5988 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5989 // [HGM] xiangqi: check for forbidden perpetuals
\r
5990 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5991 for(m=forwardMostMove; m>k; m-=2) {
\r
5992 if(MateTest(boards[m], PosFlags(m),
\r
5993 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5994 ourPerpetual = 0; // the current mover did not always check
\r
5995 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5996 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5997 hisPerpetual = 0; // the opponent did not always check
\r
5999 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
6000 ourPerpetual, hisPerpetual);
\r
6001 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6002 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6003 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6006 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6007 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6008 // Now check for perpetual chases
\r
6009 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
6010 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
6011 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
6012 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6013 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6014 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
6017 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
6018 break; // Abort repetition-checking loop.
\r
6020 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
6022 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6025 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6026 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6030 /* Now we test for 50-move draws. Determine ply count */
\r
6031 count = forwardMostMove;
\r
6032 /* look for last irreversble move */
\r
6033 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6035 /* if we hit starting position, add initial plies */
\r
6036 if( count == backwardMostMove )
\r
6037 count -= initialRulePlies;
\r
6038 count = forwardMostMove - count;
\r
6040 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6041 /* this is used to judge if draw claims are legal */
\r
6042 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6043 SendToProgram("force\n", cps->other); // suppress reply
\r
6044 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6045 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6046 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6050 /* if draw offer is pending, treat it as a draw claim
\r
6051 * when draw condition present, to allow engines a way to
\r
6052 * claim draws before making their move to avoid a race
\r
6053 * condition occurring after their move
\r
6055 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6057 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6058 p = "Draw claim: 50-move rule";
\r
6059 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6060 p = "Draw claim: 3-fold repetition";
\r
6061 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6062 p = "Draw claim: insufficient mating material";
\r
6064 SendToProgram("force\n", cps->other); // suppress reply
\r
6065 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6066 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6067 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6073 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6074 SendToProgram("force\n", cps->other); // suppress reply
\r
6075 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6076 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6078 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6085 if (gameMode == TwoMachinesPlay) {
\r
6086 /* [HGM] relaying draw offers moved to after reception of move */
\r
6087 /* and interpreting offer as claim if it brings draw condition */
\r
6088 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6089 SendToProgram("draw\n", cps->other);
\r
6091 if (cps->other->sendTime) {
\r
6092 SendTimeRemaining(cps->other,
\r
6093 cps->other->twoMachinesColor[0] == 'w');
\r
6095 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6096 if (firstMove && !bookHit) {
\r
6097 firstMove = FALSE;
\r
6098 if (cps->other->useColors) {
\r
6099 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6101 SendToProgram("go\n", cps->other);
\r
6103 cps->other->maybeThinking = TRUE;
\r
6106 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6108 if (!pausing && appData.ringBellAfterMoves) {
\r
6113 * Reenable menu items that were disabled while
\r
6114 * machine was thinking
\r
6116 if (gameMode != TwoMachinesPlay)
\r
6117 SetUserThinkingEnables();
\r
6119 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6120 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6121 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6123 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6125 strcpy(bookMove, "move ");
\r
6126 strcat(bookMove, bookHit);
\r
6127 message = bookMove;
\r
6129 programStats.depth = programStats.nodes = programStats.time =
\r
6130 programStats.score = programStats.got_only_move = 0;
\r
6131 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6133 if(cps->lastPing != cps->lastPong) {
\r
6134 savedMessage = message; // args for deferred call
\r
6136 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6139 goto FakeBookMove;
\r
6145 /* Set special modes for chess engines. Later something general
\r
6146 * could be added here; for now there is just one kludge feature,
\r
6147 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6148 * when "xboard" is given as an interactive command.
\r
6150 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6151 cps->useSigint = FALSE;
\r
6152 cps->useSigterm = FALSE;
\r
6155 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6156 * want this, I was asked to put it in, and obliged.
\r
6158 if (!strncmp(message, "setboard ", 9)) {
\r
6159 Board initial_position; int i;
\r
6161 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6163 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6164 DisplayError(_("Bad FEN received from engine"), 0);
\r
6167 Reset(FALSE, FALSE);
\r
6168 CopyBoard(boards[0], initial_position);
\r
6169 initialRulePlies = FENrulePlies;
\r
6170 epStatus[0] = FENepStatus;
\r
6171 for( i=0; i<nrCastlingRights; i++ )
\r
6172 castlingRights[0][i] = FENcastlingRights[i];
\r
6173 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6174 else gameMode = MachinePlaysBlack;
\r
6175 DrawPosition(FALSE, boards[currentMove]);
\r
6181 * Look for communication commands
\r
6183 if (!strncmp(message, "telluser ", 9)) {
\r
6184 DisplayNote(message + 9);
\r
6187 if (!strncmp(message, "tellusererror ", 14)) {
\r
6188 DisplayError(message + 14, 0);
\r
6191 if (!strncmp(message, "tellopponent ", 13)) {
\r
6192 if (appData.icsActive) {
\r
6194 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6198 DisplayNote(message + 13);
\r
6202 if (!strncmp(message, "tellothers ", 11)) {
\r
6203 if (appData.icsActive) {
\r
6205 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6211 if (!strncmp(message, "tellall ", 8)) {
\r
6212 if (appData.icsActive) {
\r
6214 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6218 DisplayNote(message + 8);
\r
6222 if (strncmp(message, "warning", 7) == 0) {
\r
6223 /* Undocumented feature, use tellusererror in new code */
\r
6224 DisplayError(message, 0);
\r
6227 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6228 strcpy(realname, cps->tidy);
\r
6229 strcat(realname, " query");
\r
6230 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6233 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6234 * sent until we are logged on. Crafty kibitzes have been known to
\r
6235 * interfere with the login process.
\r
6238 if (!strncmp(message, "tellics ", 8)) {
\r
6239 SendToICS(message + 8);
\r
6243 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6244 SendToICS(ics_prefix);
\r
6245 SendToICS(message + 15);
\r
6249 /* The following are for backward compatibility only */
\r
6250 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6251 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6252 SendToICS(ics_prefix);
\r
6253 SendToICS(message);
\r
6258 if (strncmp(message, "feature ", 8) == 0) {
\r
6259 ParseFeatures(message+8, cps);
\r
6261 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6265 * If the move is illegal, cancel it and redraw the board.
\r
6266 * Also deal with other error cases. Matching is rather loose
\r
6267 * here to accommodate engines written before the spec.
\r
6269 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6270 strncmp(message, "Error", 5) == 0) {
\r
6271 if (StrStr(message, "name") ||
\r
6272 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6273 StrStr(message, "result") || StrStr(message, "board") ||
\r
6274 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6275 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6276 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6277 StrStr(message, "accepted")) {
\r
6280 if (StrStr(message, "protover")) {
\r
6281 /* Program is responding to input, so it's apparently done
\r
6282 initializing, and this error message indicates it is
\r
6283 protocol version 1. So we don't need to wait any longer
\r
6284 for it to initialize and send feature commands. */
\r
6285 FeatureDone(cps, 1);
\r
6286 cps->protocolVersion = 1;
\r
6289 cps->maybeThinking = FALSE;
\r
6291 if (StrStr(message, "draw")) {
\r
6292 /* Program doesn't have "draw" command */
\r
6293 cps->sendDrawOffers = 0;
\r
6296 if (cps->sendTime != 1 &&
\r
6297 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6298 /* Program apparently doesn't have "time" or "otim" command */
\r
6299 cps->sendTime = 0;
\r
6302 if (StrStr(message, "analyze")) {
\r
6303 cps->analysisSupport = FALSE;
\r
6304 cps->analyzing = FALSE;
\r
6305 Reset(FALSE, TRUE);
\r
6306 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6307 DisplayError(buf2, 0);
\r
6310 if (StrStr(message, "(no matching move)st")) {
\r
6311 /* Special kludge for GNU Chess 4 only */
\r
6312 cps->stKludge = TRUE;
\r
6313 SendTimeControl(cps, movesPerSession, timeControl,
\r
6314 timeIncrement, appData.searchDepth,
\r
6318 if (StrStr(message, "(no matching move)sd")) {
\r
6319 /* Special kludge for GNU Chess 4 only */
\r
6320 cps->sdKludge = TRUE;
\r
6321 SendTimeControl(cps, movesPerSession, timeControl,
\r
6322 timeIncrement, appData.searchDepth,
\r
6326 if (!StrStr(message, "llegal")) {
\r
6329 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6330 gameMode == IcsIdle) return;
\r
6331 if (forwardMostMove <= backwardMostMove) return;
\r
6333 /* Following removed: it caused a bug where a real illegal move
\r
6334 message in analyze mored would be ignored. */
\r
6335 if (cps == &first && programStats.ok_to_send == 0) {
\r
6336 /* Bogus message from Crafty responding to "." This filtering
\r
6337 can miss some of the bad messages, but fortunately the bug
\r
6338 is fixed in current Crafty versions, so it doesn't matter. */
\r
6342 if (pausing) PauseEvent();
\r
6343 if (gameMode == PlayFromGameFile) {
\r
6344 /* Stop reading this game file */
\r
6345 gameMode = EditGame;
\r
6348 currentMove = --forwardMostMove;
\r
6349 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6351 DisplayBothClocks();
\r
6352 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6353 parseList[currentMove], cps->which);
\r
6354 DisplayMoveError(buf1);
\r
6355 DrawPosition(FALSE, boards[currentMove]);
\r
6357 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6358 /* only passes fully legal moves */
\r
6359 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6360 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6361 "False illegal-move claim", GE_XBOARD );
\r
6365 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6366 /* Program has a broken "time" command that
\r
6367 outputs a string not ending in newline.
\r
6369 cps->sendTime = 0;
\r
6373 * If chess program startup fails, exit with an error message.
\r
6374 * Attempts to recover here are futile.
\r
6376 if ((StrStr(message, "unknown host") != NULL)
\r
6377 || (StrStr(message, "No remote directory") != NULL)
\r
6378 || (StrStr(message, "not found") != NULL)
\r
6379 || (StrStr(message, "No such file") != NULL)
\r
6380 || (StrStr(message, "can't alloc") != NULL)
\r
6381 || (StrStr(message, "Permission denied") != NULL)) {
\r
6383 cps->maybeThinking = FALSE;
\r
6384 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6385 cps->which, cps->program, cps->host, message);
\r
6386 RemoveInputSource(cps->isr);
\r
6387 DisplayFatalError(buf1, 0, 1);
\r
6392 * Look for hint output
\r
6394 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6395 if (cps == &first && hintRequested) {
\r
6396 hintRequested = FALSE;
\r
6397 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6398 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6399 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6400 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6401 fromY, fromX, toY, toX, promoChar, buf1);
\r
6402 sprintf(buf2, _("Hint: %s"), buf1);
\r
6403 DisplayInformation(buf2);
\r
6405 /* Hint move could not be parsed!? */
\r
6407 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6408 buf1, cps->which);
\r
6409 DisplayError(buf2, 0);
\r
6412 strcpy(lastHint, buf1);
\r
6418 * Ignore other messages if game is not in progress
\r
6420 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6421 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6424 * look for win, lose, draw, or draw offer
\r
6426 if (strncmp(message, "1-0", 3) == 0) {
\r
6427 char *p, *q, *r = "";
\r
6428 p = strchr(message, '{');
\r
6430 q = strchr(p, '}');
\r
6436 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6438 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6439 char *p, *q, *r = "";
\r
6440 p = strchr(message, '{');
\r
6442 q = strchr(p, '}');
\r
6448 /* Kludge for Arasan 4.1 bug */
\r
6449 if (strcmp(r, "Black resigns") == 0) {
\r
6450 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6453 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6455 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6456 char *p, *q, *r = "";
\r
6457 p = strchr(message, '{');
\r
6459 q = strchr(p, '}');
\r
6466 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6469 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6470 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6472 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6473 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6475 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6476 strncmp(message, "Black matches", 13) == 0 ) {
\r
6477 /* [HGM] ignore GNUShogi noises */
\r
6479 } else if (strncmp(message, "White", 5) == 0 &&
\r
6480 message[5] != '(' &&
\r
6481 StrStr(message, "Black") == NULL) {
\r
6482 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6484 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6485 message[5] != '(') {
\r
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6488 } else if (strcmp(message, "resign") == 0 ||
\r
6489 strcmp(message, "computer resigns") == 0) {
\r
6490 switch (gameMode) {
\r
6491 case MachinePlaysBlack:
\r
6492 case IcsPlayingBlack:
\r
6493 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6495 case MachinePlaysWhite:
\r
6496 case IcsPlayingWhite:
\r
6497 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6499 case TwoMachinesPlay:
\r
6500 if (cps->twoMachinesColor[0] == 'w')
\r
6501 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6503 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6506 /* can't happen */
\r
6510 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6511 switch (gameMode) {
\r
6512 case MachinePlaysBlack:
\r
6513 case IcsPlayingBlack:
\r
6514 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6516 case MachinePlaysWhite:
\r
6517 case IcsPlayingWhite:
\r
6518 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6520 case TwoMachinesPlay:
\r
6521 if (cps->twoMachinesColor[0] == 'w')
\r
6522 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6524 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6527 /* can't happen */
\r
6531 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6532 switch (gameMode) {
\r
6533 case MachinePlaysBlack:
\r
6534 case IcsPlayingBlack:
\r
6535 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6537 case MachinePlaysWhite:
\r
6538 case IcsPlayingWhite:
\r
6539 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6541 case TwoMachinesPlay:
\r
6542 if (cps->twoMachinesColor[0] == 'w')
\r
6543 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6545 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6548 /* can't happen */
\r
6552 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6553 if (WhiteOnMove(forwardMostMove)) {
\r
6554 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6556 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6559 } else if (strstr(message, "Draw") != NULL ||
\r
6560 strstr(message, "game is a draw") != NULL) {
\r
6561 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6563 } else if (strstr(message, "offer") != NULL &&
\r
6564 strstr(message, "draw") != NULL) {
\r
6566 if (appData.zippyPlay && first.initDone) {
\r
6567 /* Relay offer to ICS */
\r
6568 SendToICS(ics_prefix);
\r
6569 SendToICS("draw\n");
\r
6572 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6573 if (gameMode == TwoMachinesPlay) {
\r
6574 if (cps->other->offeredDraw) {
\r
6575 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6576 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6577 /* until after we also have move, to see if it is really claim */
\r
6581 if (cps->other->sendDrawOffers) {
\r
6582 SendToProgram("draw\n", cps->other);
\r
6586 } else if (gameMode == MachinePlaysWhite ||
\r
6587 gameMode == MachinePlaysBlack) {
\r
6588 if (userOfferedDraw) {
\r
6589 DisplayInformation(_("Machine accepts your draw offer"));
\r
6590 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6592 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6599 * Look for thinking output
\r
6601 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6604 int plylev, mvleft, mvtot, curscore, time;
\r
6605 char mvname[MOVE_LEN];
\r
6606 u64 nodes; // [DM]
\r
6608 int ignore = FALSE;
\r
6609 int prefixHint = FALSE;
\r
6610 mvname[0] = NULLCHAR;
\r
6612 switch (gameMode) {
\r
6613 case MachinePlaysBlack:
\r
6614 case IcsPlayingBlack:
\r
6615 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6617 case MachinePlaysWhite:
\r
6618 case IcsPlayingWhite:
\r
6619 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6624 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6625 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6627 case TwoMachinesPlay:
\r
6628 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6638 buf1[0] = NULLCHAR;
\r
6639 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6640 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6642 if (plyext != ' ' && plyext != '\t') {
\r
6646 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6647 if( cps->scoreIsAbsolute &&
\r
6648 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6650 curscore = -curscore;
\r
6654 programStats.depth = plylev;
\r
6655 programStats.nodes = nodes;
\r
6656 programStats.time = time;
\r
6657 programStats.score = curscore;
\r
6658 programStats.got_only_move = 0;
\r
6660 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6663 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6664 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6665 if(WhiteOnMove(forwardMostMove))
\r
6666 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6667 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6670 /* Buffer overflow protection */
\r
6671 if (buf1[0] != NULLCHAR) {
\r
6672 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6673 && appData.debugMode) {
\r
6675 "PV is too long; using the first %d bytes.\n",
\r
6676 sizeof(programStats.movelist) - 1);
\r
6679 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6681 sprintf(programStats.movelist, " no PV\n");
\r
6684 if (programStats.seen_stat) {
\r
6685 programStats.ok_to_send = 1;
\r
6688 if (strchr(programStats.movelist, '(') != NULL) {
\r
6689 programStats.line_is_book = 1;
\r
6690 programStats.nr_moves = 0;
\r
6691 programStats.moves_left = 0;
\r
6693 programStats.line_is_book = 0;
\r
6696 SendProgramStatsToFrontend( cps, &programStats );
\r
6699 [AS] Protect the thinkOutput buffer from overflow... this
\r
6700 is only useful if buf1 hasn't overflowed first!
\r
6702 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6704 (gameMode == TwoMachinesPlay ?
\r
6705 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6706 ((double) curscore) / 100.0,
\r
6707 prefixHint ? lastHint : "",
\r
6708 prefixHint ? " " : "" );
\r
6710 if( buf1[0] != NULLCHAR ) {
\r
6711 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6713 if( strlen(buf1) > max_len ) {
\r
6714 if( appData.debugMode) {
\r
6715 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6717 buf1[max_len+1] = '\0';
\r
6720 strcat( thinkOutput, buf1 );
\r
6723 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6724 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6725 DisplayMove(currentMove - 1);
\r
6726 DisplayAnalysis();
\r
6730 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6731 /* crafty (9.25+) says "(only move) <move>"
\r
6732 * if there is only 1 legal move
\r
6734 sscanf(p, "(only move) %s", buf1);
\r
6735 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6736 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6737 programStats.depth = 1;
\r
6738 programStats.nr_moves = 1;
\r
6739 programStats.moves_left = 1;
\r
6740 programStats.nodes = 1;
\r
6741 programStats.time = 1;
\r
6742 programStats.got_only_move = 1;
\r
6744 /* Not really, but we also use this member to
\r
6745 mean "line isn't going to change" (Crafty
\r
6746 isn't searching, so stats won't change) */
\r
6747 programStats.line_is_book = 1;
\r
6749 SendProgramStatsToFrontend( cps, &programStats );
\r
6751 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6752 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6753 DisplayMove(currentMove - 1);
\r
6754 DisplayAnalysis();
\r
6757 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6758 &time, &nodes, &plylev, &mvleft,
\r
6759 &mvtot, mvname) >= 5) {
\r
6760 /* The stat01: line is from Crafty (9.29+) in response
\r
6761 to the "." command */
\r
6762 programStats.seen_stat = 1;
\r
6763 cps->maybeThinking = TRUE;
\r
6765 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6768 programStats.depth = plylev;
\r
6769 programStats.time = time;
\r
6770 programStats.nodes = nodes;
\r
6771 programStats.moves_left = mvleft;
\r
6772 programStats.nr_moves = mvtot;
\r
6773 strcpy(programStats.move_name, mvname);
\r
6774 programStats.ok_to_send = 1;
\r
6775 programStats.movelist[0] = '\0';
\r
6777 SendProgramStatsToFrontend( cps, &programStats );
\r
6779 DisplayAnalysis();
\r
6782 } else if (strncmp(message,"++",2) == 0) {
\r
6783 /* Crafty 9.29+ outputs this */
\r
6784 programStats.got_fail = 2;
\r
6787 } else if (strncmp(message,"--",2) == 0) {
\r
6788 /* Crafty 9.29+ outputs this */
\r
6789 programStats.got_fail = 1;
\r
6792 } else if (thinkOutput[0] != NULLCHAR &&
\r
6793 strncmp(message, " ", 4) == 0) {
\r
6794 unsigned message_len;
\r
6797 while (*p && *p == ' ') p++;
\r
6799 message_len = strlen( p );
\r
6801 /* [AS] Avoid buffer overflow */
\r
6802 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6803 strcat(thinkOutput, " ");
\r
6804 strcat(thinkOutput, p);
\r
6807 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6808 strcat(programStats.movelist, " ");
\r
6809 strcat(programStats.movelist, p);
\r
6812 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6813 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6814 DisplayMove(currentMove - 1);
\r
6815 DisplayAnalysis();
\r
6821 buf1[0] = NULLCHAR;
\r
6823 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6824 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6826 ChessProgramStats cpstats;
\r
6828 if (plyext != ' ' && plyext != '\t') {
\r
6832 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6833 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6834 curscore = -curscore;
\r
6837 cpstats.depth = plylev;
\r
6838 cpstats.nodes = nodes;
\r
6839 cpstats.time = time;
\r
6840 cpstats.score = curscore;
\r
6841 cpstats.got_only_move = 0;
\r
6842 cpstats.movelist[0] = '\0';
\r
6844 if (buf1[0] != NULLCHAR) {
\r
6845 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6848 cpstats.ok_to_send = 0;
\r
6849 cpstats.line_is_book = 0;
\r
6850 cpstats.nr_moves = 0;
\r
6851 cpstats.moves_left = 0;
\r
6853 SendProgramStatsToFrontend( cps, &cpstats );
\r
6860 /* Parse a game score from the character string "game", and
\r
6861 record it as the history of the current game. The game
\r
6862 score is NOT assumed to start from the standard position.
\r
6863 The display is not updated in any way.
\r
6866 ParseGameHistory(game)
\r
6869 ChessMove moveType;
\r
6870 int fromX, fromY, toX, toY, boardIndex;
\r
6873 char buf[MSG_SIZ];
\r
6875 if (appData.debugMode)
\r
6876 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6878 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6879 gameInfo.site = StrSave(appData.icsHost);
\r
6880 gameInfo.date = PGNDate();
\r
6881 gameInfo.round = StrSave("-");
\r
6883 /* Parse out names of players */
\r
6884 while (*game == ' ') game++;
\r
6886 while (*game != ' ') *p++ = *game++;
\r
6888 gameInfo.white = StrSave(buf);
\r
6889 while (*game == ' ') game++;
\r
6891 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6893 gameInfo.black = StrSave(buf);
\r
6896 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6899 yyboardindex = boardIndex;
\r
6900 moveType = (ChessMove) yylex();
\r
6901 switch (moveType) {
\r
6902 case IllegalMove: /* maybe suicide chess, etc. */
\r
6903 if (appData.debugMode) {
\r
6904 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6905 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6906 setbuf(debugFP, NULL);
\r
6908 case WhitePromotionChancellor:
\r
6909 case BlackPromotionChancellor:
\r
6910 case WhitePromotionArchbishop:
\r
6911 case BlackPromotionArchbishop:
\r
6912 case WhitePromotionQueen:
\r
6913 case BlackPromotionQueen:
\r
6914 case WhitePromotionRook:
\r
6915 case BlackPromotionRook:
\r
6916 case WhitePromotionBishop:
\r
6917 case BlackPromotionBishop:
\r
6918 case WhitePromotionKnight:
\r
6919 case BlackPromotionKnight:
\r
6920 case WhitePromotionKing:
\r
6921 case BlackPromotionKing:
\r
6923 case WhiteCapturesEnPassant:
\r
6924 case BlackCapturesEnPassant:
\r
6925 case WhiteKingSideCastle:
\r
6926 case WhiteQueenSideCastle:
\r
6927 case BlackKingSideCastle:
\r
6928 case BlackQueenSideCastle:
\r
6929 case WhiteKingSideCastleWild:
\r
6930 case WhiteQueenSideCastleWild:
\r
6931 case BlackKingSideCastleWild:
\r
6932 case BlackQueenSideCastleWild:
\r
6934 case WhiteHSideCastleFR:
\r
6935 case WhiteASideCastleFR:
\r
6936 case BlackHSideCastleFR:
\r
6937 case BlackASideCastleFR:
\r
6939 fromX = currentMoveString[0] - AAA;
\r
6940 fromY = currentMoveString[1] - ONE;
\r
6941 toX = currentMoveString[2] - AAA;
\r
6942 toY = currentMoveString[3] - ONE;
\r
6943 promoChar = currentMoveString[4];
\r
6947 fromX = moveType == WhiteDrop ?
\r
6948 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6949 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6950 fromY = DROP_RANK;
\r
6951 toX = currentMoveString[2] - AAA;
\r
6952 toY = currentMoveString[3] - ONE;
\r
6953 promoChar = NULLCHAR;
\r
6955 case AmbiguousMove:
\r
6957 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6958 if (appData.debugMode) {
\r
6959 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6960 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6961 setbuf(debugFP, NULL);
\r
6963 DisplayError(buf, 0);
\r
6965 case ImpossibleMove:
\r
6967 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6968 if (appData.debugMode) {
\r
6969 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6970 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6971 setbuf(debugFP, NULL);
\r
6973 DisplayError(buf, 0);
\r
6975 case (ChessMove) 0: /* end of file */
\r
6976 if (boardIndex < backwardMostMove) {
\r
6977 /* Oops, gap. How did that happen? */
\r
6978 DisplayError(_("Gap in move list"), 0);
\r
6981 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6982 if (boardIndex > forwardMostMove) {
\r
6983 forwardMostMove = boardIndex;
\r
6987 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6988 strcat(parseList[boardIndex-1], " ");
\r
6989 strcat(parseList[boardIndex-1], yy_text);
\r
7001 case GameUnfinished:
\r
7002 if (gameMode == IcsExamining) {
\r
7003 if (boardIndex < backwardMostMove) {
\r
7004 /* Oops, gap. How did that happen? */
\r
7007 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7010 gameInfo.result = moveType;
\r
7011 p = strchr(yy_text, '{');
\r
7012 if (p == NULL) p = strchr(yy_text, '(');
\r
7015 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7017 q = strchr(p, *p == '{' ? '}' : ')');
\r
7018 if (q != NULL) *q = NULLCHAR;
\r
7021 gameInfo.resultDetails = StrSave(p);
\r
7024 if (boardIndex >= forwardMostMove &&
\r
7025 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7026 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7029 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7030 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7031 parseList[boardIndex]);
\r
7032 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7033 /* currentMoveString is set as a side-effect of yylex */
\r
7034 strcpy(moveList[boardIndex], currentMoveString);
\r
7035 strcat(moveList[boardIndex], "\n");
\r
7037 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7038 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7039 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7041 case MT_STALEMATE:
\r
7045 if(gameInfo.variant != VariantShogi)
\r
7046 strcat(parseList[boardIndex - 1], "+");
\r
7048 case MT_CHECKMATE:
\r
7049 strcat(parseList[boardIndex - 1], "#");
\r
7056 /* Apply a move to the given board */
\r
7058 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7059 int fromX, fromY, toX, toY;
\r
7063 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7065 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7066 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7067 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7070 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7071 oldEP = epStatus[p-1];
\r
7072 epStatus[p] = EP_NONE;
\r
7074 if( board[toY][toX] != EmptySquare )
\r
7075 epStatus[p] = EP_CAPTURE;
\r
7077 if( board[fromY][fromX] == WhitePawn ) {
\r
7078 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7079 epStatus[p] = EP_PAWN_MOVE;
\r
7080 if( toY-fromY==2) {
\r
7081 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7082 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7083 epStatus[p] = toX | berolina;
\r
7084 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7085 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7086 epStatus[p] = toX;
\r
7089 if( board[fromY][fromX] == BlackPawn ) {
\r
7090 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7091 epStatus[p] = EP_PAWN_MOVE;
\r
7092 if( toY-fromY== -2) {
\r
7093 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7094 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7095 epStatus[p] = toX | berolina;
\r
7096 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7097 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7098 epStatus[p] = toX;
\r
7102 for(i=0; i<nrCastlingRights; i++) {
\r
7103 castlingRights[p][i] = castlingRights[p-1][i];
\r
7104 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7105 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7106 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7111 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7112 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7113 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7115 if (fromX == toX && fromY == toY) return;
\r
7117 if (fromY == DROP_RANK) {
\r
7118 /* must be first */
\r
7119 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7121 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7122 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7123 if(gameInfo.variant == VariantKnightmate)
\r
7124 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7126 /* Code added by Tord: */
\r
7127 /* FRC castling assumed when king captures friendly rook. */
\r
7128 if (board[fromY][fromX] == WhiteKing &&
\r
7129 board[toY][toX] == WhiteRook) {
\r
7130 board[fromY][fromX] = EmptySquare;
\r
7131 board[toY][toX] = EmptySquare;
\r
7133 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7135 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7137 } else if (board[fromY][fromX] == BlackKing &&
\r
7138 board[toY][toX] == BlackRook) {
\r
7139 board[fromY][fromX] = EmptySquare;
\r
7140 board[toY][toX] = EmptySquare;
\r
7142 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7144 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7146 /* End of code added by Tord */
\r
7148 } else if (board[fromY][fromX] == king
\r
7149 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7150 && toY == fromY && toX > fromX+1) {
\r
7151 board[fromY][fromX] = EmptySquare;
\r
7152 board[toY][toX] = king;
\r
7153 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7154 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7155 } else if (board[fromY][fromX] == king
\r
7156 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7157 && toY == fromY && toX < fromX-1) {
\r
7158 board[fromY][fromX] = EmptySquare;
\r
7159 board[toY][toX] = king;
\r
7160 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7161 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7162 } else if (board[fromY][fromX] == WhitePawn
\r
7163 && toY == BOARD_HEIGHT-1
\r
7164 && gameInfo.variant != VariantXiangqi
\r
7166 /* white pawn promotion */
\r
7167 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7168 if (board[toY][toX] == EmptySquare) {
\r
7169 board[toY][toX] = WhiteQueen;
\r
7171 if(gameInfo.variant==VariantBughouse ||
\r
7172 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7173 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7174 board[fromY][fromX] = EmptySquare;
\r
7175 } else if ((fromY == BOARD_HEIGHT-4)
\r
7177 && gameInfo.variant != VariantXiangqi
\r
7178 && gameInfo.variant != VariantBerolina
\r
7179 && (board[fromY][fromX] == WhitePawn)
\r
7180 && (board[toY][toX] == EmptySquare)) {
\r
7181 board[fromY][fromX] = EmptySquare;
\r
7182 board[toY][toX] = WhitePawn;
\r
7183 captured = board[toY - 1][toX];
\r
7184 board[toY - 1][toX] = EmptySquare;
\r
7185 } else if ((fromY == BOARD_HEIGHT-4)
\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 if(oldEP & EP_BEROLIN_A) {
\r
7193 captured = board[fromY][fromX-1];
\r
7194 board[fromY][fromX-1] = EmptySquare;
\r
7195 }else{ captured = board[fromY][fromX+1];
\r
7196 board[fromY][fromX+1] = EmptySquare;
\r
7198 } else if (board[fromY][fromX] == king
\r
7199 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7200 && toY == fromY && toX > fromX+1) {
\r
7201 board[fromY][fromX] = EmptySquare;
\r
7202 board[toY][toX] = king;
\r
7203 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7204 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7205 } else if (board[fromY][fromX] == king
\r
7206 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7207 && toY == fromY && toX < fromX-1) {
\r
7208 board[fromY][fromX] = EmptySquare;
\r
7209 board[toY][toX] = king;
\r
7210 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7211 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7212 } else if (fromY == 7 && fromX == 3
\r
7213 && board[fromY][fromX] == BlackKing
\r
7214 && toY == 7 && toX == 5) {
\r
7215 board[fromY][fromX] = EmptySquare;
\r
7216 board[toY][toX] = BlackKing;
\r
7217 board[fromY][7] = EmptySquare;
\r
7218 board[toY][4] = BlackRook;
\r
7219 } else if (fromY == 7 && fromX == 3
\r
7220 && board[fromY][fromX] == BlackKing
\r
7221 && toY == 7 && toX == 1) {
\r
7222 board[fromY][fromX] = EmptySquare;
\r
7223 board[toY][toX] = BlackKing;
\r
7224 board[fromY][0] = EmptySquare;
\r
7225 board[toY][2] = BlackRook;
\r
7226 } else if (board[fromY][fromX] == BlackPawn
\r
7228 && gameInfo.variant != VariantXiangqi
\r
7230 /* black pawn promotion */
\r
7231 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7232 if (board[0][toX] == EmptySquare) {
\r
7233 board[0][toX] = BlackQueen;
\r
7235 if(gameInfo.variant==VariantBughouse ||
\r
7236 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7237 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7238 board[fromY][fromX] = EmptySquare;
\r
7239 } else if ((fromY == 3)
\r
7241 && gameInfo.variant != VariantXiangqi
\r
7242 && gameInfo.variant != VariantBerolina
\r
7243 && (board[fromY][fromX] == BlackPawn)
\r
7244 && (board[toY][toX] == EmptySquare)) {
\r
7245 board[fromY][fromX] = EmptySquare;
\r
7246 board[toY][toX] = BlackPawn;
\r
7247 captured = board[toY + 1][toX];
\r
7248 board[toY + 1][toX] = EmptySquare;
\r
7249 } else if ((fromY == 3)
\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 if(oldEP & EP_BEROLIN_A) {
\r
7257 captured = board[fromY][fromX-1];
\r
7258 board[fromY][fromX-1] = EmptySquare;
\r
7259 }else{ captured = board[fromY][fromX+1];
\r
7260 board[fromY][fromX+1] = EmptySquare;
\r
7263 board[toY][toX] = board[fromY][fromX];
\r
7264 board[fromY][fromX] = EmptySquare;
\r
7267 /* [HGM] now we promote for Shogi, if needed */
\r
7268 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7269 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7272 if (gameInfo.holdingsWidth != 0) {
\r
7274 /* !!A lot more code needs to be written to support holdings */
\r
7275 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7276 /* penultimate board files, so they are automaticlly stored */
\r
7277 /* in the game history. */
\r
7278 if (fromY == DROP_RANK) {
\r
7279 /* Delete from holdings, by decreasing count */
\r
7280 /* and erasing image if necessary */
\r
7282 if(p < (int) BlackPawn) { /* white drop */
\r
7283 p -= (int)WhitePawn;
\r
7284 if(p >= gameInfo.holdingsSize) p = 0;
\r
7285 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7286 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7287 } else { /* black drop */
\r
7288 p -= (int)BlackPawn;
\r
7289 if(p >= gameInfo.holdingsSize) p = 0;
\r
7290 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7291 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7294 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7295 && gameInfo.variant != VariantBughouse ) {
\r
7296 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7297 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7298 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7299 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7301 p = (int) captured;
\r
7302 if (p >= (int) BlackPawn) {
\r
7303 p -= (int)BlackPawn;
\r
7304 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7305 /* in Shogi restore piece to its original first */
\r
7306 captured = (ChessSquare) (DEMOTED captured);
\r
7309 p = PieceToNumber((ChessSquare)p);
\r
7310 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7311 board[p][BOARD_WIDTH-2]++;
\r
7312 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7314 p -= (int)WhitePawn;
\r
7315 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7316 captured = (ChessSquare) (DEMOTED captured);
\r
7319 p = PieceToNumber((ChessSquare)p);
\r
7320 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7321 board[BOARD_HEIGHT-1-p][1]++;
\r
7322 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7326 } else if (gameInfo.variant == VariantAtomic) {
\r
7327 if (captured != EmptySquare) {
\r
7329 for (y = toY-1; y <= toY+1; y++) {
\r
7330 for (x = toX-1; x <= toX+1; x++) {
\r
7331 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7332 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7333 board[y][x] = EmptySquare;
\r
7337 board[toY][toX] = EmptySquare;
\r
7340 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7341 /* [HGM] Shogi promotions */
\r
7342 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7347 /* Updates forwardMostMove */
\r
7349 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7350 int fromX, fromY, toX, toY;
\r
7353 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7355 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7356 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7357 piece = boards[forwardMostMove][fromY][fromX];
\r
7358 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7359 if(gameInfo.variant == VariantKnightmate)
\r
7360 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7361 if(forwardMostMove == 0) {
\r
7362 if(blackPlaysFirst)
\r
7363 fprintf(serverMoves, "%s;", second.tidy);
\r
7364 fprintf(serverMoves, "%s;", first.tidy);
\r
7365 if(!blackPlaysFirst)
\r
7366 fprintf(serverMoves, "%s;", second.tidy);
\r
7367 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7368 lastLoadFlag = loadFlag;
\r
7369 // print base move
\r
7370 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7371 // print castling suffix
\r
7372 if( toY == fromY && piece == king ) {
\r
7374 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7376 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7379 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7380 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7381 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7383 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7384 // promotion suffix
\r
7385 if(promoChar != NULLCHAR)
\r
7386 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7388 fprintf(serverMoves, "/%d/%d",
\r
7389 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7390 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7391 else timeLeft = blackTimeRemaining/1000;
\r
7392 fprintf(serverMoves, "/%d", timeLeft);
\r
7394 fflush(serverMoves);
\r
7397 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7398 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7403 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7404 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7405 if (commentList[forwardMostMove+1] != NULL) {
\r
7406 free(commentList[forwardMostMove+1]);
\r
7407 commentList[forwardMostMove+1] = NULL;
\r
7409 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7410 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7411 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7412 gameInfo.result = GameUnfinished;
\r
7413 if (gameInfo.resultDetails != NULL) {
\r
7414 free(gameInfo.resultDetails);
\r
7415 gameInfo.resultDetails = NULL;
\r
7417 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7418 moveList[forwardMostMove - 1]);
\r
7419 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7420 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7421 fromY, fromX, toY, toX, promoChar,
\r
7422 parseList[forwardMostMove - 1]);
\r
7423 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7424 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7425 castlingRights[forwardMostMove]) ) {
\r
7427 case MT_STALEMATE:
\r
7431 if(gameInfo.variant != VariantShogi)
\r
7432 strcat(parseList[forwardMostMove - 1], "+");
\r
7434 case MT_CHECKMATE:
\r
7435 strcat(parseList[forwardMostMove - 1], "#");
\r
7438 if (appData.debugMode) {
\r
7439 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7444 /* Updates currentMove if not pausing */
\r
7446 ShowMove(fromX, fromY, toX, toY)
\r
7448 int instant = (gameMode == PlayFromGameFile) ?
\r
7449 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7450 if(appData.noGUI) return;
\r
7451 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7453 if (forwardMostMove == currentMove + 1) {
\r
7454 AnimateMove(boards[forwardMostMove - 1],
\r
7455 fromX, fromY, toX, toY);
\r
7457 if (appData.highlightLastMove) {
\r
7458 SetHighlights(fromX, fromY, toX, toY);
\r
7461 currentMove = forwardMostMove;
\r
7464 if (instant) return;
\r
7466 DisplayMove(currentMove - 1);
\r
7467 DrawPosition(FALSE, boards[currentMove]);
\r
7468 DisplayBothClocks();
\r
7469 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7472 void SendEgtPath(ChessProgramState *cps)
\r
7473 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7474 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7476 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7479 char c, *q = name+1, *r, *s;
\r
7481 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7482 while(*p && *p != ',') *q++ = *p++;
\r
7483 *q++ = ':'; *q = 0;
\r
7484 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7485 strcmp(name, ",nalimov:") == 0 ) {
\r
7486 // take nalimov path from the menu-changeable option first, if it is defined
\r
7487 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7488 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7490 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7491 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7492 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7493 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7494 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7495 c = *r; *r = 0; // temporarily null-terminate path info
\r
7496 *--q = 0; // strip of trailig ':' from name
\r
7497 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7499 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7501 if(*p == ',') p++; // read away comma to position for next format name
\r
7506 InitChessProgram(cps, setup)
\r
7507 ChessProgramState *cps;
\r
7508 int setup; /* [HGM] needed to setup FRC opening position */
\r
7510 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7511 if (appData.noChessProgram) return;
\r
7512 hintRequested = FALSE;
\r
7513 bookRequested = FALSE;
\r
7515 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7516 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7517 if(cps->memSize) { /* [HGM] memory */
\r
7518 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7519 SendToProgram(buf, cps);
\r
7521 SendEgtPath(cps); /* [HGM] EGT */
\r
7522 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7523 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7524 SendToProgram(buf, cps);
\r
7527 SendToProgram(cps->initString, cps);
\r
7528 if (gameInfo.variant != VariantNormal &&
\r
7529 gameInfo.variant != VariantLoadable
\r
7530 /* [HGM] also send variant if board size non-standard */
\r
7531 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7533 char *v = VariantName(gameInfo.variant);
\r
7534 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7535 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7536 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7537 DisplayFatalError(buf, 0, 1);
\r
7541 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7542 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7543 if( gameInfo.variant == VariantXiangqi )
\r
7544 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7545 if( gameInfo.variant == VariantShogi )
\r
7546 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7547 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7548 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7549 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7550 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7551 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7552 if( gameInfo.variant == VariantCourier )
\r
7553 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7554 if( gameInfo.variant == VariantSuper )
\r
7555 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7556 if( gameInfo.variant == VariantGreat )
\r
7557 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7560 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7561 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7562 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7563 if(StrStr(cps->variants, b) == NULL) {
\r
7564 // specific sized variant not known, check if general sizing allowed
\r
7565 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7566 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7567 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7568 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7569 DisplayFatalError(buf, 0, 1);
\r
7572 /* [HGM] here we really should compare with the maximum supported board size */
\r
7575 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7576 sprintf(buf, "variant %s\n", b);
\r
7577 SendToProgram(buf, cps);
\r
7579 currentlyInitializedVariant = gameInfo.variant;
\r
7581 /* [HGM] send opening position in FRC to first engine */
\r
7583 SendToProgram("force\n", cps);
\r
7584 SendBoard(cps, 0);
\r
7585 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7586 setboardSpoiledMachineBlack = 1;
\r
7589 if (cps->sendICS) {
\r
7590 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7591 SendToProgram(buf, cps);
\r
7593 cps->maybeThinking = FALSE;
\r
7594 cps->offeredDraw = 0;
\r
7595 if (!appData.icsActive) {
\r
7596 SendTimeControl(cps, movesPerSession, timeControl,
\r
7597 timeIncrement, appData.searchDepth,
\r
7600 if (appData.showThinking
\r
7601 // [HGM] thinking: four options require thinking output to be sent
\r
7602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7604 SendToProgram("post\n", cps);
\r
7606 SendToProgram("hard\n", cps);
\r
7607 if (!appData.ponderNextMove) {
\r
7608 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7609 it without being sure what state we are in first. "hard"
\r
7610 is not a toggle, so that one is OK.
\r
7612 SendToProgram("easy\n", cps);
\r
7614 if (cps->usePing) {
\r
7615 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7616 SendToProgram(buf, cps);
\r
7618 cps->initDone = TRUE;
\r
7623 StartChessProgram(cps)
\r
7624 ChessProgramState *cps;
\r
7626 char buf[MSG_SIZ];
\r
7629 if (appData.noChessProgram) return;
\r
7630 cps->initDone = FALSE;
\r
7632 if (strcmp(cps->host, "localhost") == 0) {
\r
7633 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7634 } else if (*appData.remoteShell == NULLCHAR) {
\r
7635 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7637 if (*appData.remoteUser == NULLCHAR) {
\r
7638 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7641 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7642 cps->host, appData.remoteUser, cps->program);
\r
7644 err = StartChildProcess(buf, "", &cps->pr);
\r
7648 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7649 DisplayFatalError(buf, err, 1);
\r
7655 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7656 if (cps->protocolVersion > 1) {
\r
7657 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7658 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7659 cps->comboCnt = 0; // and values of combo boxes
\r
7660 SendToProgram(buf, cps);
\r
7662 SendToProgram("xboard\n", cps);
\r
7668 TwoMachinesEventIfReady P((void))
\r
7670 if (first.lastPing != first.lastPong) {
\r
7671 DisplayMessage("", _("Waiting for first chess program"));
\r
7672 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7675 if (second.lastPing != second.lastPong) {
\r
7676 DisplayMessage("", _("Waiting for second chess program"));
\r
7677 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7681 TwoMachinesEvent();
\r
7685 NextMatchGame P((void))
\r
7687 int index; /* [HGM] autoinc: step lod index during match */
\r
7688 Reset(FALSE, TRUE);
\r
7689 if (*appData.loadGameFile != NULLCHAR) {
\r
7690 index = appData.loadGameIndex;
\r
7691 if(index < 0) { // [HGM] autoinc
\r
7692 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7693 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7695 LoadGameFromFile(appData.loadGameFile,
\r
7697 appData.loadGameFile, FALSE);
\r
7698 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7699 index = appData.loadPositionIndex;
\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 LoadPositionFromFile(appData.loadPositionFile,
\r
7706 appData.loadPositionFile);
\r
7708 TwoMachinesEventIfReady();
\r
7711 void UserAdjudicationEvent( int result )
\r
7713 ChessMove gameResult = GameIsDrawn;
\r
7715 if( result > 0 ) {
\r
7716 gameResult = WhiteWins;
\r
7718 else if( result < 0 ) {
\r
7719 gameResult = BlackWins;
\r
7722 if( gameMode == TwoMachinesPlay ) {
\r
7723 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7729 GameEnds(result, resultDetails, whosays)
\r
7731 char *resultDetails;
\r
7734 GameMode nextGameMode;
\r
7736 char buf[MSG_SIZ];
\r
7738 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7741 if (appData.debugMode) {
\r
7742 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7743 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7746 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7747 /* If we are playing on ICS, the server decides when the
\r
7748 game is over, but the engine can offer to draw, claim
\r
7749 a draw, or resign.
\r
7752 if (appData.zippyPlay && first.initDone) {
\r
7753 if (result == GameIsDrawn) {
\r
7754 /* In case draw still needs to be claimed */
\r
7755 SendToICS(ics_prefix);
\r
7756 SendToICS("draw\n");
\r
7757 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7758 SendToICS(ics_prefix);
\r
7759 SendToICS("resign\n");
\r
7763 endingGame = 0; /* [HGM] crash */
\r
7767 /* If we're loading the game from a file, stop */
\r
7768 if (whosays == GE_FILE) {
\r
7769 (void) StopLoadGameTimer();
\r
7770 gameFileFP = NULL;
\r
7773 /* Cancel draw offers */
\r
7774 first.offeredDraw = second.offeredDraw = 0;
\r
7776 /* If this is an ICS game, only ICS can really say it's done;
\r
7777 if not, anyone can. */
\r
7778 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7779 gameMode == IcsPlayingBlack ||
\r
7780 gameMode == IcsObserving ||
\r
7781 gameMode == IcsExamining);
\r
7783 if (!isIcsGame || whosays == GE_ICS) {
\r
7784 /* OK -- not an ICS game, or ICS said it was done */
\r
7786 if (!isIcsGame && !appData.noChessProgram)
\r
7787 SetUserThinkingEnables();
\r
7789 /* [HGM] if a machine claims the game end we verify this claim */
\r
7790 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7791 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7794 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7795 first.twoMachinesColor[0] :
\r
7796 second.twoMachinesColor[0] ;
\r
7797 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7798 (result == WhiteWins && claimer == 'w' ||
\r
7799 result == BlackWins && claimer == 'b' ) ) {
\r
7800 if (appData.debugMode) {
\r
7801 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7802 result, epStatus[forwardMostMove], forwardMostMove);
\r
7804 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7805 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7806 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7807 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7808 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7809 resultDetails = buf;
\r
7812 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7813 && (forwardMostMove <= backwardMostMove ||
\r
7814 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7815 (claimer=='b')==(forwardMostMove&1))
\r
7817 /* [HGM] verify: draws that were not flagged are false claims */
\r
7818 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7819 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7820 resultDetails = buf;
\r
7822 /* (Claiming a loss is accepted no questions asked!) */
\r
7824 /* [HGM] bare: don't allow bare King to win */
\r
7825 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7826 && result != GameIsDrawn)
\r
7827 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7828 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7829 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7830 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7832 if (appData.debugMode) {
\r
7833 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7834 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7837 result = GameIsDrawn;
\r
7838 sprintf(buf, "%s but bare king", resultDetails);
\r
7839 resultDetails = buf;
\r
7845 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7846 if(result==WhiteWins) c = '+';
\r
7847 if(result==BlackWins) c = '-';
\r
7848 if(resultDetails != NULL)
\r
7849 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7851 if (resultDetails != NULL) {
\r
7852 gameInfo.result = result;
\r
7853 gameInfo.resultDetails = StrSave(resultDetails);
\r
7855 /* display last move only if game was not loaded from file */
\r
7856 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7857 DisplayMove(currentMove - 1);
\r
7859 if (forwardMostMove != 0) {
\r
7860 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7861 if (*appData.saveGameFile != NULLCHAR) {
\r
7862 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7863 } else if (appData.autoSaveGames) {
\r
7866 if (*appData.savePositionFile != NULLCHAR) {
\r
7867 SavePositionToFile(appData.savePositionFile);
\r
7872 /* Tell program how game ended in case it is learning */
\r
7873 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7874 /* engine died and we got here through time loss. In that */
\r
7875 /* case we will get a fatal error writing the pipe, which */
\r
7876 /* would otherwise lose us the PGN. */
\r
7877 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7878 /* output during GameEnds should never be fatal anymore */
\r
7879 if (gameMode == MachinePlaysWhite ||
\r
7880 gameMode == MachinePlaysBlack ||
\r
7881 gameMode == TwoMachinesPlay ||
\r
7882 gameMode == IcsPlayingWhite ||
\r
7883 gameMode == IcsPlayingBlack ||
\r
7884 gameMode == BeginningOfGame) {
\r
7885 char buf[MSG_SIZ];
\r
7886 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7888 if (first.pr != NoProc) {
\r
7889 SendToProgram(buf, &first);
\r
7891 if (second.pr != NoProc &&
\r
7892 gameMode == TwoMachinesPlay) {
\r
7893 SendToProgram(buf, &second);
\r
7898 if (appData.icsActive) {
\r
7899 if (appData.quietPlay &&
\r
7900 (gameMode == IcsPlayingWhite ||
\r
7901 gameMode == IcsPlayingBlack)) {
\r
7902 SendToICS(ics_prefix);
\r
7903 SendToICS("set shout 1\n");
\r
7905 nextGameMode = IcsIdle;
\r
7906 ics_user_moved = FALSE;
\r
7907 /* clean up premove. It's ugly when the game has ended and the
\r
7908 * premove highlights are still on the board.
\r
7911 gotPremove = FALSE;
\r
7912 ClearPremoveHighlights();
\r
7913 DrawPosition(FALSE, boards[currentMove]);
\r
7915 if (whosays == GE_ICS) {
\r
7918 if (gameMode == IcsPlayingWhite)
\r
7919 PlayIcsWinSound();
\r
7920 else if(gameMode == IcsPlayingBlack)
\r
7921 PlayIcsLossSound();
\r
7924 if (gameMode == IcsPlayingBlack)
\r
7925 PlayIcsWinSound();
\r
7926 else if(gameMode == IcsPlayingWhite)
\r
7927 PlayIcsLossSound();
\r
7930 PlayIcsDrawSound();
\r
7933 PlayIcsUnfinishedSound();
\r
7936 } else if (gameMode == EditGame ||
\r
7937 gameMode == PlayFromGameFile ||
\r
7938 gameMode == AnalyzeMode ||
\r
7939 gameMode == AnalyzeFile) {
\r
7940 nextGameMode = gameMode;
\r
7942 nextGameMode = EndOfGame;
\r
7947 nextGameMode = gameMode;
\r
7950 if (appData.noChessProgram) {
\r
7951 gameMode = nextGameMode;
\r
7953 endingGame = 0; /* [HGM] crash */
\r
7957 if (first.reuse) {
\r
7958 /* Put first chess program into idle state */
\r
7959 if (first.pr != NoProc &&
\r
7960 (gameMode == MachinePlaysWhite ||
\r
7961 gameMode == MachinePlaysBlack ||
\r
7962 gameMode == TwoMachinesPlay ||
\r
7963 gameMode == IcsPlayingWhite ||
\r
7964 gameMode == IcsPlayingBlack ||
\r
7965 gameMode == BeginningOfGame)) {
\r
7966 SendToProgram("force\n", &first);
\r
7967 if (first.usePing) {
\r
7968 char buf[MSG_SIZ];
\r
7969 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7970 SendToProgram(buf, &first);
\r
7973 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7974 /* Kill off first chess program */
\r
7975 if (first.isr != NULL)
\r
7976 RemoveInputSource(first.isr);
\r
7979 if (first.pr != NoProc) {
\r
7980 ExitAnalyzeMode();
\r
7981 DoSleep( appData.delayBeforeQuit );
\r
7982 SendToProgram("quit\n", &first);
\r
7983 DoSleep( appData.delayAfterQuit );
\r
7984 DestroyChildProcess(first.pr, first.useSigterm);
\r
7986 first.pr = NoProc;
\r
7988 if (second.reuse) {
\r
7989 /* Put second chess program into idle state */
\r
7990 if (second.pr != NoProc &&
\r
7991 gameMode == TwoMachinesPlay) {
\r
7992 SendToProgram("force\n", &second);
\r
7993 if (second.usePing) {
\r
7994 char buf[MSG_SIZ];
\r
7995 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7996 SendToProgram(buf, &second);
\r
7999 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8000 /* Kill off second chess program */
\r
8001 if (second.isr != NULL)
\r
8002 RemoveInputSource(second.isr);
\r
8003 second.isr = NULL;
\r
8005 if (second.pr != NoProc) {
\r
8006 DoSleep( appData.delayBeforeQuit );
\r
8007 SendToProgram("quit\n", &second);
\r
8008 DoSleep( appData.delayAfterQuit );
\r
8009 DestroyChildProcess(second.pr, second.useSigterm);
\r
8011 second.pr = NoProc;
\r
8014 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8017 if (first.twoMachinesColor[0] == 'w') {
\r
8018 first.matchWins++;
\r
8020 second.matchWins++;
\r
8024 if (first.twoMachinesColor[0] == 'b') {
\r
8025 first.matchWins++;
\r
8027 second.matchWins++;
\r
8033 if (matchGame < appData.matchGames) {
\r
8035 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8036 tmp = first.twoMachinesColor;
\r
8037 first.twoMachinesColor = second.twoMachinesColor;
\r
8038 second.twoMachinesColor = tmp;
\r
8040 gameMode = nextGameMode;
\r
8042 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8043 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8044 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8045 endingGame = 0; /* [HGM] crash */
\r
8048 char buf[MSG_SIZ];
\r
8049 gameMode = nextGameMode;
\r
8050 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8051 first.tidy, second.tidy,
\r
8052 first.matchWins, second.matchWins,
\r
8053 appData.matchGames - (first.matchWins + second.matchWins));
\r
8054 DisplayFatalError(buf, 0, 0);
\r
8057 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8058 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8059 ExitAnalyzeMode();
\r
8060 gameMode = nextGameMode;
\r
8062 endingGame = 0; /* [HGM] crash */
\r
8065 /* Assumes program was just initialized (initString sent).
\r
8066 Leaves program in force mode. */
\r
8068 FeedMovesToProgram(cps, upto)
\r
8069 ChessProgramState *cps;
\r
8074 if (appData.debugMode)
\r
8075 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8076 startedFromSetupPosition ? "position and " : "",
\r
8077 backwardMostMove, upto, cps->which);
\r
8078 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8079 // [HGM] variantswitch: make engine aware of new variant
\r
8080 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8081 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8082 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8083 SendToProgram(buf, cps);
\r
8084 currentlyInitializedVariant = gameInfo.variant;
\r
8086 SendToProgram("force\n", cps);
\r
8087 if (startedFromSetupPosition) {
\r
8088 SendBoard(cps, backwardMostMove);
\r
8089 if (appData.debugMode) {
\r
8090 fprintf(debugFP, "feedMoves\n");
\r
8093 for (i = backwardMostMove; i < upto; i++) {
\r
8094 SendMoveToProgram(i, cps);
\r
8100 ResurrectChessProgram()
\r
8102 /* The chess program may have exited.
\r
8103 If so, restart it and feed it all the moves made so far. */
\r
8105 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8107 StartChessProgram(&first);
\r
8108 InitChessProgram(&first, FALSE);
\r
8109 FeedMovesToProgram(&first, currentMove);
\r
8111 if (!first.sendTime) {
\r
8112 /* can't tell gnuchess what its clock should read,
\r
8113 so we bow to its notion. */
\r
8115 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8116 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8119 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8120 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8121 SendToProgram("analyze\n", &first);
\r
8122 first.analyzing = TRUE;
\r
8127 * Button procedures
\r
8130 Reset(redraw, init)
\r
8135 if (appData.debugMode) {
\r
8136 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8137 redraw, init, gameMode);
\r
8139 pausing = pauseExamInvalid = FALSE;
\r
8140 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8142 whiteFlag = blackFlag = FALSE;
\r
8143 userOfferedDraw = FALSE;
\r
8144 hintRequested = bookRequested = FALSE;
\r
8145 first.maybeThinking = FALSE;
\r
8146 second.maybeThinking = FALSE;
\r
8147 first.bookSuspend = FALSE; // [HGM] book
\r
8148 second.bookSuspend = FALSE;
\r
8149 thinkOutput[0] = NULLCHAR;
\r
8150 lastHint[0] = NULLCHAR;
\r
8151 ClearGameInfo(&gameInfo);
\r
8152 gameInfo.variant = StringToVariant(appData.variant);
\r
8153 ics_user_moved = ics_clock_paused = FALSE;
\r
8154 ics_getting_history = H_FALSE;
\r
8156 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8157 ClearProgramStats();
\r
8158 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8161 ClearHighlights();
\r
8162 flipView = appData.flipView;
\r
8163 ClearPremoveHighlights();
\r
8164 gotPremove = FALSE;
\r
8165 alarmSounded = FALSE;
\r
8167 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8168 if(appData.serverMovesName != NULL) {
\r
8169 /* [HGM] prepare to make moves file for broadcasting */
\r
8170 clock_t t = clock();
\r
8171 if(serverMoves != NULL) fclose(serverMoves);
\r
8172 serverMoves = fopen(appData.serverMovesName, "r");
\r
8173 if(serverMoves != NULL) {
\r
8174 fclose(serverMoves);
\r
8175 /* delay 15 sec before overwriting, so all clients can see end */
\r
8176 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8178 serverMoves = fopen(appData.serverMovesName, "w");
\r
8181 ExitAnalyzeMode();
\r
8182 gameMode = BeginningOfGame;
\r
8184 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8185 InitPosition(redraw);
\r
8186 for (i = 0; i < MAX_MOVES; i++) {
\r
8187 if (commentList[i] != NULL) {
\r
8188 free(commentList[i]);
\r
8189 commentList[i] = NULL;
\r
8193 timeRemaining[0][0] = whiteTimeRemaining;
\r
8194 timeRemaining[1][0] = blackTimeRemaining;
\r
8195 if (first.pr == NULL) {
\r
8196 StartChessProgram(&first);
\r
8199 InitChessProgram(&first, startedFromSetupPosition);
\r
8202 DisplayMessage("", "");
\r
8203 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8207 AutoPlayGameLoop()
\r
8210 if (!AutoPlayOneMove())
\r
8212 if (matchMode || appData.timeDelay == 0)
\r
8214 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8216 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8225 int fromX, fromY, toX, toY;
\r
8227 if (appData.debugMode) {
\r
8228 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8231 if (gameMode != PlayFromGameFile)
\r
8234 if (currentMove >= forwardMostMove) {
\r
8235 gameMode = EditGame;
\r
8238 /* [AS] Clear current move marker at the end of a game */
\r
8239 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8244 toX = moveList[currentMove][2] - AAA;
\r
8245 toY = moveList[currentMove][3] - ONE;
\r
8247 if (moveList[currentMove][1] == '@') {
\r
8248 if (appData.highlightLastMove) {
\r
8249 SetHighlights(-1, -1, toX, toY);
\r
8252 fromX = moveList[currentMove][0] - AAA;
\r
8253 fromY = moveList[currentMove][1] - ONE;
\r
8255 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8257 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8259 if (appData.highlightLastMove) {
\r
8260 SetHighlights(fromX, fromY, toX, toY);
\r
8263 DisplayMove(currentMove);
\r
8264 SendMoveToProgram(currentMove++, &first);
\r
8265 DisplayBothClocks();
\r
8266 DrawPosition(FALSE, boards[currentMove]);
\r
8267 // [HGM] PV info: always display, routine tests if empty
\r
8268 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8274 LoadGameOneMove(readAhead)
\r
8275 ChessMove readAhead;
\r
8277 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8278 char promoChar = NULLCHAR;
\r
8279 ChessMove moveType;
\r
8280 char move[MSG_SIZ];
\r
8283 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8284 gameMode != AnalyzeMode && gameMode != Training) {
\r
8285 gameFileFP = NULL;
\r
8289 yyboardindex = forwardMostMove;
\r
8290 if (readAhead != (ChessMove)0) {
\r
8291 moveType = readAhead;
\r
8293 if (gameFileFP == NULL)
\r
8295 moveType = (ChessMove) yylex();
\r
8299 switch (moveType) {
\r
8301 if (appData.debugMode)
\r
8302 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8304 if (*p == '{' || *p == '[' || *p == '(') {
\r
8305 p[strlen(p) - 1] = NULLCHAR;
\r
8309 /* append the comment but don't display it */
\r
8310 while (*p == '\n') p++;
\r
8311 AppendComment(currentMove, p);
\r
8314 case WhiteCapturesEnPassant:
\r
8315 case BlackCapturesEnPassant:
\r
8316 case WhitePromotionChancellor:
\r
8317 case BlackPromotionChancellor:
\r
8318 case WhitePromotionArchbishop:
\r
8319 case BlackPromotionArchbishop:
\r
8320 case WhitePromotionCentaur:
\r
8321 case BlackPromotionCentaur:
\r
8322 case WhitePromotionQueen:
\r
8323 case BlackPromotionQueen:
\r
8324 case WhitePromotionRook:
\r
8325 case BlackPromotionRook:
\r
8326 case WhitePromotionBishop:
\r
8327 case BlackPromotionBishop:
\r
8328 case WhitePromotionKnight:
\r
8329 case BlackPromotionKnight:
\r
8330 case WhitePromotionKing:
\r
8331 case BlackPromotionKing:
\r
8333 case WhiteKingSideCastle:
\r
8334 case WhiteQueenSideCastle:
\r
8335 case BlackKingSideCastle:
\r
8336 case BlackQueenSideCastle:
\r
8337 case WhiteKingSideCastleWild:
\r
8338 case WhiteQueenSideCastleWild:
\r
8339 case BlackKingSideCastleWild:
\r
8340 case BlackQueenSideCastleWild:
\r
8342 case WhiteHSideCastleFR:
\r
8343 case WhiteASideCastleFR:
\r
8344 case BlackHSideCastleFR:
\r
8345 case BlackASideCastleFR:
\r
8347 if (appData.debugMode)
\r
8348 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8349 fromX = currentMoveString[0] - AAA;
\r
8350 fromY = currentMoveString[1] - ONE;
\r
8351 toX = currentMoveString[2] - AAA;
\r
8352 toY = currentMoveString[3] - ONE;
\r
8353 promoChar = currentMoveString[4];
\r
8358 if (appData.debugMode)
\r
8359 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8360 fromX = moveType == WhiteDrop ?
\r
8361 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8362 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8363 fromY = DROP_RANK;
\r
8364 toX = currentMoveString[2] - AAA;
\r
8365 toY = currentMoveString[3] - ONE;
\r
8371 case GameUnfinished:
\r
8372 if (appData.debugMode)
\r
8373 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8374 p = strchr(yy_text, '{');
\r
8375 if (p == NULL) p = strchr(yy_text, '(');
\r
8378 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8380 q = strchr(p, *p == '{' ? '}' : ')');
\r
8381 if (q != NULL) *q = NULLCHAR;
\r
8384 GameEnds(moveType, p, GE_FILE);
\r
8386 if (cmailMsgLoaded) {
\r
8387 ClearHighlights();
\r
8388 flipView = WhiteOnMove(currentMove);
\r
8389 if (moveType == GameUnfinished) flipView = !flipView;
\r
8390 if (appData.debugMode)
\r
8391 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8395 case (ChessMove) 0: /* end of file */
\r
8396 if (appData.debugMode)
\r
8397 fprintf(debugFP, "Parser hit end of file\n");
\r
8398 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8399 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8403 case MT_CHECKMATE:
\r
8404 if (WhiteOnMove(currentMove)) {
\r
8405 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8407 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8410 case MT_STALEMATE:
\r
8411 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8417 case MoveNumberOne:
\r
8418 if (lastLoadGameStart == GNUChessGame) {
\r
8419 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8420 if (appData.debugMode)
\r
8421 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8422 yy_text, (int) moveType);
\r
8423 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8425 /* else fall thru */
\r
8428 case GNUChessGame:
\r
8430 /* Reached start of next game in file */
\r
8431 if (appData.debugMode)
\r
8432 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8433 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8434 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8438 case MT_CHECKMATE:
\r
8439 if (WhiteOnMove(currentMove)) {
\r
8440 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8442 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8445 case MT_STALEMATE:
\r
8446 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8452 case PositionDiagram: /* should not happen; ignore */
\r
8453 case ElapsedTime: /* ignore */
\r
8454 case NAG: /* ignore */
\r
8455 if (appData.debugMode)
\r
8456 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8457 yy_text, (int) moveType);
\r
8458 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8461 if (appData.testLegality) {
\r
8462 if (appData.debugMode)
\r
8463 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8464 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8465 (forwardMostMove / 2) + 1,
\r
8466 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8467 DisplayError(move, 0);
\r
8470 if (appData.debugMode)
\r
8471 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8472 yy_text, currentMoveString);
\r
8473 fromX = currentMoveString[0] - AAA;
\r
8474 fromY = currentMoveString[1] - ONE;
\r
8475 toX = currentMoveString[2] - AAA;
\r
8476 toY = currentMoveString[3] - ONE;
\r
8477 promoChar = currentMoveString[4];
\r
8481 case AmbiguousMove:
\r
8482 if (appData.debugMode)
\r
8483 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8484 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8485 (forwardMostMove / 2) + 1,
\r
8486 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8487 DisplayError(move, 0);
\r
8492 case ImpossibleMove:
\r
8493 if (appData.debugMode)
\r
8494 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8495 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8496 (forwardMostMove / 2) + 1,
\r
8497 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8498 DisplayError(move, 0);
\r
8504 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8505 DrawPosition(FALSE, boards[currentMove]);
\r
8506 DisplayBothClocks();
\r
8507 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8508 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8510 (void) StopLoadGameTimer();
\r
8511 gameFileFP = NULL;
\r
8512 cmailOldMove = forwardMostMove;
\r
8515 /* currentMoveString is set as a side-effect of yylex */
\r
8516 strcat(currentMoveString, "\n");
\r
8517 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8519 thinkOutput[0] = NULLCHAR;
\r
8520 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8521 currentMove = forwardMostMove;
\r
8526 /* Load the nth game from the given file */
\r
8528 LoadGameFromFile(filename, n, title, useList)
\r
8532 /*Boolean*/ int useList;
\r
8535 char buf[MSG_SIZ];
\r
8537 if (strcmp(filename, "-") == 0) {
\r
8541 f = fopen(filename, "rb");
\r
8543 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8544 DisplayError(buf, errno);
\r
8548 if (fseek(f, 0, 0) == -1) {
\r
8549 /* f is not seekable; probably a pipe */
\r
8552 if (useList && n == 0) {
\r
8553 int error = GameListBuild(f);
\r
8555 DisplayError(_("Cannot build game list"), error);
\r
8556 } else if (!ListEmpty(&gameList) &&
\r
8557 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8558 GameListPopUp(f, title);
\r
8561 GameListDestroy();
\r
8564 if (n == 0) n = 1;
\r
8565 return LoadGame(f, n, title, FALSE);
\r
8570 MakeRegisteredMove()
\r
8572 int fromX, fromY, toX, toY;
\r
8574 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8575 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8578 if (appData.debugMode)
\r
8579 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8580 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8582 thinkOutput[0] = NULLCHAR;
\r
8583 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8584 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8585 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8586 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8587 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8588 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8589 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8590 ShowMove(fromX, fromY, toX, toY);
\r
8592 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8593 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8598 case MT_CHECKMATE:
\r
8599 if (WhiteOnMove(currentMove)) {
\r
8600 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8602 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8606 case MT_STALEMATE:
\r
8607 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8613 case CMAIL_RESIGN:
\r
8614 if (WhiteOnMove(currentMove)) {
\r
8615 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8617 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8621 case CMAIL_ACCEPT:
\r
8622 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8633 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8635 CmailLoadGame(f, gameNumber, title, useList)
\r
8643 if (gameNumber > nCmailGames) {
\r
8644 DisplayError(_("No more games in this message"), 0);
\r
8647 if (f == lastLoadGameFP) {
\r
8648 int offset = gameNumber - lastLoadGameNumber;
\r
8649 if (offset == 0) {
\r
8650 cmailMsg[0] = NULLCHAR;
\r
8651 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8652 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8653 nCmailMovesRegistered--;
\r
8655 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8656 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8657 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8660 if (! RegisterMove()) return FALSE;
\r
8664 retVal = LoadGame(f, gameNumber, title, useList);
\r
8666 /* Make move registered during previous look at this game, if any */
\r
8667 MakeRegisteredMove();
\r
8669 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8670 commentList[currentMove]
\r
8671 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8672 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8678 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8680 ReloadGame(offset)
\r
8683 int gameNumber = lastLoadGameNumber + offset;
\r
8684 if (lastLoadGameFP == NULL) {
\r
8685 DisplayError(_("No game has been loaded yet"), 0);
\r
8688 if (gameNumber <= 0) {
\r
8689 DisplayError(_("Can't back up any further"), 0);
\r
8692 if (cmailMsgLoaded) {
\r
8693 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8694 lastLoadGameTitle, lastLoadGameUseList);
\r
8696 return LoadGame(lastLoadGameFP, gameNumber,
\r
8697 lastLoadGameTitle, lastLoadGameUseList);
\r
8703 /* Load the nth game from open file f */
\r
8705 LoadGame(f, gameNumber, title, useList)
\r
8712 char buf[MSG_SIZ];
\r
8713 int gn = gameNumber;
\r
8714 ListGame *lg = NULL;
\r
8715 int numPGNTags = 0;
\r
8717 GameMode oldGameMode;
\r
8718 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8720 if (appData.debugMode)
\r
8721 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8723 if (gameMode == Training )
\r
8724 SetTrainingModeOff();
\r
8726 oldGameMode = gameMode;
\r
8727 if (gameMode != BeginningOfGame) {
\r
8728 Reset(FALSE, TRUE);
\r
8732 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8733 fclose(lastLoadGameFP);
\r
8737 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8740 fseek(f, lg->offset, 0);
\r
8741 GameListHighlight(gameNumber);
\r
8745 DisplayError(_("Game number out of range"), 0);
\r
8749 GameListDestroy();
\r
8750 if (fseek(f, 0, 0) == -1) {
\r
8751 if (f == lastLoadGameFP ?
\r
8752 gameNumber == lastLoadGameNumber + 1 :
\r
8753 gameNumber == 1) {
\r
8756 DisplayError(_("Can't seek on game file"), 0);
\r
8761 lastLoadGameFP = f;
\r
8762 lastLoadGameNumber = gameNumber;
\r
8763 strcpy(lastLoadGameTitle, title);
\r
8764 lastLoadGameUseList = useList;
\r
8768 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8769 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8770 lg->gameInfo.black);
\r
8771 DisplayTitle(buf);
\r
8772 } else if (*title != NULLCHAR) {
\r
8773 if (gameNumber > 1) {
\r
8774 sprintf(buf, "%s %d", title, gameNumber);
\r
8775 DisplayTitle(buf);
\r
8777 DisplayTitle(title);
\r
8781 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8782 gameMode = PlayFromGameFile;
\r
8786 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8787 CopyBoard(boards[0], initialPosition);
\r
8791 * Skip the first gn-1 games in the file.
\r
8792 * Also skip over anything that precedes an identifiable
\r
8793 * start of game marker, to avoid being confused by
\r
8794 * garbage at the start of the file. Currently
\r
8795 * recognized start of game markers are the move number "1",
\r
8796 * the pattern "gnuchess .* game", the pattern
\r
8797 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8798 * A game that starts with one of the latter two patterns
\r
8799 * will also have a move number 1, possibly
\r
8800 * following a position diagram.
\r
8801 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8802 * start with an unnumbered move. Does that break anything?
\r
8804 cm = lastLoadGameStart = (ChessMove) 0;
\r
8806 yyboardindex = forwardMostMove;
\r
8807 cm = (ChessMove) yylex();
\r
8809 case (ChessMove) 0:
\r
8810 if (cmailMsgLoaded) {
\r
8811 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8813 Reset(TRUE, TRUE);
\r
8814 DisplayError(_("Game not found in file"), 0);
\r
8818 case GNUChessGame:
\r
8821 lastLoadGameStart = cm;
\r
8824 case MoveNumberOne:
\r
8825 switch (lastLoadGameStart) {
\r
8826 case GNUChessGame:
\r
8830 case MoveNumberOne:
\r
8831 case (ChessMove) 0:
\r
8832 gn--; /* count this game */
\r
8833 lastLoadGameStart = cm;
\r
8842 switch (lastLoadGameStart) {
\r
8843 case GNUChessGame:
\r
8845 case MoveNumberOne:
\r
8846 case (ChessMove) 0:
\r
8847 gn--; /* count this game */
\r
8848 lastLoadGameStart = cm;
\r
8851 lastLoadGameStart = cm; /* game counted already */
\r
8859 yyboardindex = forwardMostMove;
\r
8860 cm = (ChessMove) yylex();
\r
8861 } while (cm == PGNTag || cm == Comment);
\r
8868 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8869 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8870 != CMAIL_OLD_RESULT) {
\r
8871 nCmailResults ++ ;
\r
8872 cmailResult[ CMAIL_MAX_GAMES
\r
8873 - gn - 1] = CMAIL_OLD_RESULT;
\r
8879 /* Only a NormalMove can be at the start of a game
\r
8880 * without a position diagram. */
\r
8881 if (lastLoadGameStart == (ChessMove) 0) {
\r
8883 lastLoadGameStart = MoveNumberOne;
\r
8892 if (appData.debugMode)
\r
8893 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8895 if (cm == XBoardGame) {
\r
8896 /* Skip any header junk before position diagram and/or move 1 */
\r
8898 yyboardindex = forwardMostMove;
\r
8899 cm = (ChessMove) yylex();
\r
8901 if (cm == (ChessMove) 0 ||
\r
8902 cm == GNUChessGame || cm == XBoardGame) {
\r
8903 /* Empty game; pretend end-of-file and handle later */
\r
8904 cm = (ChessMove) 0;
\r
8908 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8909 cm == PGNTag || cm == Comment)
\r
8912 } else if (cm == GNUChessGame) {
\r
8913 if (gameInfo.event != NULL) {
\r
8914 free(gameInfo.event);
\r
8916 gameInfo.event = StrSave(yy_text);
\r
8919 startedFromSetupPosition = FALSE;
\r
8920 while (cm == PGNTag) {
\r
8921 if (appData.debugMode)
\r
8922 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8923 err = ParsePGNTag(yy_text, &gameInfo);
\r
8924 if (!err) numPGNTags++;
\r
8926 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8927 if(gameInfo.variant != oldVariant) {
\r
8928 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8929 InitPosition(TRUE);
\r
8930 oldVariant = gameInfo.variant;
\r
8931 if (appData.debugMode)
\r
8932 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8936 if (gameInfo.fen != NULL) {
\r
8937 Board initial_position;
\r
8938 startedFromSetupPosition = TRUE;
\r
8939 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8940 Reset(TRUE, TRUE);
\r
8941 DisplayError(_("Bad FEN position in file"), 0);
\r
8944 CopyBoard(boards[0], initial_position);
\r
8945 if (blackPlaysFirst) {
\r
8946 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8947 CopyBoard(boards[1], initial_position);
\r
8948 strcpy(moveList[0], "");
\r
8949 strcpy(parseList[0], "");
\r
8950 timeRemaining[0][1] = whiteTimeRemaining;
\r
8951 timeRemaining[1][1] = blackTimeRemaining;
\r
8952 if (commentList[0] != NULL) {
\r
8953 commentList[1] = commentList[0];
\r
8954 commentList[0] = NULL;
\r
8957 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8959 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8961 initialRulePlies = FENrulePlies;
\r
8962 epStatus[forwardMostMove] = FENepStatus;
\r
8963 for( i=0; i< nrCastlingRights; i++ )
\r
8964 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8966 yyboardindex = forwardMostMove;
\r
8967 free(gameInfo.fen);
\r
8968 gameInfo.fen = NULL;
\r
8971 yyboardindex = forwardMostMove;
\r
8972 cm = (ChessMove) yylex();
\r
8974 /* Handle comments interspersed among the tags */
\r
8975 while (cm == Comment) {
\r
8977 if (appData.debugMode)
\r
8978 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8980 if (*p == '{' || *p == '[' || *p == '(') {
\r
8981 p[strlen(p) - 1] = NULLCHAR;
\r
8984 while (*p == '\n') p++;
\r
8985 AppendComment(currentMove, p);
\r
8986 yyboardindex = forwardMostMove;
\r
8987 cm = (ChessMove) yylex();
\r
8991 /* don't rely on existence of Event tag since if game was
\r
8992 * pasted from clipboard the Event tag may not exist
\r
8994 if (numPGNTags > 0){
\r
8996 if (gameInfo.variant == VariantNormal) {
\r
8997 gameInfo.variant = StringToVariant(gameInfo.event);
\r
9000 if( appData.autoDisplayTags ) {
\r
9001 tags = PGNTags(&gameInfo);
\r
9002 TagsPopUp(tags, CmailMsg());
\r
9007 /* Make something up, but don't display it now */
\r
9012 if (cm == PositionDiagram) {
\r
9015 Board initial_position;
\r
9017 if (appData.debugMode)
\r
9018 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9020 if (!startedFromSetupPosition) {
\r
9022 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9023 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9033 initial_position[i][j++] = CharToPiece(*p);
\r
9036 while (*p == ' ' || *p == '\t' ||
\r
9037 *p == '\n' || *p == '\r') p++;
\r
9039 if (strncmp(p, "black", strlen("black"))==0)
\r
9040 blackPlaysFirst = TRUE;
\r
9042 blackPlaysFirst = FALSE;
\r
9043 startedFromSetupPosition = TRUE;
\r
9045 CopyBoard(boards[0], initial_position);
\r
9046 if (blackPlaysFirst) {
\r
9047 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9048 CopyBoard(boards[1], initial_position);
\r
9049 strcpy(moveList[0], "");
\r
9050 strcpy(parseList[0], "");
\r
9051 timeRemaining[0][1] = whiteTimeRemaining;
\r
9052 timeRemaining[1][1] = blackTimeRemaining;
\r
9053 if (commentList[0] != NULL) {
\r
9054 commentList[1] = commentList[0];
\r
9055 commentList[0] = NULL;
\r
9058 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9061 yyboardindex = forwardMostMove;
\r
9062 cm = (ChessMove) yylex();
\r
9065 if (first.pr == NoProc) {
\r
9066 StartChessProgram(&first);
\r
9068 InitChessProgram(&first, FALSE);
\r
9069 SendToProgram("force\n", &first);
\r
9070 if (startedFromSetupPosition) {
\r
9071 SendBoard(&first, forwardMostMove);
\r
9072 if (appData.debugMode) {
\r
9073 fprintf(debugFP, "Load Game\n");
\r
9075 DisplayBothClocks();
\r
9078 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9079 loadFlag = appData.suppressLoadMoves;
\r
9081 while (cm == Comment) {
\r
9083 if (appData.debugMode)
\r
9084 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9086 if (*p == '{' || *p == '[' || *p == '(') {
\r
9087 p[strlen(p) - 1] = NULLCHAR;
\r
9090 while (*p == '\n') p++;
\r
9091 AppendComment(currentMove, p);
\r
9092 yyboardindex = forwardMostMove;
\r
9093 cm = (ChessMove) yylex();
\r
9096 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9097 cm == WhiteWins || cm == BlackWins ||
\r
9098 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9099 DisplayMessage("", _("No moves in game"));
\r
9100 if (cmailMsgLoaded) {
\r
9101 if (appData.debugMode)
\r
9102 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9103 ClearHighlights();
\r
9106 DrawPosition(FALSE, boards[currentMove]);
\r
9107 DisplayBothClocks();
\r
9108 gameMode = EditGame;
\r
9110 gameFileFP = NULL;
\r
9115 // [HGM] PV info: routine tests if comment empty
\r
9116 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9117 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9119 if (!matchMode && appData.timeDelay != 0)
\r
9120 DrawPosition(FALSE, boards[currentMove]);
\r
9122 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9123 programStats.ok_to_send = 1;
\r
9126 /* if the first token after the PGN tags is a move
\r
9127 * and not move number 1, retrieve it from the parser
\r
9129 if (cm != MoveNumberOne)
\r
9130 LoadGameOneMove(cm);
\r
9132 /* load the remaining moves from the file */
\r
9133 while (LoadGameOneMove((ChessMove)0)) {
\r
9134 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9135 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9138 /* rewind to the start of the game */
\r
9139 currentMove = backwardMostMove;
\r
9141 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9143 if (oldGameMode == AnalyzeFile ||
\r
9144 oldGameMode == AnalyzeMode) {
\r
9145 AnalyzeFileEvent();
\r
9148 if (matchMode || appData.timeDelay == 0) {
\r
9150 gameMode = EditGame;
\r
9152 } else if (appData.timeDelay > 0) {
\r
9153 AutoPlayGameLoop();
\r
9156 if (appData.debugMode)
\r
9157 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9159 loadFlag = 0; /* [HGM] true game starts */
\r
9163 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9165 ReloadPosition(offset)
\r
9168 int positionNumber = lastLoadPositionNumber + offset;
\r
9169 if (lastLoadPositionFP == NULL) {
\r
9170 DisplayError(_("No position has been loaded yet"), 0);
\r
9173 if (positionNumber <= 0) {
\r
9174 DisplayError(_("Can't back up any further"), 0);
\r
9177 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9178 lastLoadPositionTitle);
\r
9181 /* Load the nth position from the given file */
\r
9183 LoadPositionFromFile(filename, n, title)
\r
9189 char buf[MSG_SIZ];
\r
9191 if (strcmp(filename, "-") == 0) {
\r
9192 return LoadPosition(stdin, n, "stdin");
\r
9194 f = fopen(filename, "rb");
\r
9196 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9197 DisplayError(buf, errno);
\r
9200 return LoadPosition(f, n, title);
\r
9205 /* Load the nth position from the given open file, and close it */
\r
9207 LoadPosition(f, positionNumber, title)
\r
9209 int positionNumber;
\r
9212 char *p, line[MSG_SIZ];
\r
9213 Board initial_position;
\r
9214 int i, j, fenMode, pn;
\r
9216 if (gameMode == Training )
\r
9217 SetTrainingModeOff();
\r
9219 if (gameMode != BeginningOfGame) {
\r
9220 Reset(FALSE, TRUE);
\r
9222 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9223 fclose(lastLoadPositionFP);
\r
9225 if (positionNumber == 0) positionNumber = 1;
\r
9226 lastLoadPositionFP = f;
\r
9227 lastLoadPositionNumber = positionNumber;
\r
9228 strcpy(lastLoadPositionTitle, title);
\r
9229 if (first.pr == NoProc) {
\r
9230 StartChessProgram(&first);
\r
9231 InitChessProgram(&first, FALSE);
\r
9233 pn = positionNumber;
\r
9234 if (positionNumber < 0) {
\r
9235 /* Negative position number means to seek to that byte offset */
\r
9236 if (fseek(f, -positionNumber, 0) == -1) {
\r
9237 DisplayError(_("Can't seek on position file"), 0);
\r
9242 if (fseek(f, 0, 0) == -1) {
\r
9243 if (f == lastLoadPositionFP ?
\r
9244 positionNumber == lastLoadPositionNumber + 1 :
\r
9245 positionNumber == 1) {
\r
9248 DisplayError(_("Can't seek on position file"), 0);
\r
9253 /* See if this file is FEN or old-style xboard */
\r
9254 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9255 DisplayError(_("Position not found in file"), 0);
\r
9259 switch (line[0]) {
\r
9260 case '#': case 'x':
\r
9264 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9265 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9266 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9267 case '7': case '8': case '9':
\r
9268 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9269 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9270 case 'C': case 'W': case 'c': case 'w':
\r
9275 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9276 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9280 if (fenMode || line[0] == '#') pn--;
\r
9282 /* skip positions before number pn */
\r
9283 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9284 Reset(TRUE, TRUE);
\r
9285 DisplayError(_("Position not found in file"), 0);
\r
9288 if (fenMode || line[0] == '#') pn--;
\r
9293 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9294 DisplayError(_("Bad FEN position in file"), 0);
\r
9298 (void) fgets(line, MSG_SIZ, f);
\r
9299 (void) fgets(line, MSG_SIZ, f);
\r
9301 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9302 (void) fgets(line, MSG_SIZ, f);
\r
9303 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9306 initial_position[i][j++] = CharToPiece(*p);
\r
9310 blackPlaysFirst = FALSE;
\r
9312 (void) fgets(line, MSG_SIZ, f);
\r
9313 if (strncmp(line, "black", strlen("black"))==0)
\r
9314 blackPlaysFirst = TRUE;
\r
9317 startedFromSetupPosition = TRUE;
\r
9319 SendToProgram("force\n", &first);
\r
9320 CopyBoard(boards[0], initial_position);
\r
9321 if (blackPlaysFirst) {
\r
9322 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9323 strcpy(moveList[0], "");
\r
9324 strcpy(parseList[0], "");
\r
9325 CopyBoard(boards[1], initial_position);
\r
9326 DisplayMessage("", _("Black to play"));
\r
9328 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9329 DisplayMessage("", _("White to play"));
\r
9331 /* [HGM] copy FEN attributes as well */
\r
9333 initialRulePlies = FENrulePlies;
\r
9334 epStatus[forwardMostMove] = FENepStatus;
\r
9335 for( i=0; i< nrCastlingRights; i++ )
\r
9336 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9338 SendBoard(&first, forwardMostMove);
\r
9339 if (appData.debugMode) {
\r
9341 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9342 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9343 fprintf(debugFP, "Load Position\n");
\r
9346 if (positionNumber > 1) {
\r
9347 sprintf(line, "%s %d", title, positionNumber);
\r
9348 DisplayTitle(line);
\r
9350 DisplayTitle(title);
\r
9352 gameMode = EditGame;
\r
9355 timeRemaining[0][1] = whiteTimeRemaining;
\r
9356 timeRemaining[1][1] = blackTimeRemaining;
\r
9357 DrawPosition(FALSE, boards[currentMove]);
\r
9364 CopyPlayerNameIntoFileName(dest, src)
\r
9365 char **dest, *src;
\r
9367 while (*src != NULLCHAR && *src != ',') {
\r
9368 if (*src == ' ') {
\r
9372 *(*dest)++ = *src++;
\r
9377 char *DefaultFileName(ext)
\r
9380 static char def[MSG_SIZ];
\r
9383 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9385 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9387 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9391 def[0] = NULLCHAR;
\r
9396 /* Save the current game to the given file */
\r
9398 SaveGameToFile(filename, append)
\r
9403 char buf[MSG_SIZ];
\r
9405 if (strcmp(filename, "-") == 0) {
\r
9406 return SaveGame(stdout, 0, NULL);
\r
9408 f = fopen(filename, append ? "a" : "w");
\r
9410 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9411 DisplayError(buf, errno);
\r
9414 return SaveGame(f, 0, NULL);
\r
9423 static char buf[MSG_SIZ];
\r
9426 p = strchr(str, ' ');
\r
9427 if (p == NULL) return str;
\r
9428 strncpy(buf, str, p - str);
\r
9429 buf[p - str] = NULLCHAR;
\r
9433 #define PGN_MAX_LINE 75
\r
9435 #define PGN_SIDE_WHITE 0
\r
9436 #define PGN_SIDE_BLACK 1
\r
9439 static int FindFirstMoveOutOfBook( int side )
\r
9443 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9444 int index = backwardMostMove;
\r
9445 int has_book_hit = 0;
\r
9447 if( (index % 2) != side ) {
\r
9451 while( index < forwardMostMove ) {
\r
9452 /* Check to see if engine is in book */
\r
9453 int depth = pvInfoList[index].depth;
\r
9454 int score = pvInfoList[index].score;
\r
9457 if( depth <= 2 ) {
\r
9460 else if( score == 0 && depth == 63 ) {
\r
9461 in_book = 1; /* Zappa */
\r
9463 else if( score == 2 && depth == 99 ) {
\r
9464 in_book = 1; /* Abrok */
\r
9467 has_book_hit += in_book;
\r
9483 void GetOutOfBookInfo( char * buf )
\r
9487 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9489 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9490 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9494 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9495 for( i=0; i<2; i++ ) {
\r
9499 if( i > 0 && oob[0] >= 0 ) {
\r
9500 strcat( buf, " " );
\r
9503 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9504 sprintf( buf+strlen(buf), "%s%.2f",
\r
9505 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9506 pvInfoList[idx].score / 100.0 );
\r
9512 /* Save game in PGN style and close the file */
\r
9517 int i, offset, linelen, newblock;
\r
9521 int movelen, numlen, blank;
\r
9522 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9524 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9526 tm = time((time_t *) NULL);
\r
9528 PrintPGNTags(f, &gameInfo);
\r
9530 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9531 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9532 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9533 fprintf(f, "\n{--------------\n");
\r
9534 PrintPosition(f, backwardMostMove);
\r
9535 fprintf(f, "--------------}\n");
\r
9539 /* [AS] Out of book annotation */
\r
9540 if( appData.saveOutOfBookInfo ) {
\r
9543 GetOutOfBookInfo( buf );
\r
9545 if( buf[0] != '\0' ) {
\r
9546 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9553 i = backwardMostMove;
\r
9557 while (i < forwardMostMove) {
\r
9558 /* Print comments preceding this move */
\r
9559 if (commentList[i] != NULL) {
\r
9560 if (linelen > 0) fprintf(f, "\n");
\r
9561 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9566 /* Format move number */
\r
9567 if ((i % 2) == 0) {
\r
9568 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9571 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9573 numtext[0] = NULLCHAR;
\r
9576 numlen = strlen(numtext);
\r
9579 /* Print move number */
\r
9580 blank = linelen > 0 && numlen > 0;
\r
9581 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9590 fprintf(f, numtext);
\r
9591 linelen += numlen;
\r
9594 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9597 blank = linelen > 0 && movelen > 0;
\r
9598 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9607 fprintf(f, parseList[i]);
\r
9608 linelen += movelen;
\r
9610 /* [AS] Add PV info if present */
\r
9611 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9612 /* [HGM] add time */
\r
9613 char buf[MSG_SIZ]; int seconds = 0;
\r
9616 if(i >= backwardMostMove) {
\r
9617 if(WhiteOnMove(i))
\r
9618 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9619 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9621 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9622 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9624 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9626 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9628 if (appData.debugMode,0) {
\r
9629 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9630 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9631 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9635 if( seconds <= 0) buf[0] = 0; else
\r
9636 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9637 seconds = (seconds + 4)/10; // round to full seconds
\r
9638 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9639 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9642 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9643 pvInfoList[i].score >= 0 ? "+" : "",
\r
9644 pvInfoList[i].score / 100.0,
\r
9645 pvInfoList[i].depth,
\r
9648 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9650 /* Print score/depth */
\r
9651 blank = linelen > 0 && movelen > 0;
\r
9652 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9661 fprintf(f, move_buffer);
\r
9662 linelen += movelen;
\r
9668 /* Start a new line */
\r
9669 if (linelen > 0) fprintf(f, "\n");
\r
9671 /* Print comments after last move */
\r
9672 if (commentList[i] != NULL) {
\r
9673 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9676 /* Print result */
\r
9677 if (gameInfo.resultDetails != NULL &&
\r
9678 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9679 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9680 PGNResult(gameInfo.result));
\r
9682 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9689 /* Save game in old style and close the file */
\r
9691 SaveGameOldStyle(f)
\r
9697 tm = time((time_t *) NULL);
\r
9699 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9700 PrintOpponents(f);
\r
9702 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9703 fprintf(f, "\n[--------------\n");
\r
9704 PrintPosition(f, backwardMostMove);
\r
9705 fprintf(f, "--------------]\n");
\r
9710 i = backwardMostMove;
\r
9711 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9713 while (i < forwardMostMove) {
\r
9714 if (commentList[i] != NULL) {
\r
9715 fprintf(f, "[%s]\n", commentList[i]);
\r
9718 if ((i % 2) == 1) {
\r
9719 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9722 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9724 if (commentList[i] != NULL) {
\r
9728 if (i >= forwardMostMove) {
\r
9732 fprintf(f, "%s\n", parseList[i]);
\r
9737 if (commentList[i] != NULL) {
\r
9738 fprintf(f, "[%s]\n", commentList[i]);
\r
9741 /* This isn't really the old style, but it's close enough */
\r
9742 if (gameInfo.resultDetails != NULL &&
\r
9743 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9744 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9745 gameInfo.resultDetails);
\r
9747 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9754 /* Save the current game to open file f and close the file */
\r
9756 SaveGame(f, dummy, dummy2)
\r
9761 if (gameMode == EditPosition) EditPositionDone();
\r
9762 if (appData.oldSaveStyle)
\r
9763 return SaveGameOldStyle(f);
\r
9765 return SaveGamePGN(f);
\r
9768 /* Save the current position to the given file */
\r
9770 SavePositionToFile(filename)
\r
9774 char buf[MSG_SIZ];
\r
9776 if (strcmp(filename, "-") == 0) {
\r
9777 return SavePosition(stdout, 0, NULL);
\r
9779 f = fopen(filename, "a");
\r
9781 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9782 DisplayError(buf, errno);
\r
9785 SavePosition(f, 0, NULL);
\r
9791 /* Save the current position to the given open file and close the file */
\r
9793 SavePosition(f, dummy, dummy2)
\r
9801 if (appData.oldSaveStyle) {
\r
9802 tm = time((time_t *) NULL);
\r
9804 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9805 PrintOpponents(f);
\r
9806 fprintf(f, "[--------------\n");
\r
9807 PrintPosition(f, currentMove);
\r
9808 fprintf(f, "--------------]\n");
\r
9810 fen = PositionToFEN(currentMove, 1);
\r
9811 fprintf(f, "%s\n", fen);
\r
9819 ReloadCmailMsgEvent(unregister)
\r
9823 static char *inFilename = NULL;
\r
9824 static char *outFilename;
\r
9826 struct stat inbuf, outbuf;
\r
9829 /* Any registered moves are unregistered if unregister is set, */
\r
9830 /* i.e. invoked by the signal handler */
\r
9832 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9833 cmailMoveRegistered[i] = FALSE;
\r
9834 if (cmailCommentList[i] != NULL) {
\r
9835 free(cmailCommentList[i]);
\r
9836 cmailCommentList[i] = NULL;
\r
9839 nCmailMovesRegistered = 0;
\r
9842 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9843 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9845 nCmailResults = 0;
\r
9847 if (inFilename == NULL) {
\r
9848 /* Because the filenames are static they only get malloced once */
\r
9849 /* and they never get freed */
\r
9850 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9851 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9853 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9854 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9857 status = stat(outFilename, &outbuf);
\r
9859 cmailMailedMove = FALSE;
\r
9861 status = stat(inFilename, &inbuf);
\r
9862 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9865 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9866 counts the games, notes how each one terminated, etc.
\r
9868 It would be nice to remove this kludge and instead gather all
\r
9869 the information while building the game list. (And to keep it
\r
9870 in the game list nodes instead of having a bunch of fixed-size
\r
9871 parallel arrays.) Note this will require getting each game's
\r
9872 termination from the PGN tags, as the game list builder does
\r
9873 not process the game moves. --mann
\r
9875 cmailMsgLoaded = TRUE;
\r
9876 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9878 /* Load first game in the file or popup game menu */
\r
9879 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9881 #endif /* !WIN32 */
\r
9889 char string[MSG_SIZ];
\r
9891 if ( cmailMailedMove
\r
9892 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9893 return TRUE; /* Allow free viewing */
\r
9896 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9897 /* with the move registered when the conditions for registering no */
\r
9899 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9900 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9901 nCmailMovesRegistered --;
\r
9903 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9905 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9906 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9910 if (cmailOldMove == -1) {
\r
9911 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9915 if (currentMove > cmailOldMove + 1) {
\r
9916 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9920 if (currentMove < cmailOldMove) {
\r
9921 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9925 if (forwardMostMove > currentMove) {
\r
9926 /* Silently truncate extra moves */
\r
9930 if ( (currentMove == cmailOldMove + 1)
\r
9931 || ( (currentMove == cmailOldMove)
\r
9932 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9933 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9934 if (gameInfo.result != GameUnfinished) {
\r
9935 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9938 if (commentList[currentMove] != NULL) {
\r
9939 cmailCommentList[lastLoadGameNumber - 1]
\r
9940 = StrSave(commentList[currentMove]);
\r
9942 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9944 if (appData.debugMode)
\r
9945 fprintf(debugFP, "Saving %s for game %d\n",
\r
9946 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9949 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9951 f = fopen(string, "w");
\r
9952 if (appData.oldSaveStyle) {
\r
9953 SaveGameOldStyle(f); /* also closes the file */
\r
9955 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9956 f = fopen(string, "w");
\r
9957 SavePosition(f, 0, NULL); /* also closes the file */
\r
9959 fprintf(f, "{--------------\n");
\r
9960 PrintPosition(f, currentMove);
\r
9961 fprintf(f, "--------------}\n\n");
\r
9963 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9966 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9967 nCmailMovesRegistered ++;
\r
9968 } else if (nCmailGames == 1) {
\r
9969 DisplayError(_("You have not made a move yet"), 0);
\r
9980 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9981 FILE *commandOutput;
\r
9982 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9983 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9989 if (! cmailMsgLoaded) {
\r
9990 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9994 if (nCmailGames == nCmailResults) {
\r
9995 DisplayError(_("No unfinished games"), 0);
\r
9999 #if CMAIL_PROHIBIT_REMAIL
\r
10000 if (cmailMailedMove) {
\r
10001 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
10002 DisplayError(msg, 0);
\r
10007 if (! (cmailMailedMove || RegisterMove())) return;
\r
10009 if ( cmailMailedMove
\r
10010 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10011 sprintf(string, partCommandString,
\r
10012 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10013 commandOutput = popen(string, "r");
\r
10015 if (commandOutput == NULL) {
\r
10016 DisplayError(_("Failed to invoke cmail"), 0);
\r
10018 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10019 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10021 if (nBuffers > 1) {
\r
10022 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10023 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10024 nBytes = MSG_SIZ - 1;
\r
10026 (void) memcpy(msg, buffer, nBytes);
\r
10028 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10030 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10031 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10034 for (i = 0; i < nCmailGames; i ++) {
\r
10035 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10036 archived = FALSE;
\r
10040 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10042 sprintf(buffer, "%s/%s.%s.archive",
\r
10044 appData.cmailGameName,
\r
10046 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10047 cmailMsgLoaded = FALSE;
\r
10051 DisplayInformation(msg);
\r
10052 pclose(commandOutput);
\r
10055 if ((*cmailMsg) != '\0') {
\r
10056 DisplayInformation(cmailMsg);
\r
10061 #endif /* !WIN32 */
\r
10070 int prependComma = 0;
\r
10072 char string[MSG_SIZ]; /* Space for game-list */
\r
10075 if (!cmailMsgLoaded) return "";
\r
10077 if (cmailMailedMove) {
\r
10078 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10080 /* Create a list of games left */
\r
10081 sprintf(string, "[");
\r
10082 for (i = 0; i < nCmailGames; i ++) {
\r
10083 if (! ( cmailMoveRegistered[i]
\r
10084 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10085 if (prependComma) {
\r
10086 sprintf(number, ",%d", i + 1);
\r
10088 sprintf(number, "%d", i + 1);
\r
10089 prependComma = 1;
\r
10092 strcat(string, number);
\r
10095 strcat(string, "]");
\r
10097 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10098 switch (nCmailGames) {
\r
10100 sprintf(cmailMsg,
\r
10101 _("Still need to make move for game\n"));
\r
10105 sprintf(cmailMsg,
\r
10106 _("Still need to make moves for both games\n"));
\r
10110 sprintf(cmailMsg,
\r
10111 _("Still need to make moves for all %d games\n"),
\r
10116 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10118 sprintf(cmailMsg,
\r
10119 _("Still need to make a move for game %s\n"),
\r
10124 if (nCmailResults == nCmailGames) {
\r
10125 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10127 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10132 sprintf(cmailMsg,
\r
10133 _("Still need to make moves for games %s\n"),
\r
10139 #endif /* WIN32 */
\r
10145 if (gameMode == Training)
\r
10146 SetTrainingModeOff();
\r
10148 Reset(TRUE, TRUE);
\r
10149 cmailMsgLoaded = FALSE;
\r
10150 if (appData.icsActive) {
\r
10151 SendToICS(ics_prefix);
\r
10152 SendToICS("refresh\n");
\r
10157 ExitEvent(status)
\r
10161 if (exiting > 2) {
\r
10162 /* Give up on clean exit */
\r
10165 if (exiting > 1) {
\r
10166 /* Keep trying for clean exit */
\r
10170 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10172 if (telnetISR != NULL) {
\r
10173 RemoveInputSource(telnetISR);
\r
10175 if (icsPR != NoProc) {
\r
10176 DestroyChildProcess(icsPR, TRUE);
\r
10179 /* Save game if resource set and not already saved by GameEnds() */
\r
10180 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10181 && forwardMostMove > 0) {
\r
10182 if (*appData.saveGameFile != NULLCHAR) {
\r
10183 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10184 } else if (appData.autoSaveGames) {
\r
10187 if (*appData.savePositionFile != NULLCHAR) {
\r
10188 SavePositionToFile(appData.savePositionFile);
\r
10191 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10193 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10194 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10196 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10197 /* make sure this other one finishes before killing it! */
\r
10198 if(endingGame) { int count = 0;
\r
10199 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10200 while(endingGame && count++ < 10) DoSleep(1);
\r
10201 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10204 /* Kill off chess programs */
\r
10205 if (first.pr != NoProc) {
\r
10206 ExitAnalyzeMode();
\r
10208 DoSleep( appData.delayBeforeQuit );
\r
10209 SendToProgram("quit\n", &first);
\r
10210 DoSleep( appData.delayAfterQuit );
\r
10211 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10213 if (second.pr != NoProc) {
\r
10214 DoSleep( appData.delayBeforeQuit );
\r
10215 SendToProgram("quit\n", &second);
\r
10216 DoSleep( appData.delayAfterQuit );
\r
10217 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10219 if (first.isr != NULL) {
\r
10220 RemoveInputSource(first.isr);
\r
10222 if (second.isr != NULL) {
\r
10223 RemoveInputSource(second.isr);
\r
10226 ShutDownFrontEnd();
\r
10233 if (appData.debugMode)
\r
10234 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10238 if (gameMode == MachinePlaysWhite ||
\r
10239 gameMode == MachinePlaysBlack) {
\r
10242 DisplayBothClocks();
\r
10244 if (gameMode == PlayFromGameFile) {
\r
10245 if (appData.timeDelay >= 0)
\r
10246 AutoPlayGameLoop();
\r
10247 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10248 Reset(FALSE, TRUE);
\r
10249 SendToICS(ics_prefix);
\r
10250 SendToICS("refresh\n");
\r
10251 } else if (currentMove < forwardMostMove) {
\r
10252 ForwardInner(forwardMostMove);
\r
10254 pauseExamInvalid = FALSE;
\r
10256 switch (gameMode) {
\r
10259 case IcsExamining:
\r
10260 pauseExamForwardMostMove = forwardMostMove;
\r
10261 pauseExamInvalid = FALSE;
\r
10262 /* fall through */
\r
10263 case IcsObserving:
\r
10264 case IcsPlayingWhite:
\r
10265 case IcsPlayingBlack:
\r
10269 case PlayFromGameFile:
\r
10270 (void) StopLoadGameTimer();
\r
10274 case BeginningOfGame:
\r
10275 if (appData.icsActive) return;
\r
10276 /* else fall through */
\r
10277 case MachinePlaysWhite:
\r
10278 case MachinePlaysBlack:
\r
10279 case TwoMachinesPlay:
\r
10280 if (forwardMostMove == 0)
\r
10281 return; /* don't pause if no one has moved */
\r
10282 if ((gameMode == MachinePlaysWhite &&
\r
10283 !WhiteOnMove(forwardMostMove)) ||
\r
10284 (gameMode == MachinePlaysBlack &&
\r
10285 WhiteOnMove(forwardMostMove))) {
\r
10296 EditCommentEvent()
\r
10298 char title[MSG_SIZ];
\r
10300 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10301 strcpy(title, _("Edit comment"));
\r
10303 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10304 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10305 parseList[currentMove - 1]);
\r
10308 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10315 char *tags = PGNTags(&gameInfo);
\r
10316 EditTagsPopUp(tags);
\r
10321 AnalyzeModeEvent()
\r
10323 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10326 if (gameMode != AnalyzeFile) {
\r
10327 if (!appData.icsEngineAnalyze) {
\r
10329 if (gameMode != EditGame) return;
\r
10331 ResurrectChessProgram();
\r
10332 SendToProgram("analyze\n", &first);
\r
10333 first.analyzing = TRUE;
\r
10334 /*first.maybeThinking = TRUE;*/
\r
10335 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10336 AnalysisPopUp(_("Analysis"),
\r
10337 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10339 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10344 StartAnalysisClock();
\r
10345 GetTimeMark(&lastNodeCountTime);
\r
10346 lastNodeCount = 0;
\r
10350 AnalyzeFileEvent()
\r
10352 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10355 if (gameMode != AnalyzeMode) {
\r
10357 if (gameMode != EditGame) return;
\r
10358 ResurrectChessProgram();
\r
10359 SendToProgram("analyze\n", &first);
\r
10360 first.analyzing = TRUE;
\r
10361 /*first.maybeThinking = TRUE;*/
\r
10362 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10363 AnalysisPopUp(_("Analysis"),
\r
10364 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10366 gameMode = AnalyzeFile;
\r
10371 StartAnalysisClock();
\r
10372 GetTimeMark(&lastNodeCountTime);
\r
10373 lastNodeCount = 0;
\r
10377 MachineWhiteEvent()
\r
10379 char buf[MSG_SIZ];
\r
10380 char *bookHit = NULL;
\r
10382 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10386 if (gameMode == PlayFromGameFile ||
\r
10387 gameMode == TwoMachinesPlay ||
\r
10388 gameMode == Training ||
\r
10389 gameMode == AnalyzeMode ||
\r
10390 gameMode == EndOfGame)
\r
10393 if (gameMode == EditPosition)
\r
10394 EditPositionDone();
\r
10396 if (!WhiteOnMove(currentMove)) {
\r
10397 DisplayError(_("It is not White's turn"), 0);
\r
10401 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10402 ExitAnalyzeMode();
\r
10404 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10405 gameMode == AnalyzeFile)
\r
10408 ResurrectChessProgram(); /* in case it isn't running */
\r
10409 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10410 gameMode = MachinePlaysWhite;
\r
10413 gameMode = MachinePlaysWhite;
\r
10417 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10418 DisplayTitle(buf);
\r
10419 if (first.sendName) {
\r
10420 sprintf(buf, "name %s\n", gameInfo.black);
\r
10421 SendToProgram(buf, &first);
\r
10423 if (first.sendTime) {
\r
10424 if (first.useColors) {
\r
10425 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10427 SendTimeRemaining(&first, TRUE);
\r
10429 if (first.useColors) {
\r
10430 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10432 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10433 SetMachineThinkingEnables();
\r
10434 first.maybeThinking = TRUE;
\r
10437 if (appData.autoFlipView && !flipView) {
\r
10438 flipView = !flipView;
\r
10439 DrawPosition(FALSE, NULL);
\r
10440 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10443 if(bookHit) { // [HGM] book: simulate book reply
\r
10444 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10446 programStats.depth = programStats.nodes = programStats.time =
\r
10447 programStats.score = programStats.got_only_move = 0;
\r
10448 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10450 strcpy(bookMove, "move ");
\r
10451 strcat(bookMove, bookHit);
\r
10452 HandleMachineMove(bookMove, &first);
\r
10457 MachineBlackEvent()
\r
10459 char buf[MSG_SIZ];
\r
10460 char *bookHit = NULL;
\r
10462 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10466 if (gameMode == PlayFromGameFile ||
\r
10467 gameMode == TwoMachinesPlay ||
\r
10468 gameMode == Training ||
\r
10469 gameMode == AnalyzeMode ||
\r
10470 gameMode == EndOfGame)
\r
10473 if (gameMode == EditPosition)
\r
10474 EditPositionDone();
\r
10476 if (WhiteOnMove(currentMove)) {
\r
10477 DisplayError(_("It is not Black's turn"), 0);
\r
10481 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10482 ExitAnalyzeMode();
\r
10484 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10485 gameMode == AnalyzeFile)
\r
10488 ResurrectChessProgram(); /* in case it isn't running */
\r
10489 gameMode = MachinePlaysBlack;
\r
10493 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10494 DisplayTitle(buf);
\r
10495 if (first.sendName) {
\r
10496 sprintf(buf, "name %s\n", gameInfo.white);
\r
10497 SendToProgram(buf, &first);
\r
10499 if (first.sendTime) {
\r
10500 if (first.useColors) {
\r
10501 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10503 SendTimeRemaining(&first, FALSE);
\r
10505 if (first.useColors) {
\r
10506 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10508 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10509 SetMachineThinkingEnables();
\r
10510 first.maybeThinking = TRUE;
\r
10513 if (appData.autoFlipView && flipView) {
\r
10514 flipView = !flipView;
\r
10515 DrawPosition(FALSE, NULL);
\r
10516 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10518 if(bookHit) { // [HGM] book: simulate book reply
\r
10519 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10521 programStats.depth = programStats.nodes = programStats.time =
\r
10522 programStats.score = programStats.got_only_move = 0;
\r
10523 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10525 strcpy(bookMove, "move ");
\r
10526 strcat(bookMove, bookHit);
\r
10527 HandleMachineMove(bookMove, &first);
\r
10533 DisplayTwoMachinesTitle()
\r
10535 char buf[MSG_SIZ];
\r
10536 if (appData.matchGames > 0) {
\r
10537 if (first.twoMachinesColor[0] == 'w') {
\r
10538 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10539 gameInfo.white, gameInfo.black,
\r
10540 first.matchWins, second.matchWins,
\r
10541 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10543 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10544 gameInfo.white, gameInfo.black,
\r
10545 second.matchWins, first.matchWins,
\r
10546 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10549 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10551 DisplayTitle(buf);
\r
10555 TwoMachinesEvent P((void))
\r
10558 char buf[MSG_SIZ];
\r
10559 ChessProgramState *onmove;
\r
10560 char *bookHit = NULL;
\r
10562 if (appData.noChessProgram) return;
\r
10564 switch (gameMode) {
\r
10565 case TwoMachinesPlay:
\r
10567 case MachinePlaysWhite:
\r
10568 case MachinePlaysBlack:
\r
10569 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10570 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10573 /* fall through */
\r
10574 case BeginningOfGame:
\r
10575 case PlayFromGameFile:
\r
10578 if (gameMode != EditGame) return;
\r
10580 case EditPosition:
\r
10581 EditPositionDone();
\r
10583 case AnalyzeMode:
\r
10584 case AnalyzeFile:
\r
10585 ExitAnalyzeMode();
\r
10592 forwardMostMove = currentMove;
\r
10593 ResurrectChessProgram(); /* in case first program isn't running */
\r
10595 if (second.pr == NULL) {
\r
10596 StartChessProgram(&second);
\r
10597 if (second.protocolVersion == 1) {
\r
10598 TwoMachinesEventIfReady();
\r
10600 /* kludge: allow timeout for initial "feature" command */
\r
10602 DisplayMessage("", _("Starting second chess program"));
\r
10603 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10607 DisplayMessage("", "");
\r
10608 InitChessProgram(&second, FALSE);
\r
10609 SendToProgram("force\n", &second);
\r
10610 if (startedFromSetupPosition) {
\r
10611 SendBoard(&second, backwardMostMove);
\r
10612 if (appData.debugMode) {
\r
10613 fprintf(debugFP, "Two Machines\n");
\r
10616 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10617 SendMoveToProgram(i, &second);
\r
10620 gameMode = TwoMachinesPlay;
\r
10624 DisplayTwoMachinesTitle();
\r
10625 firstMove = TRUE;
\r
10626 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10629 onmove = &second;
\r
10632 SendToProgram(first.computerString, &first);
\r
10633 if (first.sendName) {
\r
10634 sprintf(buf, "name %s\n", second.tidy);
\r
10635 SendToProgram(buf, &first);
\r
10637 SendToProgram(second.computerString, &second);
\r
10638 if (second.sendName) {
\r
10639 sprintf(buf, "name %s\n", first.tidy);
\r
10640 SendToProgram(buf, &second);
\r
10644 if (!first.sendTime || !second.sendTime) {
\r
10645 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10646 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10648 if (onmove->sendTime) {
\r
10649 if (onmove->useColors) {
\r
10650 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10652 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10654 if (onmove->useColors) {
\r
10655 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10657 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10658 // SendToProgram("go\n", onmove);
\r
10659 onmove->maybeThinking = TRUE;
\r
10660 SetMachineThinkingEnables();
\r
10664 if(bookHit) { // [HGM] book: simulate book reply
\r
10665 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10667 programStats.depth = programStats.nodes = programStats.time =
\r
10668 programStats.score = programStats.got_only_move = 0;
\r
10669 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10671 strcpy(bookMove, "move ");
\r
10672 strcat(bookMove, bookHit);
\r
10673 HandleMachineMove(bookMove, &first);
\r
10680 if (gameMode == Training) {
\r
10681 SetTrainingModeOff();
\r
10682 gameMode = PlayFromGameFile;
\r
10683 DisplayMessage("", _("Training mode off"));
\r
10685 gameMode = Training;
\r
10686 animateTraining = appData.animate;
\r
10688 /* make sure we are not already at the end of the game */
\r
10689 if (currentMove < forwardMostMove) {
\r
10690 SetTrainingModeOn();
\r
10691 DisplayMessage("", _("Training mode on"));
\r
10693 gameMode = PlayFromGameFile;
\r
10694 DisplayError(_("Already at end of game"), 0);
\r
10703 if (!appData.icsActive) return;
\r
10704 switch (gameMode) {
\r
10705 case IcsPlayingWhite:
\r
10706 case IcsPlayingBlack:
\r
10707 case IcsObserving:
\r
10709 case BeginningOfGame:
\r
10710 case IcsExamining:
\r
10716 case EditPosition:
\r
10717 EditPositionDone();
\r
10720 case AnalyzeMode:
\r
10721 case AnalyzeFile:
\r
10722 ExitAnalyzeMode();
\r
10730 gameMode = IcsIdle;
\r
10741 switch (gameMode) {
\r
10743 SetTrainingModeOff();
\r
10745 case MachinePlaysWhite:
\r
10746 case MachinePlaysBlack:
\r
10747 case BeginningOfGame:
\r
10748 SendToProgram("force\n", &first);
\r
10749 SetUserThinkingEnables();
\r
10751 case PlayFromGameFile:
\r
10752 (void) StopLoadGameTimer();
\r
10753 if (gameFileFP != NULL) {
\r
10754 gameFileFP = NULL;
\r
10757 case EditPosition:
\r
10758 EditPositionDone();
\r
10760 case AnalyzeMode:
\r
10761 case AnalyzeFile:
\r
10762 ExitAnalyzeMode();
\r
10763 SendToProgram("force\n", &first);
\r
10765 case TwoMachinesPlay:
\r
10766 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10767 ResurrectChessProgram();
\r
10768 SetUserThinkingEnables();
\r
10771 ResurrectChessProgram();
\r
10773 case IcsPlayingBlack:
\r
10774 case IcsPlayingWhite:
\r
10775 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10777 case IcsObserving:
\r
10778 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10780 case IcsExamining:
\r
10781 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10792 first.offeredDraw = second.offeredDraw = 0;
\r
10794 if (gameMode == PlayFromGameFile) {
\r
10795 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10796 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10797 DisplayTitle("");
\r
10800 if (gameMode == MachinePlaysWhite ||
\r
10801 gameMode == MachinePlaysBlack ||
\r
10802 gameMode == TwoMachinesPlay ||
\r
10803 gameMode == EndOfGame) {
\r
10804 i = forwardMostMove;
\r
10805 while (i > currentMove) {
\r
10806 SendToProgram("undo\n", &first);
\r
10809 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10810 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10811 DisplayBothClocks();
\r
10812 if (whiteFlag || blackFlag) {
\r
10813 whiteFlag = blackFlag = 0;
\r
10815 DisplayTitle("");
\r
10818 gameMode = EditGame;
\r
10825 EditPositionEvent()
\r
10827 if (gameMode == EditPosition) {
\r
10833 if (gameMode != EditGame) return;
\r
10835 gameMode = EditPosition;
\r
10838 if (currentMove > 0)
\r
10839 CopyBoard(boards[0], boards[currentMove]);
\r
10841 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10843 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10844 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10849 ExitAnalyzeMode()
\r
10851 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10852 if (appData.icsEngineAnalyze) {
\r
10853 appData.icsEngineAnalyze = FALSE;
\r
10855 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10857 if (first.analysisSupport && first.analyzing) {
\r
10858 SendToProgram("exit\n", &first);
\r
10859 first.analyzing = FALSE;
\r
10861 AnalysisPopDown();
\r
10862 thinkOutput[0] = NULLCHAR;
\r
10866 EditPositionDone()
\r
10868 startedFromSetupPosition = TRUE;
\r
10869 InitChessProgram(&first, FALSE);
\r
10870 SendToProgram("force\n", &first);
\r
10871 if (blackPlaysFirst) {
\r
10872 strcpy(moveList[0], "");
\r
10873 strcpy(parseList[0], "");
\r
10874 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10875 CopyBoard(boards[1], boards[0]);
\r
10876 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10878 epStatus[1] = epStatus[0];
\r
10879 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10882 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10884 SendBoard(&first, forwardMostMove);
\r
10885 if (appData.debugMode) {
\r
10886 fprintf(debugFP, "EditPosDone\n");
\r
10888 DisplayTitle("");
\r
10889 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10890 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10891 gameMode = EditGame;
\r
10893 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10894 ClearHighlights(); /* [AS] */
\r
10897 /* Pause for `ms' milliseconds */
\r
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10905 GetTimeMark(&m1);
\r
10907 GetTimeMark(&m2);
\r
10908 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10913 SendMultiLineToICS(buf)
\r
10916 char temp[MSG_SIZ+1], *p;
\r
10919 len = strlen(buf);
\r
10920 if (len > MSG_SIZ)
\r
10923 strncpy(temp, buf, len);
\r
10928 if (*p == '\n' || *p == '\r')
\r
10933 strcat(temp, "\n");
\r
10935 SendToPlayer(temp, strlen(temp));
\r
10939 SetWhiteToPlayEvent()
\r
10941 if (gameMode == EditPosition) {
\r
10942 blackPlaysFirst = FALSE;
\r
10943 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10944 } else if (gameMode == IcsExamining) {
\r
10945 SendToICS(ics_prefix);
\r
10946 SendToICS("tomove white\n");
\r
10951 SetBlackToPlayEvent()
\r
10953 if (gameMode == EditPosition) {
\r
10954 blackPlaysFirst = TRUE;
\r
10955 currentMove = 1; /* kludge */
\r
10956 DisplayBothClocks();
\r
10958 } else if (gameMode == IcsExamining) {
\r
10959 SendToICS(ics_prefix);
\r
10960 SendToICS("tomove black\n");
\r
10965 EditPositionMenuEvent(selection, x, y)
\r
10966 ChessSquare selection;
\r
10969 char buf[MSG_SIZ];
\r
10970 ChessSquare piece = boards[0][y][x];
\r
10972 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10974 switch (selection) {
\r
10976 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10977 SendToICS(ics_prefix);
\r
10978 SendToICS("bsetup clear\n");
\r
10979 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10980 SendToICS(ics_prefix);
\r
10981 SendToICS("clearboard\n");
\r
10983 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10984 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10985 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10986 if (gameMode == IcsExamining) {
\r
10987 if (boards[currentMove][y][x] != EmptySquare) {
\r
10988 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10989 AAA + x, ONE + y);
\r
10993 boards[0][y][x] = p;
\r
10998 if (gameMode == EditPosition) {
\r
10999 DrawPosition(FALSE, boards[0]);
\r
11004 SetWhiteToPlayEvent();
\r
11008 SetBlackToPlayEvent();
\r
11011 case EmptySquare:
\r
11012 if (gameMode == IcsExamining) {
\r
11013 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11016 boards[0][y][x] = EmptySquare;
\r
11017 DrawPosition(FALSE, boards[0]);
\r
11021 case PromotePiece:
\r
11022 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11023 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11024 selection = (ChessSquare) (PROMOTED piece);
\r
11025 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11026 else selection = (ChessSquare)((int)piece - 1);
\r
11027 goto defaultlabel;
\r
11029 case DemotePiece:
\r
11030 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11031 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11032 selection = (ChessSquare) (DEMOTED piece);
\r
11033 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11034 else selection = (ChessSquare)((int)piece + 1);
\r
11035 goto defaultlabel;
\r
11039 if(gameInfo.variant == VariantShatranj ||
\r
11040 gameInfo.variant == VariantXiangqi ||
\r
11041 gameInfo.variant == VariantCourier )
\r
11042 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11043 goto defaultlabel;
\r
11047 if(gameInfo.variant == VariantXiangqi)
\r
11048 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11049 if(gameInfo.variant == VariantKnightmate)
\r
11050 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11053 if (gameMode == IcsExamining) {
\r
11054 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11055 PieceToChar(selection), AAA + x, ONE + y);
\r
11058 boards[0][y][x] = selection;
\r
11059 DrawPosition(FALSE, boards[0]);
\r
11067 DropMenuEvent(selection, x, y)
\r
11068 ChessSquare selection;
\r
11071 ChessMove moveType;
\r
11073 switch (gameMode) {
\r
11074 case IcsPlayingWhite:
\r
11075 case MachinePlaysBlack:
\r
11076 if (!WhiteOnMove(currentMove)) {
\r
11077 DisplayMoveError(_("It is Black's turn"));
\r
11080 moveType = WhiteDrop;
\r
11082 case IcsPlayingBlack:
\r
11083 case MachinePlaysWhite:
\r
11084 if (WhiteOnMove(currentMove)) {
\r
11085 DisplayMoveError(_("It is White's turn"));
\r
11088 moveType = BlackDrop;
\r
11091 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11097 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11098 selection = (ChessSquare) ((int) selection
\r
11099 + (int) BlackPawn - (int) WhitePawn);
\r
11101 if (boards[currentMove][y][x] != EmptySquare) {
\r
11102 DisplayMoveError(_("That square is occupied"));
\r
11106 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11112 /* Accept a pending offer of any kind from opponent */
\r
11114 if (appData.icsActive) {
\r
11115 SendToICS(ics_prefix);
\r
11116 SendToICS("accept\n");
\r
11117 } else if (cmailMsgLoaded) {
\r
11118 if (currentMove == cmailOldMove &&
\r
11119 commentList[cmailOldMove] != NULL &&
\r
11120 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11121 "Black offers a draw" : "White offers a draw")) {
\r
11123 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11124 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11126 DisplayError(_("There is no pending offer on this move"), 0);
\r
11127 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11130 /* Not used for offers from chess program */
\r
11137 /* Decline a pending offer of any kind from opponent */
\r
11139 if (appData.icsActive) {
\r
11140 SendToICS(ics_prefix);
\r
11141 SendToICS("decline\n");
\r
11142 } else if (cmailMsgLoaded) {
\r
11143 if (currentMove == cmailOldMove &&
\r
11144 commentList[cmailOldMove] != NULL &&
\r
11145 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11146 "Black offers a draw" : "White offers a draw")) {
\r
11148 AppendComment(cmailOldMove, "Draw declined");
\r
11149 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11150 #endif /*NOTDEF*/
\r
11152 DisplayError(_("There is no pending offer on this move"), 0);
\r
11155 /* Not used for offers from chess program */
\r
11162 /* Issue ICS rematch command */
\r
11163 if (appData.icsActive) {
\r
11164 SendToICS(ics_prefix);
\r
11165 SendToICS("rematch\n");
\r
11172 /* Call your opponent's flag (claim a win on time) */
\r
11173 if (appData.icsActive) {
\r
11174 SendToICS(ics_prefix);
\r
11175 SendToICS("flag\n");
\r
11177 switch (gameMode) {
\r
11180 case MachinePlaysWhite:
\r
11183 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11186 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11188 DisplayError(_("Your opponent is not out of time"), 0);
\r
11191 case MachinePlaysBlack:
\r
11194 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11197 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11199 DisplayError(_("Your opponent is not out of time"), 0);
\r
11209 /* Offer draw or accept pending draw offer from opponent */
\r
11211 if (appData.icsActive) {
\r
11212 /* Note: tournament rules require draw offers to be
\r
11213 made after you make your move but before you punch
\r
11214 your clock. Currently ICS doesn't let you do that;
\r
11215 instead, you immediately punch your clock after making
\r
11216 a move, but you can offer a draw at any time. */
\r
11218 SendToICS(ics_prefix);
\r
11219 SendToICS("draw\n");
\r
11220 } else if (cmailMsgLoaded) {
\r
11221 if (currentMove == cmailOldMove &&
\r
11222 commentList[cmailOldMove] != NULL &&
\r
11223 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11224 "Black offers a draw" : "White offers a draw")) {
\r
11225 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11226 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11227 } else if (currentMove == cmailOldMove + 1) {
\r
11228 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11229 "White offers a draw" : "Black offers a draw";
\r
11230 AppendComment(currentMove, offer);
\r
11231 DisplayComment(currentMove - 1, offer);
\r
11232 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11234 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11237 } else if (first.offeredDraw) {
\r
11238 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11240 if (first.sendDrawOffers) {
\r
11241 SendToProgram("draw\n", &first);
\r
11242 userOfferedDraw = TRUE;
\r
11250 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11252 if (appData.icsActive) {
\r
11253 SendToICS(ics_prefix);
\r
11254 SendToICS("adjourn\n");
\r
11256 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11264 /* Offer Abort or accept pending Abort offer from opponent */
\r
11266 if (appData.icsActive) {
\r
11267 SendToICS(ics_prefix);
\r
11268 SendToICS("abort\n");
\r
11270 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11277 /* Resign. You can do this even if it's not your turn. */
\r
11279 if (appData.icsActive) {
\r
11280 SendToICS(ics_prefix);
\r
11281 SendToICS("resign\n");
\r
11283 switch (gameMode) {
\r
11284 case MachinePlaysWhite:
\r
11285 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11287 case MachinePlaysBlack:
\r
11288 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11291 if (cmailMsgLoaded) {
\r
11293 if (WhiteOnMove(cmailOldMove)) {
\r
11294 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11296 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11298 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11309 StopObservingEvent()
\r
11311 /* Stop observing current games */
\r
11312 SendToICS(ics_prefix);
\r
11313 SendToICS("unobserve\n");
\r
11317 StopExaminingEvent()
\r
11319 /* Stop observing current game */
\r
11320 SendToICS(ics_prefix);
\r
11321 SendToICS("unexamine\n");
\r
11325 ForwardInner(target)
\r
11330 if (appData.debugMode)
\r
11331 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11332 target, currentMove, forwardMostMove);
\r
11334 if (gameMode == EditPosition)
\r
11337 if (gameMode == PlayFromGameFile && !pausing)
\r
11340 if (gameMode == IcsExamining && pausing)
\r
11341 limit = pauseExamForwardMostMove;
\r
11343 limit = forwardMostMove;
\r
11345 if (target > limit) target = limit;
\r
11347 if (target > 0 && moveList[target - 1][0]) {
\r
11348 int fromX, fromY, toX, toY;
\r
11349 toX = moveList[target - 1][2] - AAA;
\r
11350 toY = moveList[target - 1][3] - ONE;
\r
11351 if (moveList[target - 1][1] == '@') {
\r
11352 if (appData.highlightLastMove) {
\r
11353 SetHighlights(-1, -1, toX, toY);
\r
11356 fromX = moveList[target - 1][0] - AAA;
\r
11357 fromY = moveList[target - 1][1] - ONE;
\r
11358 if (target == currentMove + 1) {
\r
11359 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11361 if (appData.highlightLastMove) {
\r
11362 SetHighlights(fromX, fromY, toX, toY);
\r
11366 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11367 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11368 gameMode == AnalyzeFile) {
\r
11369 while (currentMove < target) {
\r
11370 SendMoveToProgram(currentMove++, &first);
\r
11373 currentMove = target;
\r
11376 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11377 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11378 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11380 DisplayBothClocks();
\r
11381 DisplayMove(currentMove - 1);
\r
11382 DrawPosition(FALSE, boards[currentMove]);
\r
11383 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11384 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11385 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11393 if (gameMode == IcsExamining && !pausing) {
\r
11394 SendToICS(ics_prefix);
\r
11395 SendToICS("forward\n");
\r
11397 ForwardInner(currentMove + 1);
\r
11404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11405 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11406 * the remaining moves to the engine. Otherwise we get analysis output
\r
11407 * after each move.
\r
11409 if (first.analysisSupport) {
\r
11410 SendToProgram("exit\nforce\n", &first);
\r
11411 first.analyzing = FALSE;
\r
11415 if (gameMode == IcsExamining && !pausing) {
\r
11416 SendToICS(ics_prefix);
\r
11417 SendToICS("forward 999999\n");
\r
11419 ForwardInner(forwardMostMove);
\r
11422 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11423 /* we have fed all the moves, so reactivate analysis mode */
\r
11424 SendToProgram("analyze\n", &first);
\r
11425 first.analyzing = TRUE;
\r
11426 /*first.maybeThinking = TRUE;*/
\r
11427 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11432 BackwardInner(target)
\r
11435 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11437 if (appData.debugMode)
\r
11438 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11439 target, currentMove, forwardMostMove);
\r
11441 if (gameMode == EditPosition) return;
\r
11442 if (currentMove <= backwardMostMove) {
\r
11443 ClearHighlights();
\r
11444 DrawPosition(full_redraw, boards[currentMove]);
\r
11447 if (gameMode == PlayFromGameFile && !pausing)
\r
11450 if (moveList[target][0]) {
\r
11451 int fromX, fromY, toX, toY;
\r
11452 toX = moveList[target][2] - AAA;
\r
11453 toY = moveList[target][3] - ONE;
\r
11454 if (moveList[target][1] == '@') {
\r
11455 if (appData.highlightLastMove) {
\r
11456 SetHighlights(-1, -1, toX, toY);
\r
11459 fromX = moveList[target][0] - AAA;
\r
11460 fromY = moveList[target][1] - ONE;
\r
11461 if (target == currentMove - 1) {
\r
11462 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11464 if (appData.highlightLastMove) {
\r
11465 SetHighlights(fromX, fromY, toX, toY);
\r
11469 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11470 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11471 while (currentMove > target) {
\r
11472 SendToProgram("undo\n", &first);
\r
11476 currentMove = target;
\r
11479 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11480 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11481 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11483 DisplayBothClocks();
\r
11484 DisplayMove(currentMove - 1);
\r
11485 DrawPosition(full_redraw, boards[currentMove]);
\r
11486 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11487 // [HGM] PV info: routine tests if comment empty
\r
11488 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11494 if (gameMode == IcsExamining && !pausing) {
\r
11495 SendToICS(ics_prefix);
\r
11496 SendToICS("backward\n");
\r
11498 BackwardInner(currentMove - 1);
\r
11505 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11506 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11507 * all the moves. Otherwise we get analysis output after each undo.
\r
11509 if (first.analysisSupport) {
\r
11510 SendToProgram("exit\nforce\n", &first);
\r
11511 first.analyzing = FALSE;
\r
11515 if (gameMode == IcsExamining && !pausing) {
\r
11516 SendToICS(ics_prefix);
\r
11517 SendToICS("backward 999999\n");
\r
11519 BackwardInner(backwardMostMove);
\r
11522 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11523 /* we have fed all the moves, so reactivate analysis mode */
\r
11524 SendToProgram("analyze\n", &first);
\r
11525 first.analyzing = TRUE;
\r
11526 /*first.maybeThinking = TRUE;*/
\r
11527 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11532 ToNrEvent(int to)
\r
11534 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11535 if (to >= forwardMostMove) to = forwardMostMove;
\r
11536 if (to <= backwardMostMove) to = backwardMostMove;
\r
11537 if (to < currentMove) {
\r
11538 BackwardInner(to);
\r
11540 ForwardInner(to);
\r
11547 if (gameMode != IcsExamining) {
\r
11548 DisplayError(_("You are not examining a game"), 0);
\r
11552 DisplayError(_("You can't revert while pausing"), 0);
\r
11555 SendToICS(ics_prefix);
\r
11556 SendToICS("revert\n");
\r
11560 RetractMoveEvent()
\r
11562 switch (gameMode) {
\r
11563 case MachinePlaysWhite:
\r
11564 case MachinePlaysBlack:
\r
11565 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11566 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11569 if (forwardMostMove < 2) return;
\r
11570 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11571 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11572 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11573 DisplayBothClocks();
\r
11574 DisplayMove(currentMove - 1);
\r
11575 ClearHighlights();/*!! could figure this out*/
\r
11576 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11577 SendToProgram("remove\n", &first);
\r
11578 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11581 case BeginningOfGame:
\r
11585 case IcsPlayingWhite:
\r
11586 case IcsPlayingBlack:
\r
11587 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11588 SendToICS(ics_prefix);
\r
11589 SendToICS("takeback 2\n");
\r
11591 SendToICS(ics_prefix);
\r
11592 SendToICS("takeback 1\n");
\r
11601 ChessProgramState *cps;
\r
11603 switch (gameMode) {
\r
11604 case MachinePlaysWhite:
\r
11605 if (!WhiteOnMove(forwardMostMove)) {
\r
11606 DisplayError(_("It is your turn"), 0);
\r
11611 case MachinePlaysBlack:
\r
11612 if (WhiteOnMove(forwardMostMove)) {
\r
11613 DisplayError(_("It is your turn"), 0);
\r
11618 case TwoMachinesPlay:
\r
11619 if (WhiteOnMove(forwardMostMove) ==
\r
11620 (first.twoMachinesColor[0] == 'w')) {
\r
11626 case BeginningOfGame:
\r
11630 SendToProgram("?\n", cps);
\r
11634 TruncateGameEvent()
\r
11637 if (gameMode != EditGame) return;
\r
11644 if (forwardMostMove > currentMove) {
\r
11645 if (gameInfo.resultDetails != NULL) {
\r
11646 free(gameInfo.resultDetails);
\r
11647 gameInfo.resultDetails = NULL;
\r
11648 gameInfo.result = GameUnfinished;
\r
11650 forwardMostMove = currentMove;
\r
11651 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11659 if (appData.noChessProgram) return;
\r
11660 switch (gameMode) {
\r
11661 case MachinePlaysWhite:
\r
11662 if (WhiteOnMove(forwardMostMove)) {
\r
11663 DisplayError(_("Wait until your turn"), 0);
\r
11667 case BeginningOfGame:
\r
11668 case MachinePlaysBlack:
\r
11669 if (!WhiteOnMove(forwardMostMove)) {
\r
11670 DisplayError(_("Wait until your turn"), 0);
\r
11675 DisplayError(_("No hint available"), 0);
\r
11678 SendToProgram("hint\n", &first);
\r
11679 hintRequested = TRUE;
\r
11685 if (appData.noChessProgram) return;
\r
11686 switch (gameMode) {
\r
11687 case MachinePlaysWhite:
\r
11688 if (WhiteOnMove(forwardMostMove)) {
\r
11689 DisplayError(_("Wait until your turn"), 0);
\r
11693 case BeginningOfGame:
\r
11694 case MachinePlaysBlack:
\r
11695 if (!WhiteOnMove(forwardMostMove)) {
\r
11696 DisplayError(_("Wait until your turn"), 0);
\r
11700 case EditPosition:
\r
11701 EditPositionDone();
\r
11703 case TwoMachinesPlay:
\r
11708 SendToProgram("bk\n", &first);
\r
11709 bookOutput[0] = NULLCHAR;
\r
11710 bookRequested = TRUE;
\r
11716 char *tags = PGNTags(&gameInfo);
\r
11717 TagsPopUp(tags, CmailMsg());
\r
11721 /* end button procedures */
\r
11724 PrintPosition(fp, move)
\r
11730 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11731 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11732 char c = PieceToChar(boards[move][i][j]);
\r
11733 fputc(c == 'x' ? '.' : c, fp);
\r
11734 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11737 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11738 fprintf(fp, "white to play\n");
\r
11740 fprintf(fp, "black to play\n");
\r
11744 PrintOpponents(fp)
\r
11747 if (gameInfo.white != NULL) {
\r
11748 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11750 fprintf(fp, "\n");
\r
11754 /* Find last component of program's own name, using some heuristics */
\r
11756 TidyProgramName(prog, host, buf)
\r
11757 char *prog, *host, buf[MSG_SIZ];
\r
11760 int local = (strcmp(host, "localhost") == 0);
\r
11761 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11763 while (*p == ' ') p++;
\r
11766 if (*prog == '"' || *prog == '\'') {
\r
11767 q = strchr(prog + 1, *prog);
\r
11769 q = strchr(prog, ' ');
\r
11771 if (q == NULL) q = prog + strlen(prog);
\r
11773 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11775 if(p == prog && *p == '"') p++;
\r
11776 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11777 memcpy(buf, p, q - p);
\r
11778 buf[q - p] = NULLCHAR;
\r
11780 strcat(buf, "@");
\r
11781 strcat(buf, host);
\r
11786 TimeControlTagValue()
\r
11788 char buf[MSG_SIZ];
\r
11789 if (!appData.clockMode) {
\r
11790 strcpy(buf, "-");
\r
11791 } else if (movesPerSession > 0) {
\r
11792 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11793 } else if (timeIncrement == 0) {
\r
11794 sprintf(buf, "%ld", timeControl/1000);
\r
11796 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11798 return StrSave(buf);
\r
11804 /* This routine is used only for certain modes */
\r
11805 VariantClass v = gameInfo.variant;
\r
11806 ClearGameInfo(&gameInfo);
\r
11807 gameInfo.variant = v;
\r
11809 switch (gameMode) {
\r
11810 case MachinePlaysWhite:
\r
11811 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11812 gameInfo.site = StrSave(HostName());
\r
11813 gameInfo.date = PGNDate();
\r
11814 gameInfo.round = StrSave("-");
\r
11815 gameInfo.white = StrSave(first.tidy);
\r
11816 gameInfo.black = StrSave(UserName());
\r
11817 gameInfo.timeControl = TimeControlTagValue();
\r
11820 case MachinePlaysBlack:
\r
11821 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11822 gameInfo.site = StrSave(HostName());
\r
11823 gameInfo.date = PGNDate();
\r
11824 gameInfo.round = StrSave("-");
\r
11825 gameInfo.white = StrSave(UserName());
\r
11826 gameInfo.black = StrSave(first.tidy);
\r
11827 gameInfo.timeControl = TimeControlTagValue();
\r
11830 case TwoMachinesPlay:
\r
11831 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11832 gameInfo.site = StrSave(HostName());
\r
11833 gameInfo.date = PGNDate();
\r
11834 if (matchGame > 0) {
\r
11835 char buf[MSG_SIZ];
\r
11836 sprintf(buf, "%d", matchGame);
\r
11837 gameInfo.round = StrSave(buf);
\r
11839 gameInfo.round = StrSave("-");
\r
11841 if (first.twoMachinesColor[0] == 'w') {
\r
11842 gameInfo.white = StrSave(first.tidy);
\r
11843 gameInfo.black = StrSave(second.tidy);
\r
11845 gameInfo.white = StrSave(second.tidy);
\r
11846 gameInfo.black = StrSave(first.tidy);
\r
11848 gameInfo.timeControl = TimeControlTagValue();
\r
11852 gameInfo.event = StrSave("Edited game");
\r
11853 gameInfo.site = StrSave(HostName());
\r
11854 gameInfo.date = PGNDate();
\r
11855 gameInfo.round = StrSave("-");
\r
11856 gameInfo.white = StrSave("-");
\r
11857 gameInfo.black = StrSave("-");
\r
11860 case EditPosition:
\r
11861 gameInfo.event = StrSave("Edited position");
\r
11862 gameInfo.site = StrSave(HostName());
\r
11863 gameInfo.date = PGNDate();
\r
11864 gameInfo.round = StrSave("-");
\r
11865 gameInfo.white = StrSave("-");
\r
11866 gameInfo.black = StrSave("-");
\r
11869 case IcsPlayingWhite:
\r
11870 case IcsPlayingBlack:
\r
11871 case IcsObserving:
\r
11872 case IcsExamining:
\r
11875 case PlayFromGameFile:
\r
11876 gameInfo.event = StrSave("Game from non-PGN file");
\r
11877 gameInfo.site = StrSave(HostName());
\r
11878 gameInfo.date = PGNDate();
\r
11879 gameInfo.round = StrSave("-");
\r
11880 gameInfo.white = StrSave("?");
\r
11881 gameInfo.black = StrSave("?");
\r
11890 ReplaceComment(index, text)
\r
11896 while (*text == '\n') text++;
\r
11897 len = strlen(text);
\r
11898 while (len > 0 && text[len - 1] == '\n') len--;
\r
11900 if (commentList[index] != NULL)
\r
11901 free(commentList[index]);
\r
11904 commentList[index] = NULL;
\r
11907 commentList[index] = (char *) malloc(len + 2);
\r
11908 strncpy(commentList[index], text, len);
\r
11909 commentList[index][len] = '\n';
\r
11910 commentList[index][len + 1] = NULLCHAR;
\r
11923 if (ch == '\r') continue;
\r
11925 } while (ch != '\0');
\r
11929 AppendComment(index, text)
\r
11936 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11939 while (*text == '\n') text++;
\r
11940 len = strlen(text);
\r
11941 while (len > 0 && text[len - 1] == '\n') len--;
\r
11943 if (len == 0) return;
\r
11945 if (commentList[index] != NULL) {
\r
11946 old = commentList[index];
\r
11947 oldlen = strlen(old);
\r
11948 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11949 strcpy(commentList[index], old);
\r
11951 strncpy(&commentList[index][oldlen], text, len);
\r
11952 commentList[index][oldlen + len] = '\n';
\r
11953 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11955 commentList[index] = (char *) malloc(len + 2);
\r
11956 strncpy(commentList[index], text, len);
\r
11957 commentList[index][len] = '\n';
\r
11958 commentList[index][len + 1] = NULLCHAR;
\r
11962 static char * FindStr( char * text, char * sub_text )
\r
11964 char * result = strstr( text, sub_text );
\r
11966 if( result != NULL ) {
\r
11967 result += strlen( sub_text );
\r
11973 /* [AS] Try to extract PV info from PGN comment */
\r
11974 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11975 char *GetInfoFromComment( int index, char * text )
\r
11977 char * sep = text;
\r
11979 if( text != NULL && index > 0 ) {
\r
11982 int time = -1, sec = 0, deci;
\r
11983 char * s_eval = FindStr( text, "[%eval " );
\r
11984 char * s_emt = FindStr( text, "[%emt " );
\r
11986 if( s_eval != NULL || s_emt != NULL ) {
\r
11990 if( s_eval != NULL ) {
\r
11991 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11995 if( delim != ']' ) {
\r
12000 if( s_emt != NULL ) {
\r
12004 /* We expect something like: [+|-]nnn.nn/dd */
\r
12005 int score_lo = 0;
\r
12007 sep = strchr( text, '/' );
\r
12008 if( sep == NULL || sep < (text+4) ) {
\r
12012 time = -1; sec = -1; deci = -1;
\r
12013 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12014 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12015 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12016 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12020 if( score_lo < 0 || score_lo >= 100 ) {
\r
12024 if(sec >= 0) time = 600*time + 10*sec; else
\r
12025 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12027 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12029 /* [HGM] PV time: now locate end of PV info */
\r
12030 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12032 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12034 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12036 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12037 while(*sep == ' ') sep++;
\r
12040 if( depth <= 0 ) {
\r
12048 pvInfoList[index-1].depth = depth;
\r
12049 pvInfoList[index-1].score = score;
\r
12050 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12056 SendToProgram(message, cps)
\r
12058 ChessProgramState *cps;
\r
12060 int count, outCount, error;
\r
12061 char buf[MSG_SIZ];
\r
12063 if (cps->pr == NULL) return;
\r
12066 if (appData.debugMode) {
\r
12068 GetTimeMark(&now);
\r
12069 fprintf(debugFP, "%ld >%-6s: %s",
\r
12070 SubtractTimeMarks(&now, &programStartTime),
\r
12071 cps->which, message);
\r
12074 count = strlen(message);
\r
12075 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12076 if (outCount < count && !exiting
\r
12077 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12078 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12079 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12080 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12081 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12082 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12084 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12086 gameInfo.resultDetails = buf;
\r
12088 DisplayFatalError(buf, error, 1);
\r
12093 ReceiveFromProgram(isr, closure, message, count, error)
\r
12094 InputSourceRef isr;
\r
12095 VOIDSTAR closure;
\r
12101 char buf[MSG_SIZ];
\r
12102 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12104 if (isr != cps->isr) return; /* Killed intentionally */
\r
12105 if (count <= 0) {
\r
12106 if (count == 0) {
\r
12108 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12109 cps->which, cps->program);
\r
12110 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12111 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12112 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12113 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12115 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12117 gameInfo.resultDetails = buf;
\r
12119 RemoveInputSource(cps->isr);
\r
12120 DisplayFatalError(buf, 0, 1);
\r
12123 _("Error reading from %s chess program (%s)"),
\r
12124 cps->which, cps->program);
\r
12125 RemoveInputSource(cps->isr);
\r
12127 /* [AS] Program is misbehaving badly... kill it */
\r
12128 if( count == -2 ) {
\r
12129 DestroyChildProcess( cps->pr, 9 );
\r
12130 cps->pr = NoProc;
\r
12133 DisplayFatalError(buf, error, 1);
\r
12138 if ((end_str = strchr(message, '\r')) != NULL)
\r
12139 *end_str = NULLCHAR;
\r
12140 if ((end_str = strchr(message, '\n')) != NULL)
\r
12141 *end_str = NULLCHAR;
\r
12143 if (appData.debugMode) {
\r
12144 TimeMark now; int print = 1;
\r
12145 char *quote = ""; char c; int i;
\r
12147 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12148 char start = message[0];
\r
12149 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12150 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12151 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12152 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12153 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12154 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12155 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12156 { quote = "# "; print = (appData.engineComments == 2); }
\r
12157 message[0] = start; // restore original message
\r
12160 GetTimeMark(&now);
\r
12161 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12162 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12168 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12169 if (appData.icsEngineAnalyze) {
\r
12170 if (strstr(message, "whisper") != NULL ||
\r
12171 strstr(message, "kibitz") != NULL ||
\r
12172 strstr(message, "tellics") != NULL) return;
\r
12175 HandleMachineMove(message, cps);
\r
12180 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12181 ChessProgramState *cps;
\r
12182 int mps, inc, sd, st;
\r
12185 char buf[MSG_SIZ];
\r
12188 if( timeControl_2 > 0 ) {
\r
12189 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12190 tc = timeControl_2;
\r
12193 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12194 inc /= cps->timeOdds;
\r
12195 st /= cps->timeOdds;
\r
12197 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12200 /* Set exact time per move, normally using st command */
\r
12201 if (cps->stKludge) {
\r
12202 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12203 seconds = st % 60;
\r
12204 if (seconds == 0) {
\r
12205 sprintf(buf, "level 1 %d\n", st/60);
\r
12207 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12210 sprintf(buf, "st %d\n", st);
\r
12213 /* Set conventional or incremental time control, using level command */
\r
12214 if (seconds == 0) {
\r
12215 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12216 Fixed in later versions, but still avoid :seconds
\r
12217 when seconds is 0. */
\r
12218 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12220 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12221 seconds, inc/1000);
\r
12224 SendToProgram(buf, cps);
\r
12226 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12227 /* Orthogonally, limit search to given depth */
\r
12229 if (cps->sdKludge) {
\r
12230 sprintf(buf, "depth\n%d\n", sd);
\r
12232 sprintf(buf, "sd %d\n", sd);
\r
12234 SendToProgram(buf, cps);
\r
12237 if(cps->nps > 0) { /* [HGM] nps */
\r
12238 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12240 sprintf(buf, "nps %d\n", cps->nps);
\r
12241 SendToProgram(buf, cps);
\r
12246 ChessProgramState *WhitePlayer()
\r
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12249 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12250 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12256 SendTimeRemaining(cps, machineWhite)
\r
12257 ChessProgramState *cps;
\r
12258 int /*boolean*/ machineWhite;
\r
12260 char message[MSG_SIZ];
\r
12261 long time, otime;
\r
12263 /* Note: this routine must be called when the clocks are stopped
\r
12264 or when they have *just* been set or switched; otherwise
\r
12265 it will be off by the time since the current tick started.
\r
12267 if (machineWhite) {
\r
12268 time = whiteTimeRemaining / 10;
\r
12269 otime = blackTimeRemaining / 10;
\r
12271 time = blackTimeRemaining / 10;
\r
12272 otime = whiteTimeRemaining / 10;
\r
12274 /* [HGM] translate opponent's time by time-odds factor */
\r
12275 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12276 if (appData.debugMode) {
\r
12277 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12280 if (time <= 0) time = 1;
\r
12281 if (otime <= 0) otime = 1;
\r
12283 sprintf(message, "time %ld\n", time);
\r
12284 SendToProgram(message, cps);
\r
12286 sprintf(message, "otim %ld\n", otime);
\r
12287 SendToProgram(message, cps);
\r
12291 BoolFeature(p, name, loc, cps)
\r
12295 ChessProgramState *cps;
\r
12297 char buf[MSG_SIZ];
\r
12298 int len = strlen(name);
\r
12300 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12302 sscanf(*p, "%d", &val);
\r
12303 *loc = (val != 0);
\r
12304 while (**p && **p != ' ') (*p)++;
\r
12305 sprintf(buf, "accepted %s\n", name);
\r
12306 SendToProgram(buf, cps);
\r
12313 IntFeature(p, name, loc, cps)
\r
12317 ChessProgramState *cps;
\r
12319 char buf[MSG_SIZ];
\r
12320 int len = strlen(name);
\r
12321 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12323 sscanf(*p, "%d", loc);
\r
12324 while (**p && **p != ' ') (*p)++;
\r
12325 sprintf(buf, "accepted %s\n", name);
\r
12326 SendToProgram(buf, cps);
\r
12333 StringFeature(p, name, loc, cps)
\r
12337 ChessProgramState *cps;
\r
12339 char buf[MSG_SIZ];
\r
12340 int len = strlen(name);
\r
12341 if (strncmp((*p), name, len) == 0
\r
12342 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12344 sscanf(*p, "%[^\"]", loc);
\r
12345 while (**p && **p != '\"') (*p)++;
\r
12346 if (**p == '\"') (*p)++;
\r
12347 sprintf(buf, "accepted %s\n", name);
\r
12348 SendToProgram(buf, cps);
\r
12355 ParseOption(Option *opt, ChessProgramState *cps)
\r
12356 // [HGM] options: process the string that defines an engine option, and determine
\r
12357 // name, type, default value, and allowed value range
\r
12359 char *p, *q, buf[MSG_SIZ];
\r
12360 int n, min = (-1)<<31, max = 1<<31, def;
\r
12362 if(p = strstr(opt->name, " -spin ")) {
\r
12363 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12364 if(max < min) max = min; // enforce consistency
\r
12365 if(def < min) def = min;
\r
12366 if(def > max) def = max;
\r
12367 opt->value = def;
\r
12370 opt->type = Spin;
\r
12371 } else if(p = strstr(opt->name, " -string ")) {
\r
12372 opt->textValue = p+9;
\r
12373 opt->type = TextBox;
\r
12374 } else if(p = strstr(opt->name, " -check ")) {
\r
12375 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12376 opt->value = (def != 0);
\r
12377 opt->type = CheckBox;
\r
12378 } else if(p = strstr(opt->name, " -combo ")) {
\r
12379 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12380 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12381 opt->value = n = 0;
\r
12382 while(q = StrStr(q, " /// ")) {
\r
12383 n++; *q = 0; // count choices, and null-terminate each of them
\r
12385 if(*q == '*') { // remember default, which is marked with * prefix
\r
12389 cps->comboList[cps->comboCnt++] = q;
\r
12391 cps->comboList[cps->comboCnt++] = NULL;
\r
12392 opt->max = n + 1;
\r
12393 opt->type = ComboBox;
\r
12394 } else if(p = strstr(opt->name, " -button")) {
\r
12395 opt->type = Button;
\r
12396 } else if(p = strstr(opt->name, " -save")) {
\r
12397 opt->type = SaveButton;
\r
12398 } else return FALSE;
\r
12399 *p = 0; // terminate option name
\r
12400 // now look if the command-line options define a setting for this engine option.
\r
12401 p = strstr(cps->optionSettings, opt->name);
\r
12402 if(p == cps->optionSettings || p[-1] == ',') {
\r
12403 sprintf(buf, "option %s", p);
\r
12404 if(p = strstr(buf, ",")) *p = 0;
\r
12405 strcat(buf, "\n");
\r
12406 SendToProgram(buf, cps);
\r
12412 FeatureDone(cps, val)
\r
12413 ChessProgramState* cps;
\r
12416 DelayedEventCallback cb = GetDelayedEvent();
\r
12417 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12418 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12419 CancelDelayedEvent();
\r
12420 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12422 cps->initDone = val;
\r
12425 /* Parse feature command from engine */
\r
12427 ParseFeatures(args, cps)
\r
12429 ChessProgramState *cps;
\r
12434 char buf[MSG_SIZ];
\r
12437 while (*p == ' ') p++;
\r
12438 if (*p == NULLCHAR) return;
\r
12440 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12441 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12442 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12443 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12444 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12445 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12446 /* Engine can disable reuse, but can't enable it if user said no */
\r
12447 if (!val) cps->reuse = FALSE;
\r
12450 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12451 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12452 if (gameMode == TwoMachinesPlay) {
\r
12453 DisplayTwoMachinesTitle();
\r
12455 DisplayTitle("");
\r
12459 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12460 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12461 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12462 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12463 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12464 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12465 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12466 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12467 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12468 if (IntFeature(&p, "done", &val, cps)) {
\r
12469 FeatureDone(cps, val);
\r
12472 /* Added by Tord: */
\r
12473 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12474 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12475 /* End of additions by Tord */
\r
12477 /* [HGM] added features: */
\r
12478 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12479 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12480 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12481 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12482 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12483 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12484 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12485 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12486 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12487 cps->nrOptions--;
\r
12488 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12489 DisplayError(buf, 0);
\r
12493 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12494 /* End of additions by HGM */
\r
12496 /* unknown feature: complain and skip */
\r
12498 while (*q && *q != '=') q++;
\r
12499 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12500 SendToProgram(buf, cps);
\r
12504 if (*p == '\"') {
\r
12506 while (*p && *p != '\"') p++;
\r
12507 if (*p == '\"') p++;
\r
12509 while (*p && *p != ' ') p++;
\r
12517 PeriodicUpdatesEvent(newState)
\r
12520 if (newState == appData.periodicUpdates)
\r
12523 appData.periodicUpdates=newState;
\r
12525 /* Display type changes, so update it now */
\r
12526 DisplayAnalysis();
\r
12528 /* Get the ball rolling again... */
\r
12530 AnalysisPeriodicEvent(1);
\r
12531 StartAnalysisClock();
\r
12536 PonderNextMoveEvent(newState)
\r
12539 if (newState == appData.ponderNextMove) return;
\r
12540 if (gameMode == EditPosition) EditPositionDone();
\r
12542 SendToProgram("hard\n", &first);
\r
12543 if (gameMode == TwoMachinesPlay) {
\r
12544 SendToProgram("hard\n", &second);
\r
12547 SendToProgram("easy\n", &first);
\r
12548 thinkOutput[0] = NULLCHAR;
\r
12549 if (gameMode == TwoMachinesPlay) {
\r
12550 SendToProgram("easy\n", &second);
\r
12553 appData.ponderNextMove = newState;
\r
12557 NewSettingEvent(option, command, value)
\r
12559 int option, value;
\r
12561 char buf[MSG_SIZ];
\r
12563 if (gameMode == EditPosition) EditPositionDone();
\r
12564 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12565 SendToProgram(buf, &first);
\r
12566 if (gameMode == TwoMachinesPlay) {
\r
12567 SendToProgram(buf, &second);
\r
12572 ShowThinkingEvent()
\r
12573 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12575 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12576 int newState = appData.showThinking
\r
12577 // [HGM] thinking: other features now need thinking output as well
\r
12578 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12580 if (oldState == newState) return;
\r
12581 oldState = newState;
\r
12582 if (gameMode == EditPosition) EditPositionDone();
\r
12584 SendToProgram("post\n", &first);
\r
12585 if (gameMode == TwoMachinesPlay) {
\r
12586 SendToProgram("post\n", &second);
\r
12589 SendToProgram("nopost\n", &first);
\r
12590 thinkOutput[0] = NULLCHAR;
\r
12591 if (gameMode == TwoMachinesPlay) {
\r
12592 SendToProgram("nopost\n", &second);
\r
12595 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12599 AskQuestionEvent(title, question, replyPrefix, which)
\r
12600 char *title; char *question; char *replyPrefix; char *which;
\r
12602 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12603 if (pr == NoProc) return;
\r
12604 AskQuestion(title, question, replyPrefix, pr);
\r
12608 DisplayMove(moveNumber)
\r
12611 char message[MSG_SIZ];
\r
12612 char res[MSG_SIZ];
\r
12613 char cpThinkOutput[MSG_SIZ];
\r
12615 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12617 if (moveNumber == forwardMostMove - 1 ||
\r
12618 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12620 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12622 if (strchr(cpThinkOutput, '\n')) {
\r
12623 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12626 *cpThinkOutput = NULLCHAR;
\r
12629 /* [AS] Hide thinking from human user */
\r
12630 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12631 *cpThinkOutput = NULLCHAR;
\r
12632 if( thinkOutput[0] != NULLCHAR ) {
\r
12635 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12636 cpThinkOutput[i] = '.';
\r
12638 cpThinkOutput[i] = NULLCHAR;
\r
12639 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12643 if (moveNumber == forwardMostMove - 1 &&
\r
12644 gameInfo.resultDetails != NULL) {
\r
12645 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12646 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12648 sprintf(res, " {%s} %s",
\r
12649 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12652 res[0] = NULLCHAR;
\r
12655 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12656 DisplayMessage(res, cpThinkOutput);
\r
12658 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12659 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12660 parseList[moveNumber], res);
\r
12661 DisplayMessage(message, cpThinkOutput);
\r
12666 DisplayAnalysisText(text)
\r
12669 char buf[MSG_SIZ];
\r
12671 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12672 || appData.icsEngineAnalyze) {
\r
12673 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12674 AnalysisPopUp(buf, text);
\r
12679 only_one_move(str)
\r
12682 while (*str && isspace(*str)) ++str;
\r
12683 while (*str && !isspace(*str)) ++str;
\r
12684 if (!*str) return 1;
\r
12685 while (*str && isspace(*str)) ++str;
\r
12686 if (!*str) return 1;
\r
12691 DisplayAnalysis()
\r
12693 char buf[MSG_SIZ];
\r
12694 char lst[MSG_SIZ / 2];
\r
12696 static char *xtra[] = { "", " (--)", " (++)" };
\r
12699 if (programStats.time == 0) {
\r
12700 programStats.time = 1;
\r
12703 if (programStats.got_only_move) {
\r
12704 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12706 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12708 nps = (u64ToDouble(programStats.nodes) /
\r
12709 ((double)programStats.time /100.0));
\r
12711 cs = programStats.time % 100;
\r
12712 s = programStats.time / 100;
\r
12713 h = (s / (60*60));
\r
12718 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12719 if (programStats.move_name[0] != NULLCHAR) {
\r
12720 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12721 programStats.depth,
\r
12722 programStats.nr_moves-programStats.moves_left,
\r
12723 programStats.nr_moves, programStats.move_name,
\r
12724 ((float)programStats.score)/100.0, lst,
\r
12725 only_one_move(lst)?
\r
12726 xtra[programStats.got_fail] : "",
\r
12727 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12729 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12730 programStats.depth,
\r
12731 programStats.nr_moves-programStats.moves_left,
\r
12732 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12734 only_one_move(lst)?
\r
12735 xtra[programStats.got_fail] : "",
\r
12736 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12739 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12740 programStats.depth,
\r
12741 ((float)programStats.score)/100.0,
\r
12743 only_one_move(lst)?
\r
12744 xtra[programStats.got_fail] : "",
\r
12745 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12748 DisplayAnalysisText(buf);
\r
12752 DisplayComment(moveNumber, text)
\r
12756 char title[MSG_SIZ];
\r
12757 char buf[8000]; // comment can be long!
\r
12758 int score, depth;
\r
12760 if( appData.autoDisplayComment ) {
\r
12761 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12762 strcpy(title, "Comment");
\r
12764 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12765 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12766 parseList[moveNumber]);
\r
12768 } else title[0] = 0;
\r
12770 // [HGM] PV info: display PV info together with (or as) comment
\r
12771 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12772 if(text == NULL) text = "";
\r
12773 score = pvInfoList[moveNumber].score;
\r
12774 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12775 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12776 CommentPopUp(title, buf);
\r
12778 if (text != NULL)
\r
12779 CommentPopUp(title, text);
\r
12782 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12783 * might be busy thinking or pondering. It can be omitted if your
\r
12784 * gnuchess is configured to stop thinking immediately on any user
\r
12785 * input. However, that gnuchess feature depends on the FIONREAD
\r
12786 * ioctl, which does not work properly on some flavors of Unix.
\r
12790 ChessProgramState *cps;
\r
12793 if (!cps->useSigint) return;
\r
12794 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12795 switch (gameMode) {
\r
12796 case MachinePlaysWhite:
\r
12797 case MachinePlaysBlack:
\r
12798 case TwoMachinesPlay:
\r
12799 case IcsPlayingWhite:
\r
12800 case IcsPlayingBlack:
\r
12801 case AnalyzeMode:
\r
12802 case AnalyzeFile:
\r
12803 /* Skip if we know it isn't thinking */
\r
12804 if (!cps->maybeThinking) return;
\r
12805 if (appData.debugMode)
\r
12806 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12807 InterruptChildProcess(cps->pr);
\r
12808 cps->maybeThinking = FALSE;
\r
12813 #endif /*ATTENTION*/
\r
12819 if (whiteTimeRemaining <= 0) {
\r
12820 if (!whiteFlag) {
\r
12821 whiteFlag = TRUE;
\r
12822 if (appData.icsActive) {
\r
12823 if (appData.autoCallFlag &&
\r
12824 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12825 SendToICS(ics_prefix);
\r
12826 SendToICS("flag\n");
\r
12830 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12832 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12833 if (appData.autoCallFlag) {
\r
12834 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12841 if (blackTimeRemaining <= 0) {
\r
12842 if (!blackFlag) {
\r
12843 blackFlag = TRUE;
\r
12844 if (appData.icsActive) {
\r
12845 if (appData.autoCallFlag &&
\r
12846 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12847 SendToICS(ics_prefix);
\r
12848 SendToICS("flag\n");
\r
12852 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12854 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12855 if (appData.autoCallFlag) {
\r
12856 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12867 CheckTimeControl()
\r
12869 if (!appData.clockMode || appData.icsActive ||
\r
12870 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12873 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12875 if ( !WhiteOnMove(forwardMostMove) )
\r
12876 /* White made time control */
\r
12877 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12878 /* [HGM] time odds: correct new time quota for time odds! */
\r
12879 / WhitePlayer()->timeOdds;
\r
12881 /* Black made time control */
\r
12882 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12883 / WhitePlayer()->other->timeOdds;
\r
12887 DisplayBothClocks()
\r
12889 int wom = gameMode == EditPosition ?
\r
12890 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12891 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12892 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12896 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12897 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12898 to use other calls if you don't. Clocks will be less accurate if
\r
12899 you have neither ftime nor gettimeofday.
\r
12902 /* Get the current time as a TimeMark */
\r
12907 #if HAVE_GETTIMEOFDAY
\r
12909 struct timeval timeVal;
\r
12910 struct timezone timeZone;
\r
12912 gettimeofday(&timeVal, &timeZone);
\r
12913 tm->sec = (long) timeVal.tv_sec;
\r
12914 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12916 #else /*!HAVE_GETTIMEOFDAY*/
\r
12919 #include <sys/timeb.h>
\r
12920 struct timeb timeB;
\r
12923 tm->sec = (long) timeB.time;
\r
12924 tm->ms = (int) timeB.millitm;
\r
12926 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12927 tm->sec = (long) time(NULL);
\r
12933 /* Return the difference in milliseconds between two
\r
12934 time marks. We assume the difference will fit in a long!
\r
12937 SubtractTimeMarks(tm2, tm1)
\r
12938 TimeMark *tm2, *tm1;
\r
12940 return 1000L*(tm2->sec - tm1->sec) +
\r
12941 (long) (tm2->ms - tm1->ms);
\r
12946 * Code to manage the game clocks.
\r
12948 * In tournament play, black starts the clock and then white makes a move.
\r
12949 * We give the human user a slight advantage if he is playing white---the
\r
12950 * clocks don't run until he makes his first move, so it takes zero time.
\r
12951 * Also, we don't account for network lag, so we could get out of sync
\r
12952 * with GNU Chess's clock -- but then, referees are always right.
\r
12955 static TimeMark tickStartTM;
\r
12956 static long intendedTickLength;
\r
12959 NextTickLength(timeRemaining)
\r
12960 long timeRemaining;
\r
12962 long nominalTickLength, nextTickLength;
\r
12964 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12965 nominalTickLength = 100L;
\r
12967 nominalTickLength = 1000L;
\r
12968 nextTickLength = timeRemaining % nominalTickLength;
\r
12969 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12971 return nextTickLength;
\r
12974 /* Adjust clock one minute up or down */
\r
12976 AdjustClock(Boolean which, int dir)
\r
12978 if(which) blackTimeRemaining += 60000*dir;
\r
12979 else whiteTimeRemaining += 60000*dir;
\r
12980 DisplayBothClocks();
\r
12983 /* Stop clocks and reset to a fresh time control */
\r
12987 (void) StopClockTimer();
\r
12988 if (appData.icsActive) {
\r
12989 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12990 } else { /* [HGM] correct new time quote for time odds */
\r
12991 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12992 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12994 if (whiteFlag || blackFlag) {
\r
12995 DisplayTitle("");
\r
12996 whiteFlag = blackFlag = FALSE;
\r
12998 DisplayBothClocks();
\r
13001 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13003 /* Decrement running clock by amount of time that has passed */
\r
13005 DecrementClocks()
\r
13007 long timeRemaining;
\r
13008 long lastTickLength, fudge;
\r
13011 if (!appData.clockMode) return;
\r
13012 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13014 GetTimeMark(&now);
\r
13016 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13018 /* Fudge if we woke up a little too soon */
\r
13019 fudge = intendedTickLength - lastTickLength;
\r
13020 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13022 if (WhiteOnMove(forwardMostMove)) {
\r
13023 if(whiteNPS >= 0) lastTickLength = 0;
\r
13024 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13025 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13026 WhiteOnMove(currentMove));
\r
13028 if(blackNPS >= 0) lastTickLength = 0;
\r
13029 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13030 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13031 !WhiteOnMove(currentMove));
\r
13034 if (CheckFlags()) return;
\r
13036 tickStartTM = now;
\r
13037 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13038 StartClockTimer(intendedTickLength);
\r
13040 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13041 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13042 * with increment) the time remaining has increased to a level above the
\r
13043 * threshold, reset the alarm so it can sound again.
\r
13046 if (appData.icsActive && appData.icsAlarm) {
\r
13048 /* make sure we are dealing with the user's clock */
\r
13049 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13050 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13053 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13054 alarmSounded = FALSE;
\r
13055 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13056 PlayAlarmSound();
\r
13057 alarmSounded = TRUE;
\r
13063 /* A player has just moved, so stop the previously running
\r
13064 clock and (if in clock mode) start the other one.
\r
13065 We redisplay both clocks in case we're in ICS mode, because
\r
13066 ICS gives us an update to both clocks after every move.
\r
13067 Note that this routine is called *after* forwardMostMove
\r
13068 is updated, so the last fractional tick must be subtracted
\r
13069 from the color that is *not* on move now.
\r
13074 long lastTickLength;
\r
13076 int flagged = FALSE;
\r
13078 GetTimeMark(&now);
\r
13080 if (StopClockTimer() && appData.clockMode) {
\r
13081 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13082 if (WhiteOnMove(forwardMostMove)) {
\r
13083 if(blackNPS >= 0) lastTickLength = 0;
\r
13084 blackTimeRemaining -= lastTickLength;
\r
13085 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13086 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13087 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13088 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13090 if(whiteNPS >= 0) lastTickLength = 0;
\r
13091 whiteTimeRemaining -= lastTickLength;
\r
13092 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13093 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13094 pvInfoList[forwardMostMove-1].time =
\r
13095 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13097 flagged = CheckFlags();
\r
13099 CheckTimeControl();
\r
13101 if (flagged || !appData.clockMode) return;
\r
13103 switch (gameMode) {
\r
13104 case MachinePlaysBlack:
\r
13105 case MachinePlaysWhite:
\r
13106 case BeginningOfGame:
\r
13107 if (pausing) return;
\r
13111 case PlayFromGameFile:
\r
13112 case IcsExamining:
\r
13119 tickStartTM = now;
\r
13120 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13121 whiteTimeRemaining : blackTimeRemaining);
\r
13122 StartClockTimer(intendedTickLength);
\r
13126 /* Stop both clocks */
\r
13130 long lastTickLength;
\r
13133 if (!StopClockTimer()) return;
\r
13134 if (!appData.clockMode) return;
\r
13136 GetTimeMark(&now);
\r
13138 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13139 if (WhiteOnMove(forwardMostMove)) {
\r
13140 if(whiteNPS >= 0) lastTickLength = 0;
\r
13141 whiteTimeRemaining -= lastTickLength;
\r
13142 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13144 if(blackNPS >= 0) lastTickLength = 0;
\r
13145 blackTimeRemaining -= lastTickLength;
\r
13146 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13151 /* Start clock of player on move. Time may have been reset, so
\r
13152 if clock is already running, stop and restart it. */
\r
13156 (void) StopClockTimer(); /* in case it was running already */
\r
13157 DisplayBothClocks();
\r
13158 if (CheckFlags()) return;
\r
13160 if (!appData.clockMode) return;
\r
13161 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13163 GetTimeMark(&tickStartTM);
\r
13164 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13165 whiteTimeRemaining : blackTimeRemaining);
\r
13167 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13168 whiteNPS = blackNPS = -1;
\r
13169 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13170 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13171 whiteNPS = first.nps;
\r
13172 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13173 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13174 blackNPS = first.nps;
\r
13175 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13176 whiteNPS = second.nps;
\r
13177 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13178 blackNPS = second.nps;
\r
13179 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13181 StartClockTimer(intendedTickLength);
\r
13188 long second, minute, hour, day;
\r
13190 static char buf[32];
\r
13192 if (ms > 0 && ms <= 9900) {
\r
13193 /* convert milliseconds to tenths, rounding up */
\r
13194 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13196 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13200 /* convert milliseconds to seconds, rounding up */
\r
13201 /* use floating point to avoid strangeness of integer division
\r
13202 with negative dividends on many machines */
\r
13203 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13205 if (second < 0) {
\r
13207 second = -second;
\r
13210 day = second / (60 * 60 * 24);
\r
13211 second = second % (60 * 60 * 24);
\r
13212 hour = second / (60 * 60);
\r
13213 second = second % (60 * 60);
\r
13214 minute = second / 60;
\r
13215 second = second % 60;
\r
13218 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13219 sign, day, hour, minute, second);
\r
13220 else if (hour > 0)
\r
13221 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13223 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13230 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13233 StrStr(string, match)
\r
13234 char *string, *match;
\r
13238 length = strlen(match);
\r
13240 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13241 if (!strncmp(match, string, length))
\r
13248 StrCaseStr(string, match)
\r
13249 char *string, *match;
\r
13251 int i, j, length;
\r
13253 length = strlen(match);
\r
13255 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13256 for (j = 0; j < length; j++) {
\r
13257 if (ToLower(match[j]) != ToLower(string[j]))
\r
13260 if (j == length) return string;
\r
13266 #ifndef _amigados
\r
13268 StrCaseCmp(s1, s2)
\r
13274 c1 = ToLower(*s1++);
\r
13275 c2 = ToLower(*s2++);
\r
13276 if (c1 > c2) return 1;
\r
13277 if (c1 < c2) return -1;
\r
13278 if (c1 == NULLCHAR) return 0;
\r
13287 return isupper(c) ? tolower(c) : c;
\r
13295 return islower(c) ? toupper(c) : c;
\r
13297 #endif /* !_amigados */
\r
13305 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13312 StrSavePtr(s, savePtr)
\r
13313 char *s, **savePtr;
\r
13318 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13319 strcpy(*savePtr, s);
\r
13321 return(*savePtr);
\r
13329 char buf[MSG_SIZ];
\r
13331 clock = time((time_t *)NULL);
\r
13332 tm = localtime(&clock);
\r
13333 sprintf(buf, "%04d.%02d.%02d",
\r
13334 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13335 return StrSave(buf);
\r
13340 PositionToFEN(move, useFEN960)
\r
13344 int i, j, fromX, fromY, toX, toY;
\r
13349 ChessSquare piece;
\r
13351 whiteToPlay = (gameMode == EditPosition) ?
\r
13352 !blackPlaysFirst : (move % 2 == 0);
\r
13355 /* Piece placement data */
\r
13356 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13358 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13359 if (boards[move][i][j] == EmptySquare) {
\r
13361 } else { ChessSquare piece = boards[move][i][j];
\r
13362 if (emptycount > 0) {
\r
13363 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13364 *p++ = '0' + emptycount;
\r
13365 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13368 if(PieceToChar(piece) == '+') {
\r
13369 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13371 piece = (ChessSquare)(DEMOTED piece);
\r
13373 *p++ = PieceToChar(piece);
\r
13374 if(p[-1] == '~') {
\r
13375 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13376 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13381 if (emptycount > 0) {
\r
13382 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13383 *p++ = '0' + emptycount;
\r
13384 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13391 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13392 if( gameInfo.holdingsWidth ) {
\r
13393 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13395 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13396 piece = boards[move][i][BOARD_WIDTH-1];
\r
13397 if( piece != EmptySquare )
\r
13398 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13399 *p++ = PieceToChar(piece);
\r
13401 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13402 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13403 if( piece != EmptySquare )
\r
13404 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13405 *p++ = PieceToChar(piece);
\r
13408 if( q == p ) *p++ = '-';
\r
13413 /* Active color */
\r
13414 *p++ = whiteToPlay ? 'w' : 'b';
\r
13417 if(nrCastlingRights) {
\r
13419 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13420 /* [HGM] write directly from rights */
\r
13421 if(castlingRights[move][2] >= 0 &&
\r
13422 castlingRights[move][0] >= 0 )
\r
13423 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13424 if(castlingRights[move][2] >= 0 &&
\r
13425 castlingRights[move][1] >= 0 )
\r
13426 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13427 if(castlingRights[move][5] >= 0 &&
\r
13428 castlingRights[move][3] >= 0 )
\r
13429 *p++ = castlingRights[move][3] + AAA;
\r
13430 if(castlingRights[move][5] >= 0 &&
\r
13431 castlingRights[move][4] >= 0 )
\r
13432 *p++ = castlingRights[move][4] + AAA;
\r
13435 /* [HGM] write true castling rights */
\r
13436 if( nrCastlingRights == 6 ) {
\r
13437 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13438 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13439 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13440 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13441 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13442 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13443 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13444 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13447 if (q == p) *p++ = '-'; /* No castling rights */
\r
13451 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13452 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13453 /* En passant target square */
\r
13454 if (move > backwardMostMove) {
\r
13455 fromX = moveList[move - 1][0] - AAA;
\r
13456 fromY = moveList[move - 1][1] - ONE;
\r
13457 toX = moveList[move - 1][2] - AAA;
\r
13458 toY = moveList[move - 1][3] - ONE;
\r
13459 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13460 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13461 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13463 /* 2-square pawn move just happened */
\r
13464 *p++ = toX + AAA;
\r
13465 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13475 /* [HGM] find reversible plies */
\r
13476 { int i = 0, j=move;
\r
13478 if (appData.debugMode) { int k;
\r
13479 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13480 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13481 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13485 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13486 if( j == backwardMostMove ) i += initialRulePlies;
\r
13487 sprintf(p, "%d ", i);
\r
13488 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13490 /* Fullmove number */
\r
13491 sprintf(p, "%d", (move / 2) + 1);
\r
13493 return StrSave(buf);
\r
13497 ParseFEN(board, blackPlaysFirst, fen)
\r
13499 int *blackPlaysFirst;
\r
13505 ChessSquare piece;
\r
13509 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13510 if(gameInfo.holdingsWidth) {
\r
13511 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13512 board[i][0] = EmptySquare; /* black holdings */
\r
13513 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13514 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13515 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13519 /* Piece placement data */
\r
13520 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13523 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13524 if (*p == '/') p++;
\r
13525 emptycount = gameInfo.boardWidth - j;
\r
13526 while (emptycount--)
\r
13527 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13529 #if(BOARD_SIZE >= 10)
\r
13530 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13531 p++; emptycount=10;
\r
13532 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13533 while (emptycount--)
\r
13534 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13536 } else if (isdigit(*p)) {
\r
13537 emptycount = *p++ - '0';
\r
13538 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13539 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13540 while (emptycount--)
\r
13541 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13542 } else if (*p == '+' || isalpha(*p)) {
\r
13543 if (j >= gameInfo.boardWidth) return FALSE;
\r
13545 piece = CharToPiece(*++p);
\r
13546 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13547 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13548 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13549 } else piece = CharToPiece(*p++);
\r
13551 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13552 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13553 piece = (ChessSquare) (PROMOTED piece);
\r
13554 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13557 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13563 while (*p == '/' || *p == ' ') p++;
\r
13565 /* [HGM] look for Crazyhouse holdings here */
\r
13566 while(*p==' ') p++;
\r
13567 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13568 if(*p == '[') p++;
\r
13569 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13570 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13571 /* if we would allow FEN reading to set board size, we would */
\r
13572 /* have to add holdings and shift the board read so far here */
\r
13573 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13575 if((int) piece >= (int) BlackPawn ) {
\r
13576 i = (int)piece - (int)BlackPawn;
\r
13577 i = PieceToNumber((ChessSquare)i);
\r
13578 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13579 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13580 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13582 i = (int)piece - (int)WhitePawn;
\r
13583 i = PieceToNumber((ChessSquare)i);
\r
13584 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13585 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13586 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13590 if(*p == ']') *p++;
\r
13593 while(*p == ' ') p++;
\r
13595 /* Active color */
\r
13598 *blackPlaysFirst = FALSE;
\r
13601 *blackPlaysFirst = TRUE;
\r
13607 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13608 /* return the extra info in global variiables */
\r
13610 /* set defaults in case FEN is incomplete */
\r
13611 FENepStatus = EP_UNKNOWN;
\r
13612 for(i=0; i<nrCastlingRights; i++ ) {
\r
13613 FENcastlingRights[i] =
\r
13614 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13615 } /* assume possible unless obviously impossible */
\r
13616 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13617 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13618 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13619 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13620 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13621 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13622 FENrulePlies = 0;
\r
13624 while(*p==' ') p++;
\r
13625 if(nrCastlingRights) {
\r
13626 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13627 /* castling indicator present, so default becomes no castlings */
\r
13628 for(i=0; i<nrCastlingRights; i++ ) {
\r
13629 FENcastlingRights[i] = -1;
\r
13632 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13633 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13634 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13635 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13636 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13638 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13639 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13640 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13644 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13645 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13646 FENcastlingRights[2] = whiteKingFile;
\r
13649 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13650 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13651 FENcastlingRights[2] = whiteKingFile;
\r
13654 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13655 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13656 FENcastlingRights[5] = blackKingFile;
\r
13659 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13660 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13661 FENcastlingRights[5] = blackKingFile;
\r
13664 default: /* FRC castlings */
\r
13665 if(c >= 'a') { /* black rights */
\r
13666 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13667 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13668 if(i == BOARD_RGHT) break;
\r
13669 FENcastlingRights[5] = i;
\r
13671 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13672 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13674 FENcastlingRights[3] = c;
\r
13676 FENcastlingRights[4] = c;
\r
13677 } else { /* white rights */
\r
13678 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13679 if(board[0][i] == WhiteKing) break;
\r
13680 if(i == BOARD_RGHT) break;
\r
13681 FENcastlingRights[2] = i;
\r
13682 c -= AAA - 'a' + 'A';
\r
13683 if(board[0][c] >= WhiteKing) break;
\r
13685 FENcastlingRights[0] = c;
\r
13687 FENcastlingRights[1] = c;
\r
13691 if (appData.debugMode) {
\r
13692 fprintf(debugFP, "FEN castling rights:");
\r
13693 for(i=0; i<nrCastlingRights; i++)
\r
13694 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13695 fprintf(debugFP, "\n");
\r
13698 while(*p==' ') p++;
\r
13701 /* read e.p. field in games that know e.p. capture */
\r
13702 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13703 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13705 p++; FENepStatus = EP_NONE;
\r
13707 char c = *p++ - AAA;
\r
13709 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13710 if(*p >= '0' && *p <='9') *p++;
\r
13716 if(sscanf(p, "%d", &i) == 1) {
\r
13717 FENrulePlies = i; /* 50-move ply counter */
\r
13718 /* (The move number is still ignored) */
\r
13725 EditPositionPasteFEN(char *fen)
\r
13727 if (fen != NULL) {
\r
13728 Board initial_position;
\r
13730 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13731 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13734 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13735 EditPositionEvent();
\r
13736 blackPlaysFirst = savedBlackPlaysFirst;
\r
13737 CopyBoard(boards[0], initial_position);
\r
13738 /* [HGM] copy FEN attributes as well */
\r
13740 initialRulePlies = FENrulePlies;
\r
13741 epStatus[0] = FENepStatus;
\r
13742 for( i=0; i<nrCastlingRights; i++ )
\r
13743 castlingRights[0][i] = FENcastlingRights[i];
\r
13745 EditPositionDone();
\r
13746 DisplayBothClocks();
\r
13747 DrawPosition(FALSE, boards[currentMove]);
\r