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(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6000 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6001 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6004 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6005 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6006 // if neither of us is checking all the time, or both are, it is draw
\r
6007 // (illegal-chase forfeits not implemented yet!)
\r
6009 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6012 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6013 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6017 /* Now we test for 50-move draws. Determine ply count */
\r
6018 count = forwardMostMove;
\r
6019 /* look for last irreversble move */
\r
6020 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6022 /* if we hit starting position, add initial plies */
\r
6023 if( count == backwardMostMove )
\r
6024 count -= initialRulePlies;
\r
6025 count = forwardMostMove - count;
\r
6027 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6028 /* this is used to judge if draw claims are legal */
\r
6029 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6030 SendToProgram("force\n", cps->other); // suppress reply
\r
6031 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6032 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6033 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6037 /* if draw offer is pending, treat it as a draw claim
\r
6038 * when draw condition present, to allow engines a way to
\r
6039 * claim draws before making their move to avoid a race
\r
6040 * condition occurring after their move
\r
6042 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6044 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6045 p = "Draw claim: 50-move rule";
\r
6046 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6047 p = "Draw claim: 3-fold repetition";
\r
6048 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6049 p = "Draw claim: insufficient mating material";
\r
6051 SendToProgram("force\n", cps->other); // suppress reply
\r
6052 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6053 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6054 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6060 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6061 SendToProgram("force\n", cps->other); // suppress reply
\r
6062 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6063 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6065 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6072 if (gameMode == TwoMachinesPlay) {
\r
6073 /* [HGM] relaying draw offers moved to after reception of move */
\r
6074 /* and interpreting offer as claim if it brings draw condition */
\r
6075 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6076 SendToProgram("draw\n", cps->other);
\r
6078 if (cps->other->sendTime) {
\r
6079 SendTimeRemaining(cps->other,
\r
6080 cps->other->twoMachinesColor[0] == 'w');
\r
6082 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6083 if (firstMove && !bookHit) {
\r
6084 firstMove = FALSE;
\r
6085 if (cps->other->useColors) {
\r
6086 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6088 SendToProgram("go\n", cps->other);
\r
6090 cps->other->maybeThinking = TRUE;
\r
6093 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6095 if (!pausing && appData.ringBellAfterMoves) {
\r
6100 * Reenable menu items that were disabled while
\r
6101 * machine was thinking
\r
6103 if (gameMode != TwoMachinesPlay)
\r
6104 SetUserThinkingEnables();
\r
6106 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6107 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6108 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6110 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6112 strcpy(bookMove, "move ");
\r
6113 strcat(bookMove, bookHit);
\r
6114 message = bookMove;
\r
6116 programStats.depth = programStats.nodes = programStats.time =
\r
6117 programStats.score = programStats.got_only_move = 0;
\r
6118 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6120 if(cps->lastPing != cps->lastPong) {
\r
6121 savedMessage = message; // args for deferred call
\r
6123 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6126 goto FakeBookMove;
\r
6132 /* Set special modes for chess engines. Later something general
\r
6133 * could be added here; for now there is just one kludge feature,
\r
6134 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6135 * when "xboard" is given as an interactive command.
\r
6137 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6138 cps->useSigint = FALSE;
\r
6139 cps->useSigterm = FALSE;
\r
6142 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6143 * want this, I was asked to put it in, and obliged.
\r
6145 if (!strncmp(message, "setboard ", 9)) {
\r
6146 Board initial_position; int i;
\r
6148 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6150 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6151 DisplayError(_("Bad FEN received from engine"), 0);
\r
6154 Reset(FALSE, FALSE);
\r
6155 CopyBoard(boards[0], initial_position);
\r
6156 initialRulePlies = FENrulePlies;
\r
6157 epStatus[0] = FENepStatus;
\r
6158 for( i=0; i<nrCastlingRights; i++ )
\r
6159 castlingRights[0][i] = FENcastlingRights[i];
\r
6160 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6161 else gameMode = MachinePlaysBlack;
\r
6162 DrawPosition(FALSE, boards[currentMove]);
\r
6168 * Look for communication commands
\r
6170 if (!strncmp(message, "telluser ", 9)) {
\r
6171 DisplayNote(message + 9);
\r
6174 if (!strncmp(message, "tellusererror ", 14)) {
\r
6175 DisplayError(message + 14, 0);
\r
6178 if (!strncmp(message, "tellopponent ", 13)) {
\r
6179 if (appData.icsActive) {
\r
6181 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6185 DisplayNote(message + 13);
\r
6189 if (!strncmp(message, "tellothers ", 11)) {
\r
6190 if (appData.icsActive) {
\r
6192 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6198 if (!strncmp(message, "tellall ", 8)) {
\r
6199 if (appData.icsActive) {
\r
6201 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6205 DisplayNote(message + 8);
\r
6209 if (strncmp(message, "warning", 7) == 0) {
\r
6210 /* Undocumented feature, use tellusererror in new code */
\r
6211 DisplayError(message, 0);
\r
6214 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6215 strcpy(realname, cps->tidy);
\r
6216 strcat(realname, " query");
\r
6217 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6220 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6221 * sent until we are logged on. Crafty kibitzes have been known to
\r
6222 * interfere with the login process.
\r
6225 if (!strncmp(message, "tellics ", 8)) {
\r
6226 SendToICS(message + 8);
\r
6230 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6231 SendToICS(ics_prefix);
\r
6232 SendToICS(message + 15);
\r
6236 /* The following are for backward compatibility only */
\r
6237 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6238 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6239 SendToICS(ics_prefix);
\r
6240 SendToICS(message);
\r
6245 if (strncmp(message, "feature ", 8) == 0) {
\r
6246 ParseFeatures(message+8, cps);
\r
6248 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6252 * If the move is illegal, cancel it and redraw the board.
\r
6253 * Also deal with other error cases. Matching is rather loose
\r
6254 * here to accommodate engines written before the spec.
\r
6256 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6257 strncmp(message, "Error", 5) == 0) {
\r
6258 if (StrStr(message, "name") ||
\r
6259 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6260 StrStr(message, "result") || StrStr(message, "board") ||
\r
6261 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6262 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6263 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6264 StrStr(message, "accepted")) {
\r
6267 if (StrStr(message, "protover")) {
\r
6268 /* Program is responding to input, so it's apparently done
\r
6269 initializing, and this error message indicates it is
\r
6270 protocol version 1. So we don't need to wait any longer
\r
6271 for it to initialize and send feature commands. */
\r
6272 FeatureDone(cps, 1);
\r
6273 cps->protocolVersion = 1;
\r
6276 cps->maybeThinking = FALSE;
\r
6278 if (StrStr(message, "draw")) {
\r
6279 /* Program doesn't have "draw" command */
\r
6280 cps->sendDrawOffers = 0;
\r
6283 if (cps->sendTime != 1 &&
\r
6284 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6285 /* Program apparently doesn't have "time" or "otim" command */
\r
6286 cps->sendTime = 0;
\r
6289 if (StrStr(message, "analyze")) {
\r
6290 cps->analysisSupport = FALSE;
\r
6291 cps->analyzing = FALSE;
\r
6292 Reset(FALSE, TRUE);
\r
6293 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6294 DisplayError(buf2, 0);
\r
6297 if (StrStr(message, "(no matching move)st")) {
\r
6298 /* Special kludge for GNU Chess 4 only */
\r
6299 cps->stKludge = TRUE;
\r
6300 SendTimeControl(cps, movesPerSession, timeControl,
\r
6301 timeIncrement, appData.searchDepth,
\r
6305 if (StrStr(message, "(no matching move)sd")) {
\r
6306 /* Special kludge for GNU Chess 4 only */
\r
6307 cps->sdKludge = TRUE;
\r
6308 SendTimeControl(cps, movesPerSession, timeControl,
\r
6309 timeIncrement, appData.searchDepth,
\r
6313 if (!StrStr(message, "llegal")) {
\r
6316 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6317 gameMode == IcsIdle) return;
\r
6318 if (forwardMostMove <= backwardMostMove) return;
\r
6320 /* Following removed: it caused a bug where a real illegal move
\r
6321 message in analyze mored would be ignored. */
\r
6322 if (cps == &first && programStats.ok_to_send == 0) {
\r
6323 /* Bogus message from Crafty responding to "." This filtering
\r
6324 can miss some of the bad messages, but fortunately the bug
\r
6325 is fixed in current Crafty versions, so it doesn't matter. */
\r
6329 if (pausing) PauseEvent();
\r
6330 if (gameMode == PlayFromGameFile) {
\r
6331 /* Stop reading this game file */
\r
6332 gameMode = EditGame;
\r
6335 currentMove = --forwardMostMove;
\r
6336 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6338 DisplayBothClocks();
\r
6339 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6340 parseList[currentMove], cps->which);
\r
6341 DisplayMoveError(buf1);
\r
6342 DrawPosition(FALSE, boards[currentMove]);
\r
6344 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6345 /* only passes fully legal moves */
\r
6346 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6347 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6348 "False illegal-move claim", GE_XBOARD );
\r
6352 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6353 /* Program has a broken "time" command that
\r
6354 outputs a string not ending in newline.
\r
6356 cps->sendTime = 0;
\r
6360 * If chess program startup fails, exit with an error message.
\r
6361 * Attempts to recover here are futile.
\r
6363 if ((StrStr(message, "unknown host") != NULL)
\r
6364 || (StrStr(message, "No remote directory") != NULL)
\r
6365 || (StrStr(message, "not found") != NULL)
\r
6366 || (StrStr(message, "No such file") != NULL)
\r
6367 || (StrStr(message, "can't alloc") != NULL)
\r
6368 || (StrStr(message, "Permission denied") != NULL)) {
\r
6370 cps->maybeThinking = FALSE;
\r
6371 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6372 cps->which, cps->program, cps->host, message);
\r
6373 RemoveInputSource(cps->isr);
\r
6374 DisplayFatalError(buf1, 0, 1);
\r
6379 * Look for hint output
\r
6381 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6382 if (cps == &first && hintRequested) {
\r
6383 hintRequested = FALSE;
\r
6384 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6385 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6386 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6387 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6388 fromY, fromX, toY, toX, promoChar, buf1);
\r
6389 sprintf(buf2, _("Hint: %s"), buf1);
\r
6390 DisplayInformation(buf2);
\r
6392 /* Hint move could not be parsed!? */
\r
6394 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6395 buf1, cps->which);
\r
6396 DisplayError(buf2, 0);
\r
6399 strcpy(lastHint, buf1);
\r
6405 * Ignore other messages if game is not in progress
\r
6407 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6408 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6411 * look for win, lose, draw, or draw offer
\r
6413 if (strncmp(message, "1-0", 3) == 0) {
\r
6414 char *p, *q, *r = "";
\r
6415 p = strchr(message, '{');
\r
6417 q = strchr(p, '}');
\r
6423 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6425 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6426 char *p, *q, *r = "";
\r
6427 p = strchr(message, '{');
\r
6429 q = strchr(p, '}');
\r
6435 /* Kludge for Arasan 4.1 bug */
\r
6436 if (strcmp(r, "Black resigns") == 0) {
\r
6437 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6440 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6442 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6443 char *p, *q, *r = "";
\r
6444 p = strchr(message, '{');
\r
6446 q = strchr(p, '}');
\r
6453 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6456 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6457 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6459 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6460 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6462 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6463 strncmp(message, "Black matches", 13) == 0 ) {
\r
6464 /* [HGM] ignore GNUShogi noises */
\r
6466 } else if (strncmp(message, "White", 5) == 0 &&
\r
6467 message[5] != '(' &&
\r
6468 StrStr(message, "Black") == NULL) {
\r
6469 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6471 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6472 message[5] != '(') {
\r
6473 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6475 } else if (strcmp(message, "resign") == 0 ||
\r
6476 strcmp(message, "computer resigns") == 0) {
\r
6477 switch (gameMode) {
\r
6478 case MachinePlaysBlack:
\r
6479 case IcsPlayingBlack:
\r
6480 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6482 case MachinePlaysWhite:
\r
6483 case IcsPlayingWhite:
\r
6484 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6486 case TwoMachinesPlay:
\r
6487 if (cps->twoMachinesColor[0] == 'w')
\r
6488 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6490 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6493 /* can't happen */
\r
6497 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6498 switch (gameMode) {
\r
6499 case MachinePlaysBlack:
\r
6500 case IcsPlayingBlack:
\r
6501 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6503 case MachinePlaysWhite:
\r
6504 case IcsPlayingWhite:
\r
6505 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6507 case TwoMachinesPlay:
\r
6508 if (cps->twoMachinesColor[0] == 'w')
\r
6509 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6511 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6514 /* can't happen */
\r
6518 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6519 switch (gameMode) {
\r
6520 case MachinePlaysBlack:
\r
6521 case IcsPlayingBlack:
\r
6522 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6524 case MachinePlaysWhite:
\r
6525 case IcsPlayingWhite:
\r
6526 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6528 case TwoMachinesPlay:
\r
6529 if (cps->twoMachinesColor[0] == 'w')
\r
6530 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6532 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6535 /* can't happen */
\r
6539 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6540 if (WhiteOnMove(forwardMostMove)) {
\r
6541 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6543 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6546 } else if (strstr(message, "Draw") != NULL ||
\r
6547 strstr(message, "game is a draw") != NULL) {
\r
6548 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6550 } else if (strstr(message, "offer") != NULL &&
\r
6551 strstr(message, "draw") != NULL) {
\r
6553 if (appData.zippyPlay && first.initDone) {
\r
6554 /* Relay offer to ICS */
\r
6555 SendToICS(ics_prefix);
\r
6556 SendToICS("draw\n");
\r
6559 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6560 if (gameMode == TwoMachinesPlay) {
\r
6561 if (cps->other->offeredDraw) {
\r
6562 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6563 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6564 /* until after we also have move, to see if it is really claim */
\r
6568 if (cps->other->sendDrawOffers) {
\r
6569 SendToProgram("draw\n", cps->other);
\r
6573 } else if (gameMode == MachinePlaysWhite ||
\r
6574 gameMode == MachinePlaysBlack) {
\r
6575 if (userOfferedDraw) {
\r
6576 DisplayInformation(_("Machine accepts your draw offer"));
\r
6577 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6579 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6586 * Look for thinking output
\r
6588 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6589 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6591 int plylev, mvleft, mvtot, curscore, time;
\r
6592 char mvname[MOVE_LEN];
\r
6593 u64 nodes; // [DM]
\r
6595 int ignore = FALSE;
\r
6596 int prefixHint = FALSE;
\r
6597 mvname[0] = NULLCHAR;
\r
6599 switch (gameMode) {
\r
6600 case MachinePlaysBlack:
\r
6601 case IcsPlayingBlack:
\r
6602 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6604 case MachinePlaysWhite:
\r
6605 case IcsPlayingWhite:
\r
6606 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6611 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6612 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6614 case TwoMachinesPlay:
\r
6615 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6625 buf1[0] = NULLCHAR;
\r
6626 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6627 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6629 if (plyext != ' ' && plyext != '\t') {
\r
6633 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6634 if( cps->scoreIsAbsolute &&
\r
6635 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6637 curscore = -curscore;
\r
6641 programStats.depth = plylev;
\r
6642 programStats.nodes = nodes;
\r
6643 programStats.time = time;
\r
6644 programStats.score = curscore;
\r
6645 programStats.got_only_move = 0;
\r
6647 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6650 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6651 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6652 if(WhiteOnMove(forwardMostMove))
\r
6653 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6654 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6657 /* Buffer overflow protection */
\r
6658 if (buf1[0] != NULLCHAR) {
\r
6659 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6660 && appData.debugMode) {
\r
6662 "PV is too long; using the first %d bytes.\n",
\r
6663 sizeof(programStats.movelist) - 1);
\r
6666 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6668 sprintf(programStats.movelist, " no PV\n");
\r
6671 if (programStats.seen_stat) {
\r
6672 programStats.ok_to_send = 1;
\r
6675 if (strchr(programStats.movelist, '(') != NULL) {
\r
6676 programStats.line_is_book = 1;
\r
6677 programStats.nr_moves = 0;
\r
6678 programStats.moves_left = 0;
\r
6680 programStats.line_is_book = 0;
\r
6683 SendProgramStatsToFrontend( cps, &programStats );
\r
6686 [AS] Protect the thinkOutput buffer from overflow... this
\r
6687 is only useful if buf1 hasn't overflowed first!
\r
6689 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6691 (gameMode == TwoMachinesPlay ?
\r
6692 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6693 ((double) curscore) / 100.0,
\r
6694 prefixHint ? lastHint : "",
\r
6695 prefixHint ? " " : "" );
\r
6697 if( buf1[0] != NULLCHAR ) {
\r
6698 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6700 if( strlen(buf1) > max_len ) {
\r
6701 if( appData.debugMode) {
\r
6702 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6704 buf1[max_len+1] = '\0';
\r
6707 strcat( thinkOutput, buf1 );
\r
6710 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6711 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6712 DisplayMove(currentMove - 1);
\r
6713 DisplayAnalysis();
\r
6717 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6718 /* crafty (9.25+) says "(only move) <move>"
\r
6719 * if there is only 1 legal move
\r
6721 sscanf(p, "(only move) %s", buf1);
\r
6722 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6723 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6724 programStats.depth = 1;
\r
6725 programStats.nr_moves = 1;
\r
6726 programStats.moves_left = 1;
\r
6727 programStats.nodes = 1;
\r
6728 programStats.time = 1;
\r
6729 programStats.got_only_move = 1;
\r
6731 /* Not really, but we also use this member to
\r
6732 mean "line isn't going to change" (Crafty
\r
6733 isn't searching, so stats won't change) */
\r
6734 programStats.line_is_book = 1;
\r
6736 SendProgramStatsToFrontend( cps, &programStats );
\r
6738 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6739 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6740 DisplayMove(currentMove - 1);
\r
6741 DisplayAnalysis();
\r
6744 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6745 &time, &nodes, &plylev, &mvleft,
\r
6746 &mvtot, mvname) >= 5) {
\r
6747 /* The stat01: line is from Crafty (9.29+) in response
\r
6748 to the "." command */
\r
6749 programStats.seen_stat = 1;
\r
6750 cps->maybeThinking = TRUE;
\r
6752 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6755 programStats.depth = plylev;
\r
6756 programStats.time = time;
\r
6757 programStats.nodes = nodes;
\r
6758 programStats.moves_left = mvleft;
\r
6759 programStats.nr_moves = mvtot;
\r
6760 strcpy(programStats.move_name, mvname);
\r
6761 programStats.ok_to_send = 1;
\r
6762 programStats.movelist[0] = '\0';
\r
6764 SendProgramStatsToFrontend( cps, &programStats );
\r
6766 DisplayAnalysis();
\r
6769 } else if (strncmp(message,"++",2) == 0) {
\r
6770 /* Crafty 9.29+ outputs this */
\r
6771 programStats.got_fail = 2;
\r
6774 } else if (strncmp(message,"--",2) == 0) {
\r
6775 /* Crafty 9.29+ outputs this */
\r
6776 programStats.got_fail = 1;
\r
6779 } else if (thinkOutput[0] != NULLCHAR &&
\r
6780 strncmp(message, " ", 4) == 0) {
\r
6781 unsigned message_len;
\r
6784 while (*p && *p == ' ') p++;
\r
6786 message_len = strlen( p );
\r
6788 /* [AS] Avoid buffer overflow */
\r
6789 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6790 strcat(thinkOutput, " ");
\r
6791 strcat(thinkOutput, p);
\r
6794 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6795 strcat(programStats.movelist, " ");
\r
6796 strcat(programStats.movelist, p);
\r
6799 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6800 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6801 DisplayMove(currentMove - 1);
\r
6802 DisplayAnalysis();
\r
6808 buf1[0] = NULLCHAR;
\r
6810 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6811 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6813 ChessProgramStats cpstats;
\r
6815 if (plyext != ' ' && plyext != '\t') {
\r
6819 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6820 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6821 curscore = -curscore;
\r
6824 cpstats.depth = plylev;
\r
6825 cpstats.nodes = nodes;
\r
6826 cpstats.time = time;
\r
6827 cpstats.score = curscore;
\r
6828 cpstats.got_only_move = 0;
\r
6829 cpstats.movelist[0] = '\0';
\r
6831 if (buf1[0] != NULLCHAR) {
\r
6832 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6835 cpstats.ok_to_send = 0;
\r
6836 cpstats.line_is_book = 0;
\r
6837 cpstats.nr_moves = 0;
\r
6838 cpstats.moves_left = 0;
\r
6840 SendProgramStatsToFrontend( cps, &cpstats );
\r
6847 /* Parse a game score from the character string "game", and
\r
6848 record it as the history of the current game. The game
\r
6849 score is NOT assumed to start from the standard position.
\r
6850 The display is not updated in any way.
\r
6853 ParseGameHistory(game)
\r
6856 ChessMove moveType;
\r
6857 int fromX, fromY, toX, toY, boardIndex;
\r
6860 char buf[MSG_SIZ];
\r
6862 if (appData.debugMode)
\r
6863 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6865 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6866 gameInfo.site = StrSave(appData.icsHost);
\r
6867 gameInfo.date = PGNDate();
\r
6868 gameInfo.round = StrSave("-");
\r
6870 /* Parse out names of players */
\r
6871 while (*game == ' ') game++;
\r
6873 while (*game != ' ') *p++ = *game++;
\r
6875 gameInfo.white = StrSave(buf);
\r
6876 while (*game == ' ') game++;
\r
6878 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6880 gameInfo.black = StrSave(buf);
\r
6883 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6886 yyboardindex = boardIndex;
\r
6887 moveType = (ChessMove) yylex();
\r
6888 switch (moveType) {
\r
6889 case IllegalMove: /* maybe suicide chess, etc. */
\r
6890 if (appData.debugMode) {
\r
6891 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6892 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6893 setbuf(debugFP, NULL);
\r
6895 case WhitePromotionChancellor:
\r
6896 case BlackPromotionChancellor:
\r
6897 case WhitePromotionArchbishop:
\r
6898 case BlackPromotionArchbishop:
\r
6899 case WhitePromotionQueen:
\r
6900 case BlackPromotionQueen:
\r
6901 case WhitePromotionRook:
\r
6902 case BlackPromotionRook:
\r
6903 case WhitePromotionBishop:
\r
6904 case BlackPromotionBishop:
\r
6905 case WhitePromotionKnight:
\r
6906 case BlackPromotionKnight:
\r
6907 case WhitePromotionKing:
\r
6908 case BlackPromotionKing:
\r
6910 case WhiteCapturesEnPassant:
\r
6911 case BlackCapturesEnPassant:
\r
6912 case WhiteKingSideCastle:
\r
6913 case WhiteQueenSideCastle:
\r
6914 case BlackKingSideCastle:
\r
6915 case BlackQueenSideCastle:
\r
6916 case WhiteKingSideCastleWild:
\r
6917 case WhiteQueenSideCastleWild:
\r
6918 case BlackKingSideCastleWild:
\r
6919 case BlackQueenSideCastleWild:
\r
6921 case WhiteHSideCastleFR:
\r
6922 case WhiteASideCastleFR:
\r
6923 case BlackHSideCastleFR:
\r
6924 case BlackASideCastleFR:
\r
6926 fromX = currentMoveString[0] - AAA;
\r
6927 fromY = currentMoveString[1] - ONE;
\r
6928 toX = currentMoveString[2] - AAA;
\r
6929 toY = currentMoveString[3] - ONE;
\r
6930 promoChar = currentMoveString[4];
\r
6934 fromX = moveType == WhiteDrop ?
\r
6935 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6936 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6937 fromY = DROP_RANK;
\r
6938 toX = currentMoveString[2] - AAA;
\r
6939 toY = currentMoveString[3] - ONE;
\r
6940 promoChar = NULLCHAR;
\r
6942 case AmbiguousMove:
\r
6944 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6945 if (appData.debugMode) {
\r
6946 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6947 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6948 setbuf(debugFP, NULL);
\r
6950 DisplayError(buf, 0);
\r
6952 case ImpossibleMove:
\r
6954 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6955 if (appData.debugMode) {
\r
6956 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6957 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6958 setbuf(debugFP, NULL);
\r
6960 DisplayError(buf, 0);
\r
6962 case (ChessMove) 0: /* end of file */
\r
6963 if (boardIndex < backwardMostMove) {
\r
6964 /* Oops, gap. How did that happen? */
\r
6965 DisplayError(_("Gap in move list"), 0);
\r
6968 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6969 if (boardIndex > forwardMostMove) {
\r
6970 forwardMostMove = boardIndex;
\r
6974 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6975 strcat(parseList[boardIndex-1], " ");
\r
6976 strcat(parseList[boardIndex-1], yy_text);
\r
6988 case GameUnfinished:
\r
6989 if (gameMode == IcsExamining) {
\r
6990 if (boardIndex < backwardMostMove) {
\r
6991 /* Oops, gap. How did that happen? */
\r
6994 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6997 gameInfo.result = moveType;
\r
6998 p = strchr(yy_text, '{');
\r
6999 if (p == NULL) p = strchr(yy_text, '(');
\r
7002 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7004 q = strchr(p, *p == '{' ? '}' : ')');
\r
7005 if (q != NULL) *q = NULLCHAR;
\r
7008 gameInfo.resultDetails = StrSave(p);
\r
7011 if (boardIndex >= forwardMostMove &&
\r
7012 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7013 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7016 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7017 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7018 parseList[boardIndex]);
\r
7019 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7020 /* currentMoveString is set as a side-effect of yylex */
\r
7021 strcpy(moveList[boardIndex], currentMoveString);
\r
7022 strcat(moveList[boardIndex], "\n");
\r
7024 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7025 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7026 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7028 case MT_STALEMATE:
\r
7032 if(gameInfo.variant != VariantShogi)
\r
7033 strcat(parseList[boardIndex - 1], "+");
\r
7035 case MT_CHECKMATE:
\r
7036 strcat(parseList[boardIndex - 1], "#");
\r
7043 /* Apply a move to the given board */
\r
7045 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7046 int fromX, fromY, toX, toY;
\r
7050 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7052 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7053 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7054 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7057 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7058 oldEP = epStatus[p-1];
\r
7059 epStatus[p] = EP_NONE;
\r
7061 if( board[toY][toX] != EmptySquare )
\r
7062 epStatus[p] = EP_CAPTURE;
\r
7064 if( board[fromY][fromX] == WhitePawn ) {
\r
7065 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7066 epStatus[p] = EP_PAWN_MOVE;
\r
7067 if( toY-fromY==2) {
\r
7068 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7069 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7070 epStatus[p] = toX | berolina;
\r
7071 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7072 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7073 epStatus[p] = toX;
\r
7076 if( board[fromY][fromX] == BlackPawn ) {
\r
7077 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7078 epStatus[p] = EP_PAWN_MOVE;
\r
7079 if( toY-fromY== -2) {
\r
7080 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7081 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7082 epStatus[p] = toX | berolina;
\r
7083 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7084 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7085 epStatus[p] = toX;
\r
7089 for(i=0; i<nrCastlingRights; i++) {
\r
7090 castlingRights[p][i] = castlingRights[p-1][i];
\r
7091 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7092 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7093 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7098 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7099 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7100 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7102 if (fromX == toX && fromY == toY) return;
\r
7104 if (fromY == DROP_RANK) {
\r
7105 /* must be first */
\r
7106 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7108 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7109 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7110 if(gameInfo.variant == VariantKnightmate)
\r
7111 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7113 /* Code added by Tord: */
\r
7114 /* FRC castling assumed when king captures friendly rook. */
\r
7115 if (board[fromY][fromX] == WhiteKing &&
\r
7116 board[toY][toX] == WhiteRook) {
\r
7117 board[fromY][fromX] = EmptySquare;
\r
7118 board[toY][toX] = EmptySquare;
\r
7120 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7122 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7124 } else if (board[fromY][fromX] == BlackKing &&
\r
7125 board[toY][toX] == BlackRook) {
\r
7126 board[fromY][fromX] = EmptySquare;
\r
7127 board[toY][toX] = EmptySquare;
\r
7129 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7131 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7133 /* End of code added by Tord */
\r
7135 } else if (board[fromY][fromX] == king
\r
7136 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7137 && toY == fromY && toX > fromX+1) {
\r
7138 board[fromY][fromX] = EmptySquare;
\r
7139 board[toY][toX] = king;
\r
7140 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7141 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7142 } else if (board[fromY][fromX] == king
\r
7143 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7144 && toY == fromY && toX < fromX-1) {
\r
7145 board[fromY][fromX] = EmptySquare;
\r
7146 board[toY][toX] = king;
\r
7147 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7148 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7149 } else if (board[fromY][fromX] == WhitePawn
\r
7150 && toY == BOARD_HEIGHT-1
\r
7151 && gameInfo.variant != VariantXiangqi
\r
7153 /* white pawn promotion */
\r
7154 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7155 if (board[toY][toX] == EmptySquare) {
\r
7156 board[toY][toX] = WhiteQueen;
\r
7158 if(gameInfo.variant==VariantBughouse ||
\r
7159 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7160 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7161 board[fromY][fromX] = EmptySquare;
\r
7162 } else if ((fromY == BOARD_HEIGHT-4)
\r
7164 && gameInfo.variant != VariantXiangqi
\r
7165 && gameInfo.variant != VariantBerolina
\r
7166 && (board[fromY][fromX] == WhitePawn)
\r
7167 && (board[toY][toX] == EmptySquare)) {
\r
7168 board[fromY][fromX] = EmptySquare;
\r
7169 board[toY][toX] = WhitePawn;
\r
7170 captured = board[toY - 1][toX];
\r
7171 board[toY - 1][toX] = EmptySquare;
\r
7172 } else if ((fromY == BOARD_HEIGHT-4)
\r
7174 && gameInfo.variant == VariantBerolina
\r
7175 && (board[fromY][fromX] == WhitePawn)
\r
7176 && (board[toY][toX] == EmptySquare)) {
\r
7177 board[fromY][fromX] = EmptySquare;
\r
7178 board[toY][toX] = WhitePawn;
\r
7179 if(oldEP & EP_BEROLIN_A) {
\r
7180 captured = board[fromY][fromX-1];
\r
7181 board[fromY][fromX-1] = EmptySquare;
\r
7182 }else{ captured = board[fromY][fromX+1];
\r
7183 board[fromY][fromX+1] = EmptySquare;
\r
7185 } else if (board[fromY][fromX] == king
\r
7186 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7187 && toY == fromY && toX > fromX+1) {
\r
7188 board[fromY][fromX] = EmptySquare;
\r
7189 board[toY][toX] = king;
\r
7190 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7191 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7192 } else if (board[fromY][fromX] == king
\r
7193 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7194 && toY == fromY && toX < fromX-1) {
\r
7195 board[fromY][fromX] = EmptySquare;
\r
7196 board[toY][toX] = king;
\r
7197 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7198 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7199 } else if (fromY == 7 && fromX == 3
\r
7200 && board[fromY][fromX] == BlackKing
\r
7201 && toY == 7 && toX == 5) {
\r
7202 board[fromY][fromX] = EmptySquare;
\r
7203 board[toY][toX] = BlackKing;
\r
7204 board[fromY][7] = EmptySquare;
\r
7205 board[toY][4] = BlackRook;
\r
7206 } else if (fromY == 7 && fromX == 3
\r
7207 && board[fromY][fromX] == BlackKing
\r
7208 && toY == 7 && toX == 1) {
\r
7209 board[fromY][fromX] = EmptySquare;
\r
7210 board[toY][toX] = BlackKing;
\r
7211 board[fromY][0] = EmptySquare;
\r
7212 board[toY][2] = BlackRook;
\r
7213 } else if (board[fromY][fromX] == BlackPawn
\r
7215 && gameInfo.variant != VariantXiangqi
\r
7217 /* black pawn promotion */
\r
7218 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7219 if (board[0][toX] == EmptySquare) {
\r
7220 board[0][toX] = BlackQueen;
\r
7222 if(gameInfo.variant==VariantBughouse ||
\r
7223 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7224 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7225 board[fromY][fromX] = EmptySquare;
\r
7226 } else if ((fromY == 3)
\r
7228 && gameInfo.variant != VariantXiangqi
\r
7229 && gameInfo.variant != VariantBerolina
\r
7230 && (board[fromY][fromX] == BlackPawn)
\r
7231 && (board[toY][toX] == EmptySquare)) {
\r
7232 board[fromY][fromX] = EmptySquare;
\r
7233 board[toY][toX] = BlackPawn;
\r
7234 captured = board[toY + 1][toX];
\r
7235 board[toY + 1][toX] = EmptySquare;
\r
7236 } else if ((fromY == 3)
\r
7238 && gameInfo.variant == VariantBerolina
\r
7239 && (board[fromY][fromX] == BlackPawn)
\r
7240 && (board[toY][toX] == EmptySquare)) {
\r
7241 board[fromY][fromX] = EmptySquare;
\r
7242 board[toY][toX] = BlackPawn;
\r
7243 if(oldEP & EP_BEROLIN_A) {
\r
7244 captured = board[fromY][fromX-1];
\r
7245 board[fromY][fromX-1] = EmptySquare;
\r
7246 }else{ captured = board[fromY][fromX+1];
\r
7247 board[fromY][fromX+1] = EmptySquare;
\r
7250 board[toY][toX] = board[fromY][fromX];
\r
7251 board[fromY][fromX] = EmptySquare;
\r
7254 /* [HGM] now we promote for Shogi, if needed */
\r
7255 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7256 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7259 if (gameInfo.holdingsWidth != 0) {
\r
7261 /* !!A lot more code needs to be written to support holdings */
\r
7262 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7263 /* penultimate board files, so they are automaticlly stored */
\r
7264 /* in the game history. */
\r
7265 if (fromY == DROP_RANK) {
\r
7266 /* Delete from holdings, by decreasing count */
\r
7267 /* and erasing image if necessary */
\r
7269 if(p < (int) BlackPawn) { /* white drop */
\r
7270 p -= (int)WhitePawn;
\r
7271 if(p >= gameInfo.holdingsSize) p = 0;
\r
7272 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7273 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7274 } else { /* black drop */
\r
7275 p -= (int)BlackPawn;
\r
7276 if(p >= gameInfo.holdingsSize) p = 0;
\r
7277 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7278 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7281 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7282 && gameInfo.variant != VariantBughouse ) {
\r
7283 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7284 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7285 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7286 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7288 p = (int) captured;
\r
7289 if (p >= (int) BlackPawn) {
\r
7290 p -= (int)BlackPawn;
\r
7291 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7292 /* in Shogi restore piece to its original first */
\r
7293 captured = (ChessSquare) (DEMOTED captured);
\r
7296 p = PieceToNumber((ChessSquare)p);
\r
7297 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7298 board[p][BOARD_WIDTH-2]++;
\r
7299 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7301 p -= (int)WhitePawn;
\r
7302 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7303 captured = (ChessSquare) (DEMOTED captured);
\r
7306 p = PieceToNumber((ChessSquare)p);
\r
7307 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7308 board[BOARD_HEIGHT-1-p][1]++;
\r
7309 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7313 } else if (gameInfo.variant == VariantAtomic) {
\r
7314 if (captured != EmptySquare) {
\r
7316 for (y = toY-1; y <= toY+1; y++) {
\r
7317 for (x = toX-1; x <= toX+1; x++) {
\r
7318 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7319 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7320 board[y][x] = EmptySquare;
\r
7324 board[toY][toX] = EmptySquare;
\r
7327 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7328 /* [HGM] Shogi promotions */
\r
7329 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7334 /* Updates forwardMostMove */
\r
7336 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7337 int fromX, fromY, toX, toY;
\r
7340 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7342 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7343 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7344 piece = boards[forwardMostMove][fromY][fromX];
\r
7345 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7346 if(gameInfo.variant == VariantKnightmate)
\r
7347 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7348 if(forwardMostMove == 0) {
\r
7349 if(blackPlaysFirst)
\r
7350 fprintf(serverMoves, "%s;", second.tidy);
\r
7351 fprintf(serverMoves, "%s;", first.tidy);
\r
7352 if(!blackPlaysFirst)
\r
7353 fprintf(serverMoves, "%s;", second.tidy);
\r
7354 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7355 lastLoadFlag = loadFlag;
\r
7356 // print base move
\r
7357 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7358 // print castling suffix
\r
7359 if( toY == fromY && piece == king ) {
\r
7361 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7363 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7366 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7367 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7368 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7370 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7371 // promotion suffix
\r
7372 if(promoChar != NULLCHAR)
\r
7373 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7375 fprintf(serverMoves, "/%d/%d",
\r
7376 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7377 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7378 else timeLeft = blackTimeRemaining/1000;
\r
7379 fprintf(serverMoves, "/%d", timeLeft);
\r
7381 fflush(serverMoves);
\r
7384 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7385 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7390 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7391 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7392 if (commentList[forwardMostMove+1] != NULL) {
\r
7393 free(commentList[forwardMostMove+1]);
\r
7394 commentList[forwardMostMove+1] = NULL;
\r
7396 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7397 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7398 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7399 gameInfo.result = GameUnfinished;
\r
7400 if (gameInfo.resultDetails != NULL) {
\r
7401 free(gameInfo.resultDetails);
\r
7402 gameInfo.resultDetails = NULL;
\r
7404 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7405 moveList[forwardMostMove - 1]);
\r
7406 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7407 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7408 fromY, fromX, toY, toX, promoChar,
\r
7409 parseList[forwardMostMove - 1]);
\r
7410 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7411 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7412 castlingRights[forwardMostMove]) ) {
\r
7414 case MT_STALEMATE:
\r
7418 if(gameInfo.variant != VariantShogi)
\r
7419 strcat(parseList[forwardMostMove - 1], "+");
\r
7421 case MT_CHECKMATE:
\r
7422 strcat(parseList[forwardMostMove - 1], "#");
\r
7425 if (appData.debugMode) {
\r
7426 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7431 /* Updates currentMove if not pausing */
\r
7433 ShowMove(fromX, fromY, toX, toY)
\r
7435 int instant = (gameMode == PlayFromGameFile) ?
\r
7436 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7437 if(appData.noGUI) return;
\r
7438 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7440 if (forwardMostMove == currentMove + 1) {
\r
7441 AnimateMove(boards[forwardMostMove - 1],
\r
7442 fromX, fromY, toX, toY);
\r
7444 if (appData.highlightLastMove) {
\r
7445 SetHighlights(fromX, fromY, toX, toY);
\r
7448 currentMove = forwardMostMove;
\r
7451 if (instant) return;
\r
7453 DisplayMove(currentMove - 1);
\r
7454 DrawPosition(FALSE, boards[currentMove]);
\r
7455 DisplayBothClocks();
\r
7456 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7459 void SendEgtPath(ChessProgramState *cps)
\r
7460 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7461 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7463 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7466 char c, *q = name+1, *r, *s;
\r
7468 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7469 while(*p && *p != ',') *q++ = *p++;
\r
7470 *q++ = ':'; *q = 0;
\r
7471 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7472 strcmp(name, ",nalimov:") == 0 ) {
\r
7473 // take nalimov path from the menu-changeable option first, if it is defined
\r
7474 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7475 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7477 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7478 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7479 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7480 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7481 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7482 c = *r; *r = 0; // temporarily null-terminate path info
\r
7483 *--q = 0; // strip of trailig ':' from name
\r
7484 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7486 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7488 if(*p == ',') p++; // read away comma to position for next format name
\r
7493 InitChessProgram(cps, setup)
\r
7494 ChessProgramState *cps;
\r
7495 int setup; /* [HGM] needed to setup FRC opening position */
\r
7497 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7498 if (appData.noChessProgram) return;
\r
7499 hintRequested = FALSE;
\r
7500 bookRequested = FALSE;
\r
7502 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7503 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7504 if(cps->memSize) { /* [HGM] memory */
\r
7505 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7506 SendToProgram(buf, cps);
\r
7508 SendEgtPath(cps); /* [HGM] EGT */
\r
7509 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7510 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7511 SendToProgram(buf, cps);
\r
7514 SendToProgram(cps->initString, cps);
\r
7515 if (gameInfo.variant != VariantNormal &&
\r
7516 gameInfo.variant != VariantLoadable
\r
7517 /* [HGM] also send variant if board size non-standard */
\r
7518 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7520 char *v = VariantName(gameInfo.variant);
\r
7521 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7522 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7523 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7524 DisplayFatalError(buf, 0, 1);
\r
7528 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7529 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7530 if( gameInfo.variant == VariantXiangqi )
\r
7531 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7532 if( gameInfo.variant == VariantShogi )
\r
7533 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7534 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7535 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7536 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7537 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7538 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7539 if( gameInfo.variant == VariantCourier )
\r
7540 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7541 if( gameInfo.variant == VariantSuper )
\r
7542 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7543 if( gameInfo.variant == VariantGreat )
\r
7544 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7547 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7548 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7549 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7550 if(StrStr(cps->variants, b) == NULL) {
\r
7551 // specific sized variant not known, check if general sizing allowed
\r
7552 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7553 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7554 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7555 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7556 DisplayFatalError(buf, 0, 1);
\r
7559 /* [HGM] here we really should compare with the maximum supported board size */
\r
7562 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7563 sprintf(buf, "variant %s\n", b);
\r
7564 SendToProgram(buf, cps);
\r
7566 currentlyInitializedVariant = gameInfo.variant;
\r
7568 /* [HGM] send opening position in FRC to first engine */
\r
7570 SendToProgram("force\n", cps);
\r
7571 SendBoard(cps, 0);
\r
7572 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7573 setboardSpoiledMachineBlack = 1;
\r
7576 if (cps->sendICS) {
\r
7577 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7578 SendToProgram(buf, cps);
\r
7580 cps->maybeThinking = FALSE;
\r
7581 cps->offeredDraw = 0;
\r
7582 if (!appData.icsActive) {
\r
7583 SendTimeControl(cps, movesPerSession, timeControl,
\r
7584 timeIncrement, appData.searchDepth,
\r
7587 if (appData.showThinking
\r
7588 // [HGM] thinking: four options require thinking output to be sent
\r
7589 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7591 SendToProgram("post\n", cps);
\r
7593 SendToProgram("hard\n", cps);
\r
7594 if (!appData.ponderNextMove) {
\r
7595 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7596 it without being sure what state we are in first. "hard"
\r
7597 is not a toggle, so that one is OK.
\r
7599 SendToProgram("easy\n", cps);
\r
7601 if (cps->usePing) {
\r
7602 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7603 SendToProgram(buf, cps);
\r
7605 cps->initDone = TRUE;
\r
7610 StartChessProgram(cps)
\r
7611 ChessProgramState *cps;
\r
7613 char buf[MSG_SIZ];
\r
7616 if (appData.noChessProgram) return;
\r
7617 cps->initDone = FALSE;
\r
7619 if (strcmp(cps->host, "localhost") == 0) {
\r
7620 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7621 } else if (*appData.remoteShell == NULLCHAR) {
\r
7622 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7624 if (*appData.remoteUser == NULLCHAR) {
\r
7625 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7628 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7629 cps->host, appData.remoteUser, cps->program);
\r
7631 err = StartChildProcess(buf, "", &cps->pr);
\r
7635 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7636 DisplayFatalError(buf, err, 1);
\r
7642 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7643 if (cps->protocolVersion > 1) {
\r
7644 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7645 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7646 cps->comboCnt = 0; // and values of combo boxes
\r
7647 SendToProgram(buf, cps);
\r
7649 SendToProgram("xboard\n", cps);
\r
7655 TwoMachinesEventIfReady P((void))
\r
7657 if (first.lastPing != first.lastPong) {
\r
7658 DisplayMessage("", _("Waiting for first chess program"));
\r
7659 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7662 if (second.lastPing != second.lastPong) {
\r
7663 DisplayMessage("", _("Waiting for second chess program"));
\r
7664 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7668 TwoMachinesEvent();
\r
7672 NextMatchGame P((void))
\r
7674 int index; /* [HGM] autoinc: step lod index during match */
\r
7675 Reset(FALSE, TRUE);
\r
7676 if (*appData.loadGameFile != NULLCHAR) {
\r
7677 index = appData.loadGameIndex;
\r
7678 if(index < 0) { // [HGM] autoinc
\r
7679 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7680 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7682 LoadGameFromFile(appData.loadGameFile,
\r
7684 appData.loadGameFile, FALSE);
\r
7685 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7686 index = appData.loadPositionIndex;
\r
7687 if(index < 0) { // [HGM] autoinc
\r
7688 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7689 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7691 LoadPositionFromFile(appData.loadPositionFile,
\r
7693 appData.loadPositionFile);
\r
7695 TwoMachinesEventIfReady();
\r
7698 void UserAdjudicationEvent( int result )
\r
7700 ChessMove gameResult = GameIsDrawn;
\r
7702 if( result > 0 ) {
\r
7703 gameResult = WhiteWins;
\r
7705 else if( result < 0 ) {
\r
7706 gameResult = BlackWins;
\r
7709 if( gameMode == TwoMachinesPlay ) {
\r
7710 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7716 GameEnds(result, resultDetails, whosays)
\r
7718 char *resultDetails;
\r
7721 GameMode nextGameMode;
\r
7723 char buf[MSG_SIZ];
\r
7725 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7728 if (appData.debugMode) {
\r
7729 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7730 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7733 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7734 /* If we are playing on ICS, the server decides when the
\r
7735 game is over, but the engine can offer to draw, claim
\r
7736 a draw, or resign.
\r
7739 if (appData.zippyPlay && first.initDone) {
\r
7740 if (result == GameIsDrawn) {
\r
7741 /* In case draw still needs to be claimed */
\r
7742 SendToICS(ics_prefix);
\r
7743 SendToICS("draw\n");
\r
7744 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7745 SendToICS(ics_prefix);
\r
7746 SendToICS("resign\n");
\r
7750 endingGame = 0; /* [HGM] crash */
\r
7754 /* If we're loading the game from a file, stop */
\r
7755 if (whosays == GE_FILE) {
\r
7756 (void) StopLoadGameTimer();
\r
7757 gameFileFP = NULL;
\r
7760 /* Cancel draw offers */
\r
7761 first.offeredDraw = second.offeredDraw = 0;
\r
7763 /* If this is an ICS game, only ICS can really say it's done;
\r
7764 if not, anyone can. */
\r
7765 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7766 gameMode == IcsPlayingBlack ||
\r
7767 gameMode == IcsObserving ||
\r
7768 gameMode == IcsExamining);
\r
7770 if (!isIcsGame || whosays == GE_ICS) {
\r
7771 /* OK -- not an ICS game, or ICS said it was done */
\r
7773 if (!isIcsGame && !appData.noChessProgram)
\r
7774 SetUserThinkingEnables();
\r
7776 /* [HGM] if a machine claims the game end we verify this claim */
\r
7777 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7778 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7781 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7782 first.twoMachinesColor[0] :
\r
7783 second.twoMachinesColor[0] ;
\r
7784 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7785 (result == WhiteWins && claimer == 'w' ||
\r
7786 result == BlackWins && claimer == 'b' ) ) {
\r
7787 if (appData.debugMode) {
\r
7788 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7789 result, epStatus[forwardMostMove], forwardMostMove);
\r
7791 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7792 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7793 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7794 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7795 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7796 resultDetails = buf;
\r
7799 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7800 && (forwardMostMove <= backwardMostMove ||
\r
7801 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7802 (claimer=='b')==(forwardMostMove&1))
\r
7804 /* [HGM] verify: draws that were not flagged are false claims */
\r
7805 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7806 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7807 resultDetails = buf;
\r
7809 /* (Claiming a loss is accepted no questions asked!) */
\r
7811 /* [HGM] bare: don't allow bare King to win */
\r
7812 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7813 && result != GameIsDrawn)
\r
7814 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7815 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7816 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7817 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7819 if (appData.debugMode) {
\r
7820 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7821 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7824 result = GameIsDrawn;
\r
7825 sprintf(buf, "%s but bare king", resultDetails);
\r
7826 resultDetails = buf;
\r
7832 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7833 if(result==WhiteWins) c = '+';
\r
7834 if(result==BlackWins) c = '-';
\r
7835 if(resultDetails != NULL)
\r
7836 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7838 if (resultDetails != NULL) {
\r
7839 gameInfo.result = result;
\r
7840 gameInfo.resultDetails = StrSave(resultDetails);
\r
7842 /* display last move only if game was not loaded from file */
\r
7843 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7844 DisplayMove(currentMove - 1);
\r
7846 if (forwardMostMove != 0) {
\r
7847 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7848 if (*appData.saveGameFile != NULLCHAR) {
\r
7849 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7850 } else if (appData.autoSaveGames) {
\r
7853 if (*appData.savePositionFile != NULLCHAR) {
\r
7854 SavePositionToFile(appData.savePositionFile);
\r
7859 /* Tell program how game ended in case it is learning */
\r
7860 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7861 /* engine died and we got here through time loss. In that */
\r
7862 /* case we will get a fatal error writing the pipe, which */
\r
7863 /* would otherwise lose us the PGN. */
\r
7864 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7865 /* output during GameEnds should never be fatal anymore */
\r
7866 if (gameMode == MachinePlaysWhite ||
\r
7867 gameMode == MachinePlaysBlack ||
\r
7868 gameMode == TwoMachinesPlay ||
\r
7869 gameMode == IcsPlayingWhite ||
\r
7870 gameMode == IcsPlayingBlack ||
\r
7871 gameMode == BeginningOfGame) {
\r
7872 char buf[MSG_SIZ];
\r
7873 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7875 if (first.pr != NoProc) {
\r
7876 SendToProgram(buf, &first);
\r
7878 if (second.pr != NoProc &&
\r
7879 gameMode == TwoMachinesPlay) {
\r
7880 SendToProgram(buf, &second);
\r
7885 if (appData.icsActive) {
\r
7886 if (appData.quietPlay &&
\r
7887 (gameMode == IcsPlayingWhite ||
\r
7888 gameMode == IcsPlayingBlack)) {
\r
7889 SendToICS(ics_prefix);
\r
7890 SendToICS("set shout 1\n");
\r
7892 nextGameMode = IcsIdle;
\r
7893 ics_user_moved = FALSE;
\r
7894 /* clean up premove. It's ugly when the game has ended and the
\r
7895 * premove highlights are still on the board.
\r
7898 gotPremove = FALSE;
\r
7899 ClearPremoveHighlights();
\r
7900 DrawPosition(FALSE, boards[currentMove]);
\r
7902 if (whosays == GE_ICS) {
\r
7905 if (gameMode == IcsPlayingWhite)
\r
7906 PlayIcsWinSound();
\r
7907 else if(gameMode == IcsPlayingBlack)
\r
7908 PlayIcsLossSound();
\r
7911 if (gameMode == IcsPlayingBlack)
\r
7912 PlayIcsWinSound();
\r
7913 else if(gameMode == IcsPlayingWhite)
\r
7914 PlayIcsLossSound();
\r
7917 PlayIcsDrawSound();
\r
7920 PlayIcsUnfinishedSound();
\r
7923 } else if (gameMode == EditGame ||
\r
7924 gameMode == PlayFromGameFile ||
\r
7925 gameMode == AnalyzeMode ||
\r
7926 gameMode == AnalyzeFile) {
\r
7927 nextGameMode = gameMode;
\r
7929 nextGameMode = EndOfGame;
\r
7934 nextGameMode = gameMode;
\r
7937 if (appData.noChessProgram) {
\r
7938 gameMode = nextGameMode;
\r
7940 endingGame = 0; /* [HGM] crash */
\r
7944 if (first.reuse) {
\r
7945 /* Put first chess program into idle state */
\r
7946 if (first.pr != NoProc &&
\r
7947 (gameMode == MachinePlaysWhite ||
\r
7948 gameMode == MachinePlaysBlack ||
\r
7949 gameMode == TwoMachinesPlay ||
\r
7950 gameMode == IcsPlayingWhite ||
\r
7951 gameMode == IcsPlayingBlack ||
\r
7952 gameMode == BeginningOfGame)) {
\r
7953 SendToProgram("force\n", &first);
\r
7954 if (first.usePing) {
\r
7955 char buf[MSG_SIZ];
\r
7956 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7957 SendToProgram(buf, &first);
\r
7960 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7961 /* Kill off first chess program */
\r
7962 if (first.isr != NULL)
\r
7963 RemoveInputSource(first.isr);
\r
7966 if (first.pr != NoProc) {
\r
7967 ExitAnalyzeMode();
\r
7968 DoSleep( appData.delayBeforeQuit );
\r
7969 SendToProgram("quit\n", &first);
\r
7970 DoSleep( appData.delayAfterQuit );
\r
7971 DestroyChildProcess(first.pr, first.useSigterm);
\r
7973 first.pr = NoProc;
\r
7975 if (second.reuse) {
\r
7976 /* Put second chess program into idle state */
\r
7977 if (second.pr != NoProc &&
\r
7978 gameMode == TwoMachinesPlay) {
\r
7979 SendToProgram("force\n", &second);
\r
7980 if (second.usePing) {
\r
7981 char buf[MSG_SIZ];
\r
7982 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7983 SendToProgram(buf, &second);
\r
7986 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7987 /* Kill off second chess program */
\r
7988 if (second.isr != NULL)
\r
7989 RemoveInputSource(second.isr);
\r
7990 second.isr = NULL;
\r
7992 if (second.pr != NoProc) {
\r
7993 DoSleep( appData.delayBeforeQuit );
\r
7994 SendToProgram("quit\n", &second);
\r
7995 DoSleep( appData.delayAfterQuit );
\r
7996 DestroyChildProcess(second.pr, second.useSigterm);
\r
7998 second.pr = NoProc;
\r
8001 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8004 if (first.twoMachinesColor[0] == 'w') {
\r
8005 first.matchWins++;
\r
8007 second.matchWins++;
\r
8011 if (first.twoMachinesColor[0] == 'b') {
\r
8012 first.matchWins++;
\r
8014 second.matchWins++;
\r
8020 if (matchGame < appData.matchGames) {
\r
8022 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8023 tmp = first.twoMachinesColor;
\r
8024 first.twoMachinesColor = second.twoMachinesColor;
\r
8025 second.twoMachinesColor = tmp;
\r
8027 gameMode = nextGameMode;
\r
8029 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8030 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8031 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8032 endingGame = 0; /* [HGM] crash */
\r
8035 char buf[MSG_SIZ];
\r
8036 gameMode = nextGameMode;
\r
8037 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8038 first.tidy, second.tidy,
\r
8039 first.matchWins, second.matchWins,
\r
8040 appData.matchGames - (first.matchWins + second.matchWins));
\r
8041 DisplayFatalError(buf, 0, 0);
\r
8044 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8045 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8046 ExitAnalyzeMode();
\r
8047 gameMode = nextGameMode;
\r
8049 endingGame = 0; /* [HGM] crash */
\r
8052 /* Assumes program was just initialized (initString sent).
\r
8053 Leaves program in force mode. */
\r
8055 FeedMovesToProgram(cps, upto)
\r
8056 ChessProgramState *cps;
\r
8061 if (appData.debugMode)
\r
8062 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8063 startedFromSetupPosition ? "position and " : "",
\r
8064 backwardMostMove, upto, cps->which);
\r
8065 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8066 // [HGM] variantswitch: make engine aware of new variant
\r
8067 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8068 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8069 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8070 SendToProgram(buf, cps);
\r
8071 currentlyInitializedVariant = gameInfo.variant;
\r
8073 SendToProgram("force\n", cps);
\r
8074 if (startedFromSetupPosition) {
\r
8075 SendBoard(cps, backwardMostMove);
\r
8076 if (appData.debugMode) {
\r
8077 fprintf(debugFP, "feedMoves\n");
\r
8080 for (i = backwardMostMove; i < upto; i++) {
\r
8081 SendMoveToProgram(i, cps);
\r
8087 ResurrectChessProgram()
\r
8089 /* The chess program may have exited.
\r
8090 If so, restart it and feed it all the moves made so far. */
\r
8092 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8094 StartChessProgram(&first);
\r
8095 InitChessProgram(&first, FALSE);
\r
8096 FeedMovesToProgram(&first, currentMove);
\r
8098 if (!first.sendTime) {
\r
8099 /* can't tell gnuchess what its clock should read,
\r
8100 so we bow to its notion. */
\r
8102 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8103 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8106 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8107 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8108 SendToProgram("analyze\n", &first);
\r
8109 first.analyzing = TRUE;
\r
8114 * Button procedures
\r
8117 Reset(redraw, init)
\r
8122 if (appData.debugMode) {
\r
8123 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8124 redraw, init, gameMode);
\r
8126 pausing = pauseExamInvalid = FALSE;
\r
8127 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8129 whiteFlag = blackFlag = FALSE;
\r
8130 userOfferedDraw = FALSE;
\r
8131 hintRequested = bookRequested = FALSE;
\r
8132 first.maybeThinking = FALSE;
\r
8133 second.maybeThinking = FALSE;
\r
8134 first.bookSuspend = FALSE; // [HGM] book
\r
8135 second.bookSuspend = FALSE;
\r
8136 thinkOutput[0] = NULLCHAR;
\r
8137 lastHint[0] = NULLCHAR;
\r
8138 ClearGameInfo(&gameInfo);
\r
8139 gameInfo.variant = StringToVariant(appData.variant);
\r
8140 ics_user_moved = ics_clock_paused = FALSE;
\r
8141 ics_getting_history = H_FALSE;
\r
8143 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8144 ClearProgramStats();
\r
8145 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8148 ClearHighlights();
\r
8149 flipView = appData.flipView;
\r
8150 ClearPremoveHighlights();
\r
8151 gotPremove = FALSE;
\r
8152 alarmSounded = FALSE;
\r
8154 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8155 if(appData.serverMovesName != NULL) {
\r
8156 /* [HGM] prepare to make moves file for broadcasting */
\r
8157 clock_t t = clock();
\r
8158 if(serverMoves != NULL) fclose(serverMoves);
\r
8159 serverMoves = fopen(appData.serverMovesName, "r");
\r
8160 if(serverMoves != NULL) {
\r
8161 fclose(serverMoves);
\r
8162 /* delay 15 sec before overwriting, so all clients can see end */
\r
8163 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8165 serverMoves = fopen(appData.serverMovesName, "w");
\r
8168 ExitAnalyzeMode();
\r
8169 gameMode = BeginningOfGame;
\r
8171 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8172 InitPosition(redraw);
\r
8173 for (i = 0; i < MAX_MOVES; i++) {
\r
8174 if (commentList[i] != NULL) {
\r
8175 free(commentList[i]);
\r
8176 commentList[i] = NULL;
\r
8180 timeRemaining[0][0] = whiteTimeRemaining;
\r
8181 timeRemaining[1][0] = blackTimeRemaining;
\r
8182 if (first.pr == NULL) {
\r
8183 StartChessProgram(&first);
\r
8186 InitChessProgram(&first, startedFromSetupPosition);
\r
8189 DisplayMessage("", "");
\r
8190 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8194 AutoPlayGameLoop()
\r
8197 if (!AutoPlayOneMove())
\r
8199 if (matchMode || appData.timeDelay == 0)
\r
8201 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8203 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8212 int fromX, fromY, toX, toY;
\r
8214 if (appData.debugMode) {
\r
8215 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8218 if (gameMode != PlayFromGameFile)
\r
8221 if (currentMove >= forwardMostMove) {
\r
8222 gameMode = EditGame;
\r
8225 /* [AS] Clear current move marker at the end of a game */
\r
8226 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8231 toX = moveList[currentMove][2] - AAA;
\r
8232 toY = moveList[currentMove][3] - ONE;
\r
8234 if (moveList[currentMove][1] == '@') {
\r
8235 if (appData.highlightLastMove) {
\r
8236 SetHighlights(-1, -1, toX, toY);
\r
8239 fromX = moveList[currentMove][0] - AAA;
\r
8240 fromY = moveList[currentMove][1] - ONE;
\r
8242 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8244 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8246 if (appData.highlightLastMove) {
\r
8247 SetHighlights(fromX, fromY, toX, toY);
\r
8250 DisplayMove(currentMove);
\r
8251 SendMoveToProgram(currentMove++, &first);
\r
8252 DisplayBothClocks();
\r
8253 DrawPosition(FALSE, boards[currentMove]);
\r
8254 // [HGM] PV info: always display, routine tests if empty
\r
8255 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8261 LoadGameOneMove(readAhead)
\r
8262 ChessMove readAhead;
\r
8264 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8265 char promoChar = NULLCHAR;
\r
8266 ChessMove moveType;
\r
8267 char move[MSG_SIZ];
\r
8270 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8271 gameMode != AnalyzeMode && gameMode != Training) {
\r
8272 gameFileFP = NULL;
\r
8276 yyboardindex = forwardMostMove;
\r
8277 if (readAhead != (ChessMove)0) {
\r
8278 moveType = readAhead;
\r
8280 if (gameFileFP == NULL)
\r
8282 moveType = (ChessMove) yylex();
\r
8286 switch (moveType) {
\r
8288 if (appData.debugMode)
\r
8289 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8291 if (*p == '{' || *p == '[' || *p == '(') {
\r
8292 p[strlen(p) - 1] = NULLCHAR;
\r
8296 /* append the comment but don't display it */
\r
8297 while (*p == '\n') p++;
\r
8298 AppendComment(currentMove, p);
\r
8301 case WhiteCapturesEnPassant:
\r
8302 case BlackCapturesEnPassant:
\r
8303 case WhitePromotionChancellor:
\r
8304 case BlackPromotionChancellor:
\r
8305 case WhitePromotionArchbishop:
\r
8306 case BlackPromotionArchbishop:
\r
8307 case WhitePromotionCentaur:
\r
8308 case BlackPromotionCentaur:
\r
8309 case WhitePromotionQueen:
\r
8310 case BlackPromotionQueen:
\r
8311 case WhitePromotionRook:
\r
8312 case BlackPromotionRook:
\r
8313 case WhitePromotionBishop:
\r
8314 case BlackPromotionBishop:
\r
8315 case WhitePromotionKnight:
\r
8316 case BlackPromotionKnight:
\r
8317 case WhitePromotionKing:
\r
8318 case BlackPromotionKing:
\r
8320 case WhiteKingSideCastle:
\r
8321 case WhiteQueenSideCastle:
\r
8322 case BlackKingSideCastle:
\r
8323 case BlackQueenSideCastle:
\r
8324 case WhiteKingSideCastleWild:
\r
8325 case WhiteQueenSideCastleWild:
\r
8326 case BlackKingSideCastleWild:
\r
8327 case BlackQueenSideCastleWild:
\r
8329 case WhiteHSideCastleFR:
\r
8330 case WhiteASideCastleFR:
\r
8331 case BlackHSideCastleFR:
\r
8332 case BlackASideCastleFR:
\r
8334 if (appData.debugMode)
\r
8335 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8336 fromX = currentMoveString[0] - AAA;
\r
8337 fromY = currentMoveString[1] - ONE;
\r
8338 toX = currentMoveString[2] - AAA;
\r
8339 toY = currentMoveString[3] - ONE;
\r
8340 promoChar = currentMoveString[4];
\r
8345 if (appData.debugMode)
\r
8346 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8347 fromX = moveType == WhiteDrop ?
\r
8348 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8349 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8350 fromY = DROP_RANK;
\r
8351 toX = currentMoveString[2] - AAA;
\r
8352 toY = currentMoveString[3] - ONE;
\r
8358 case GameUnfinished:
\r
8359 if (appData.debugMode)
\r
8360 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8361 p = strchr(yy_text, '{');
\r
8362 if (p == NULL) p = strchr(yy_text, '(');
\r
8365 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8367 q = strchr(p, *p == '{' ? '}' : ')');
\r
8368 if (q != NULL) *q = NULLCHAR;
\r
8371 GameEnds(moveType, p, GE_FILE);
\r
8373 if (cmailMsgLoaded) {
\r
8374 ClearHighlights();
\r
8375 flipView = WhiteOnMove(currentMove);
\r
8376 if (moveType == GameUnfinished) flipView = !flipView;
\r
8377 if (appData.debugMode)
\r
8378 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8382 case (ChessMove) 0: /* end of file */
\r
8383 if (appData.debugMode)
\r
8384 fprintf(debugFP, "Parser hit end of file\n");
\r
8385 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8386 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8390 case MT_CHECKMATE:
\r
8391 if (WhiteOnMove(currentMove)) {
\r
8392 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8394 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8397 case MT_STALEMATE:
\r
8398 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8404 case MoveNumberOne:
\r
8405 if (lastLoadGameStart == GNUChessGame) {
\r
8406 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8407 if (appData.debugMode)
\r
8408 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8409 yy_text, (int) moveType);
\r
8410 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8412 /* else fall thru */
\r
8415 case GNUChessGame:
\r
8417 /* Reached start of next game in file */
\r
8418 if (appData.debugMode)
\r
8419 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8420 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8421 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8425 case MT_CHECKMATE:
\r
8426 if (WhiteOnMove(currentMove)) {
\r
8427 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8429 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8432 case MT_STALEMATE:
\r
8433 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8439 case PositionDiagram: /* should not happen; ignore */
\r
8440 case ElapsedTime: /* ignore */
\r
8441 case NAG: /* ignore */
\r
8442 if (appData.debugMode)
\r
8443 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8444 yy_text, (int) moveType);
\r
8445 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8448 if (appData.testLegality) {
\r
8449 if (appData.debugMode)
\r
8450 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8451 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8452 (forwardMostMove / 2) + 1,
\r
8453 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8454 DisplayError(move, 0);
\r
8457 if (appData.debugMode)
\r
8458 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8459 yy_text, currentMoveString);
\r
8460 fromX = currentMoveString[0] - AAA;
\r
8461 fromY = currentMoveString[1] - ONE;
\r
8462 toX = currentMoveString[2] - AAA;
\r
8463 toY = currentMoveString[3] - ONE;
\r
8464 promoChar = currentMoveString[4];
\r
8468 case AmbiguousMove:
\r
8469 if (appData.debugMode)
\r
8470 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8471 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8472 (forwardMostMove / 2) + 1,
\r
8473 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8474 DisplayError(move, 0);
\r
8479 case ImpossibleMove:
\r
8480 if (appData.debugMode)
\r
8481 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8482 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8483 (forwardMostMove / 2) + 1,
\r
8484 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8485 DisplayError(move, 0);
\r
8491 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8492 DrawPosition(FALSE, boards[currentMove]);
\r
8493 DisplayBothClocks();
\r
8494 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8495 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8497 (void) StopLoadGameTimer();
\r
8498 gameFileFP = NULL;
\r
8499 cmailOldMove = forwardMostMove;
\r
8502 /* currentMoveString is set as a side-effect of yylex */
\r
8503 strcat(currentMoveString, "\n");
\r
8504 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8506 thinkOutput[0] = NULLCHAR;
\r
8507 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8508 currentMove = forwardMostMove;
\r
8513 /* Load the nth game from the given file */
\r
8515 LoadGameFromFile(filename, n, title, useList)
\r
8519 /*Boolean*/ int useList;
\r
8522 char buf[MSG_SIZ];
\r
8524 if (strcmp(filename, "-") == 0) {
\r
8528 f = fopen(filename, "rb");
\r
8530 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8531 DisplayError(buf, errno);
\r
8535 if (fseek(f, 0, 0) == -1) {
\r
8536 /* f is not seekable; probably a pipe */
\r
8539 if (useList && n == 0) {
\r
8540 int error = GameListBuild(f);
\r
8542 DisplayError(_("Cannot build game list"), error);
\r
8543 } else if (!ListEmpty(&gameList) &&
\r
8544 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8545 GameListPopUp(f, title);
\r
8548 GameListDestroy();
\r
8551 if (n == 0) n = 1;
\r
8552 return LoadGame(f, n, title, FALSE);
\r
8557 MakeRegisteredMove()
\r
8559 int fromX, fromY, toX, toY;
\r
8561 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8562 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8565 if (appData.debugMode)
\r
8566 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8567 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8569 thinkOutput[0] = NULLCHAR;
\r
8570 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8571 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8572 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8573 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8574 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8575 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8576 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8577 ShowMove(fromX, fromY, toX, toY);
\r
8579 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8580 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8585 case MT_CHECKMATE:
\r
8586 if (WhiteOnMove(currentMove)) {
\r
8587 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8589 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8593 case MT_STALEMATE:
\r
8594 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8600 case CMAIL_RESIGN:
\r
8601 if (WhiteOnMove(currentMove)) {
\r
8602 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8604 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8608 case CMAIL_ACCEPT:
\r
8609 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8620 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8622 CmailLoadGame(f, gameNumber, title, useList)
\r
8630 if (gameNumber > nCmailGames) {
\r
8631 DisplayError(_("No more games in this message"), 0);
\r
8634 if (f == lastLoadGameFP) {
\r
8635 int offset = gameNumber - lastLoadGameNumber;
\r
8636 if (offset == 0) {
\r
8637 cmailMsg[0] = NULLCHAR;
\r
8638 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8639 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8640 nCmailMovesRegistered--;
\r
8642 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8643 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8644 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8647 if (! RegisterMove()) return FALSE;
\r
8651 retVal = LoadGame(f, gameNumber, title, useList);
\r
8653 /* Make move registered during previous look at this game, if any */
\r
8654 MakeRegisteredMove();
\r
8656 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8657 commentList[currentMove]
\r
8658 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8659 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8665 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8667 ReloadGame(offset)
\r
8670 int gameNumber = lastLoadGameNumber + offset;
\r
8671 if (lastLoadGameFP == NULL) {
\r
8672 DisplayError(_("No game has been loaded yet"), 0);
\r
8675 if (gameNumber <= 0) {
\r
8676 DisplayError(_("Can't back up any further"), 0);
\r
8679 if (cmailMsgLoaded) {
\r
8680 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8681 lastLoadGameTitle, lastLoadGameUseList);
\r
8683 return LoadGame(lastLoadGameFP, gameNumber,
\r
8684 lastLoadGameTitle, lastLoadGameUseList);
\r
8690 /* Load the nth game from open file f */
\r
8692 LoadGame(f, gameNumber, title, useList)
\r
8699 char buf[MSG_SIZ];
\r
8700 int gn = gameNumber;
\r
8701 ListGame *lg = NULL;
\r
8702 int numPGNTags = 0;
\r
8704 GameMode oldGameMode;
\r
8705 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8707 if (appData.debugMode)
\r
8708 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8710 if (gameMode == Training )
\r
8711 SetTrainingModeOff();
\r
8713 oldGameMode = gameMode;
\r
8714 if (gameMode != BeginningOfGame) {
\r
8715 Reset(FALSE, TRUE);
\r
8719 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8720 fclose(lastLoadGameFP);
\r
8724 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8727 fseek(f, lg->offset, 0);
\r
8728 GameListHighlight(gameNumber);
\r
8732 DisplayError(_("Game number out of range"), 0);
\r
8736 GameListDestroy();
\r
8737 if (fseek(f, 0, 0) == -1) {
\r
8738 if (f == lastLoadGameFP ?
\r
8739 gameNumber == lastLoadGameNumber + 1 :
\r
8740 gameNumber == 1) {
\r
8743 DisplayError(_("Can't seek on game file"), 0);
\r
8748 lastLoadGameFP = f;
\r
8749 lastLoadGameNumber = gameNumber;
\r
8750 strcpy(lastLoadGameTitle, title);
\r
8751 lastLoadGameUseList = useList;
\r
8755 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8756 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8757 lg->gameInfo.black);
\r
8758 DisplayTitle(buf);
\r
8759 } else if (*title != NULLCHAR) {
\r
8760 if (gameNumber > 1) {
\r
8761 sprintf(buf, "%s %d", title, gameNumber);
\r
8762 DisplayTitle(buf);
\r
8764 DisplayTitle(title);
\r
8768 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8769 gameMode = PlayFromGameFile;
\r
8773 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8774 CopyBoard(boards[0], initialPosition);
\r
8778 * Skip the first gn-1 games in the file.
\r
8779 * Also skip over anything that precedes an identifiable
\r
8780 * start of game marker, to avoid being confused by
\r
8781 * garbage at the start of the file. Currently
\r
8782 * recognized start of game markers are the move number "1",
\r
8783 * the pattern "gnuchess .* game", the pattern
\r
8784 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8785 * A game that starts with one of the latter two patterns
\r
8786 * will also have a move number 1, possibly
\r
8787 * following a position diagram.
\r
8788 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8789 * start with an unnumbered move. Does that break anything?
\r
8791 cm = lastLoadGameStart = (ChessMove) 0;
\r
8793 yyboardindex = forwardMostMove;
\r
8794 cm = (ChessMove) yylex();
\r
8796 case (ChessMove) 0:
\r
8797 if (cmailMsgLoaded) {
\r
8798 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8800 Reset(TRUE, TRUE);
\r
8801 DisplayError(_("Game not found in file"), 0);
\r
8805 case GNUChessGame:
\r
8808 lastLoadGameStart = cm;
\r
8811 case MoveNumberOne:
\r
8812 switch (lastLoadGameStart) {
\r
8813 case GNUChessGame:
\r
8817 case MoveNumberOne:
\r
8818 case (ChessMove) 0:
\r
8819 gn--; /* count this game */
\r
8820 lastLoadGameStart = cm;
\r
8829 switch (lastLoadGameStart) {
\r
8830 case GNUChessGame:
\r
8832 case MoveNumberOne:
\r
8833 case (ChessMove) 0:
\r
8834 gn--; /* count this game */
\r
8835 lastLoadGameStart = cm;
\r
8838 lastLoadGameStart = cm; /* game counted already */
\r
8846 yyboardindex = forwardMostMove;
\r
8847 cm = (ChessMove) yylex();
\r
8848 } while (cm == PGNTag || cm == Comment);
\r
8855 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8856 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8857 != CMAIL_OLD_RESULT) {
\r
8858 nCmailResults ++ ;
\r
8859 cmailResult[ CMAIL_MAX_GAMES
\r
8860 - gn - 1] = CMAIL_OLD_RESULT;
\r
8866 /* Only a NormalMove can be at the start of a game
\r
8867 * without a position diagram. */
\r
8868 if (lastLoadGameStart == (ChessMove) 0) {
\r
8870 lastLoadGameStart = MoveNumberOne;
\r
8879 if (appData.debugMode)
\r
8880 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8882 if (cm == XBoardGame) {
\r
8883 /* Skip any header junk before position diagram and/or move 1 */
\r
8885 yyboardindex = forwardMostMove;
\r
8886 cm = (ChessMove) yylex();
\r
8888 if (cm == (ChessMove) 0 ||
\r
8889 cm == GNUChessGame || cm == XBoardGame) {
\r
8890 /* Empty game; pretend end-of-file and handle later */
\r
8891 cm = (ChessMove) 0;
\r
8895 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8896 cm == PGNTag || cm == Comment)
\r
8899 } else if (cm == GNUChessGame) {
\r
8900 if (gameInfo.event != NULL) {
\r
8901 free(gameInfo.event);
\r
8903 gameInfo.event = StrSave(yy_text);
\r
8906 startedFromSetupPosition = FALSE;
\r
8907 while (cm == PGNTag) {
\r
8908 if (appData.debugMode)
\r
8909 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8910 err = ParsePGNTag(yy_text, &gameInfo);
\r
8911 if (!err) numPGNTags++;
\r
8913 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8914 if(gameInfo.variant != oldVariant) {
\r
8915 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8916 InitPosition(TRUE);
\r
8917 oldVariant = gameInfo.variant;
\r
8918 if (appData.debugMode)
\r
8919 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8923 if (gameInfo.fen != NULL) {
\r
8924 Board initial_position;
\r
8925 startedFromSetupPosition = TRUE;
\r
8926 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8927 Reset(TRUE, TRUE);
\r
8928 DisplayError(_("Bad FEN position in file"), 0);
\r
8931 CopyBoard(boards[0], initial_position);
\r
8932 if (blackPlaysFirst) {
\r
8933 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8934 CopyBoard(boards[1], initial_position);
\r
8935 strcpy(moveList[0], "");
\r
8936 strcpy(parseList[0], "");
\r
8937 timeRemaining[0][1] = whiteTimeRemaining;
\r
8938 timeRemaining[1][1] = blackTimeRemaining;
\r
8939 if (commentList[0] != NULL) {
\r
8940 commentList[1] = commentList[0];
\r
8941 commentList[0] = NULL;
\r
8944 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8946 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8948 initialRulePlies = FENrulePlies;
\r
8949 epStatus[forwardMostMove] = FENepStatus;
\r
8950 for( i=0; i< nrCastlingRights; i++ )
\r
8951 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8953 yyboardindex = forwardMostMove;
\r
8954 free(gameInfo.fen);
\r
8955 gameInfo.fen = NULL;
\r
8958 yyboardindex = forwardMostMove;
\r
8959 cm = (ChessMove) yylex();
\r
8961 /* Handle comments interspersed among the tags */
\r
8962 while (cm == Comment) {
\r
8964 if (appData.debugMode)
\r
8965 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8967 if (*p == '{' || *p == '[' || *p == '(') {
\r
8968 p[strlen(p) - 1] = NULLCHAR;
\r
8971 while (*p == '\n') p++;
\r
8972 AppendComment(currentMove, p);
\r
8973 yyboardindex = forwardMostMove;
\r
8974 cm = (ChessMove) yylex();
\r
8978 /* don't rely on existence of Event tag since if game was
\r
8979 * pasted from clipboard the Event tag may not exist
\r
8981 if (numPGNTags > 0){
\r
8983 if (gameInfo.variant == VariantNormal) {
\r
8984 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8987 if( appData.autoDisplayTags ) {
\r
8988 tags = PGNTags(&gameInfo);
\r
8989 TagsPopUp(tags, CmailMsg());
\r
8994 /* Make something up, but don't display it now */
\r
8999 if (cm == PositionDiagram) {
\r
9002 Board initial_position;
\r
9004 if (appData.debugMode)
\r
9005 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9007 if (!startedFromSetupPosition) {
\r
9009 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9010 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9020 initial_position[i][j++] = CharToPiece(*p);
\r
9023 while (*p == ' ' || *p == '\t' ||
\r
9024 *p == '\n' || *p == '\r') p++;
\r
9026 if (strncmp(p, "black", strlen("black"))==0)
\r
9027 blackPlaysFirst = TRUE;
\r
9029 blackPlaysFirst = FALSE;
\r
9030 startedFromSetupPosition = TRUE;
\r
9032 CopyBoard(boards[0], initial_position);
\r
9033 if (blackPlaysFirst) {
\r
9034 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9035 CopyBoard(boards[1], initial_position);
\r
9036 strcpy(moveList[0], "");
\r
9037 strcpy(parseList[0], "");
\r
9038 timeRemaining[0][1] = whiteTimeRemaining;
\r
9039 timeRemaining[1][1] = blackTimeRemaining;
\r
9040 if (commentList[0] != NULL) {
\r
9041 commentList[1] = commentList[0];
\r
9042 commentList[0] = NULL;
\r
9045 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9048 yyboardindex = forwardMostMove;
\r
9049 cm = (ChessMove) yylex();
\r
9052 if (first.pr == NoProc) {
\r
9053 StartChessProgram(&first);
\r
9055 InitChessProgram(&first, FALSE);
\r
9056 SendToProgram("force\n", &first);
\r
9057 if (startedFromSetupPosition) {
\r
9058 SendBoard(&first, forwardMostMove);
\r
9059 if (appData.debugMode) {
\r
9060 fprintf(debugFP, "Load Game\n");
\r
9062 DisplayBothClocks();
\r
9065 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9066 loadFlag = appData.suppressLoadMoves;
\r
9068 while (cm == Comment) {
\r
9070 if (appData.debugMode)
\r
9071 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9073 if (*p == '{' || *p == '[' || *p == '(') {
\r
9074 p[strlen(p) - 1] = NULLCHAR;
\r
9077 while (*p == '\n') p++;
\r
9078 AppendComment(currentMove, p);
\r
9079 yyboardindex = forwardMostMove;
\r
9080 cm = (ChessMove) yylex();
\r
9083 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9084 cm == WhiteWins || cm == BlackWins ||
\r
9085 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9086 DisplayMessage("", _("No moves in game"));
\r
9087 if (cmailMsgLoaded) {
\r
9088 if (appData.debugMode)
\r
9089 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9090 ClearHighlights();
\r
9093 DrawPosition(FALSE, boards[currentMove]);
\r
9094 DisplayBothClocks();
\r
9095 gameMode = EditGame;
\r
9097 gameFileFP = NULL;
\r
9102 // [HGM] PV info: routine tests if comment empty
\r
9103 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9104 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9106 if (!matchMode && appData.timeDelay != 0)
\r
9107 DrawPosition(FALSE, boards[currentMove]);
\r
9109 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9110 programStats.ok_to_send = 1;
\r
9113 /* if the first token after the PGN tags is a move
\r
9114 * and not move number 1, retrieve it from the parser
\r
9116 if (cm != MoveNumberOne)
\r
9117 LoadGameOneMove(cm);
\r
9119 /* load the remaining moves from the file */
\r
9120 while (LoadGameOneMove((ChessMove)0)) {
\r
9121 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9122 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9125 /* rewind to the start of the game */
\r
9126 currentMove = backwardMostMove;
\r
9128 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9130 if (oldGameMode == AnalyzeFile ||
\r
9131 oldGameMode == AnalyzeMode) {
\r
9132 AnalyzeFileEvent();
\r
9135 if (matchMode || appData.timeDelay == 0) {
\r
9137 gameMode = EditGame;
\r
9139 } else if (appData.timeDelay > 0) {
\r
9140 AutoPlayGameLoop();
\r
9143 if (appData.debugMode)
\r
9144 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9146 loadFlag = 0; /* [HGM] true game starts */
\r
9150 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9152 ReloadPosition(offset)
\r
9155 int positionNumber = lastLoadPositionNumber + offset;
\r
9156 if (lastLoadPositionFP == NULL) {
\r
9157 DisplayError(_("No position has been loaded yet"), 0);
\r
9160 if (positionNumber <= 0) {
\r
9161 DisplayError(_("Can't back up any further"), 0);
\r
9164 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9165 lastLoadPositionTitle);
\r
9168 /* Load the nth position from the given file */
\r
9170 LoadPositionFromFile(filename, n, title)
\r
9176 char buf[MSG_SIZ];
\r
9178 if (strcmp(filename, "-") == 0) {
\r
9179 return LoadPosition(stdin, n, "stdin");
\r
9181 f = fopen(filename, "rb");
\r
9183 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9184 DisplayError(buf, errno);
\r
9187 return LoadPosition(f, n, title);
\r
9192 /* Load the nth position from the given open file, and close it */
\r
9194 LoadPosition(f, positionNumber, title)
\r
9196 int positionNumber;
\r
9199 char *p, line[MSG_SIZ];
\r
9200 Board initial_position;
\r
9201 int i, j, fenMode, pn;
\r
9203 if (gameMode == Training )
\r
9204 SetTrainingModeOff();
\r
9206 if (gameMode != BeginningOfGame) {
\r
9207 Reset(FALSE, TRUE);
\r
9209 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9210 fclose(lastLoadPositionFP);
\r
9212 if (positionNumber == 0) positionNumber = 1;
\r
9213 lastLoadPositionFP = f;
\r
9214 lastLoadPositionNumber = positionNumber;
\r
9215 strcpy(lastLoadPositionTitle, title);
\r
9216 if (first.pr == NoProc) {
\r
9217 StartChessProgram(&first);
\r
9218 InitChessProgram(&first, FALSE);
\r
9220 pn = positionNumber;
\r
9221 if (positionNumber < 0) {
\r
9222 /* Negative position number means to seek to that byte offset */
\r
9223 if (fseek(f, -positionNumber, 0) == -1) {
\r
9224 DisplayError(_("Can't seek on position file"), 0);
\r
9229 if (fseek(f, 0, 0) == -1) {
\r
9230 if (f == lastLoadPositionFP ?
\r
9231 positionNumber == lastLoadPositionNumber + 1 :
\r
9232 positionNumber == 1) {
\r
9235 DisplayError(_("Can't seek on position file"), 0);
\r
9240 /* See if this file is FEN or old-style xboard */
\r
9241 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9242 DisplayError(_("Position not found in file"), 0);
\r
9246 switch (line[0]) {
\r
9247 case '#': case 'x':
\r
9251 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9252 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9253 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9254 case '7': case '8': case '9':
\r
9255 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9256 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9257 case 'C': case 'W': case 'c': case 'w':
\r
9262 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9263 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9267 if (fenMode || line[0] == '#') pn--;
\r
9269 /* skip positions before number pn */
\r
9270 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9271 Reset(TRUE, TRUE);
\r
9272 DisplayError(_("Position not found in file"), 0);
\r
9275 if (fenMode || line[0] == '#') pn--;
\r
9280 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9281 DisplayError(_("Bad FEN position in file"), 0);
\r
9285 (void) fgets(line, MSG_SIZ, f);
\r
9286 (void) fgets(line, MSG_SIZ, f);
\r
9288 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9289 (void) fgets(line, MSG_SIZ, f);
\r
9290 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9293 initial_position[i][j++] = CharToPiece(*p);
\r
9297 blackPlaysFirst = FALSE;
\r
9299 (void) fgets(line, MSG_SIZ, f);
\r
9300 if (strncmp(line, "black", strlen("black"))==0)
\r
9301 blackPlaysFirst = TRUE;
\r
9304 startedFromSetupPosition = TRUE;
\r
9306 SendToProgram("force\n", &first);
\r
9307 CopyBoard(boards[0], initial_position);
\r
9308 if (blackPlaysFirst) {
\r
9309 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9310 strcpy(moveList[0], "");
\r
9311 strcpy(parseList[0], "");
\r
9312 CopyBoard(boards[1], initial_position);
\r
9313 DisplayMessage("", _("Black to play"));
\r
9315 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9316 DisplayMessage("", _("White to play"));
\r
9318 /* [HGM] copy FEN attributes as well */
\r
9320 initialRulePlies = FENrulePlies;
\r
9321 epStatus[forwardMostMove] = FENepStatus;
\r
9322 for( i=0; i< nrCastlingRights; i++ )
\r
9323 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9325 SendBoard(&first, forwardMostMove);
\r
9326 if (appData.debugMode) {
\r
9328 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9329 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9330 fprintf(debugFP, "Load Position\n");
\r
9333 if (positionNumber > 1) {
\r
9334 sprintf(line, "%s %d", title, positionNumber);
\r
9335 DisplayTitle(line);
\r
9337 DisplayTitle(title);
\r
9339 gameMode = EditGame;
\r
9342 timeRemaining[0][1] = whiteTimeRemaining;
\r
9343 timeRemaining[1][1] = blackTimeRemaining;
\r
9344 DrawPosition(FALSE, boards[currentMove]);
\r
9351 CopyPlayerNameIntoFileName(dest, src)
\r
9352 char **dest, *src;
\r
9354 while (*src != NULLCHAR && *src != ',') {
\r
9355 if (*src == ' ') {
\r
9359 *(*dest)++ = *src++;
\r
9364 char *DefaultFileName(ext)
\r
9367 static char def[MSG_SIZ];
\r
9370 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9372 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9374 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9378 def[0] = NULLCHAR;
\r
9383 /* Save the current game to the given file */
\r
9385 SaveGameToFile(filename, append)
\r
9390 char buf[MSG_SIZ];
\r
9392 if (strcmp(filename, "-") == 0) {
\r
9393 return SaveGame(stdout, 0, NULL);
\r
9395 f = fopen(filename, append ? "a" : "w");
\r
9397 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9398 DisplayError(buf, errno);
\r
9401 return SaveGame(f, 0, NULL);
\r
9410 static char buf[MSG_SIZ];
\r
9413 p = strchr(str, ' ');
\r
9414 if (p == NULL) return str;
\r
9415 strncpy(buf, str, p - str);
\r
9416 buf[p - str] = NULLCHAR;
\r
9420 #define PGN_MAX_LINE 75
\r
9422 #define PGN_SIDE_WHITE 0
\r
9423 #define PGN_SIDE_BLACK 1
\r
9426 static int FindFirstMoveOutOfBook( int side )
\r
9430 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9431 int index = backwardMostMove;
\r
9432 int has_book_hit = 0;
\r
9434 if( (index % 2) != side ) {
\r
9438 while( index < forwardMostMove ) {
\r
9439 /* Check to see if engine is in book */
\r
9440 int depth = pvInfoList[index].depth;
\r
9441 int score = pvInfoList[index].score;
\r
9444 if( depth <= 2 ) {
\r
9447 else if( score == 0 && depth == 63 ) {
\r
9448 in_book = 1; /* Zappa */
\r
9450 else if( score == 2 && depth == 99 ) {
\r
9451 in_book = 1; /* Abrok */
\r
9454 has_book_hit += in_book;
\r
9470 void GetOutOfBookInfo( char * buf )
\r
9474 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9476 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9477 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9481 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9482 for( i=0; i<2; i++ ) {
\r
9486 if( i > 0 && oob[0] >= 0 ) {
\r
9487 strcat( buf, " " );
\r
9490 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9491 sprintf( buf+strlen(buf), "%s%.2f",
\r
9492 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9493 pvInfoList[idx].score / 100.0 );
\r
9499 /* Save game in PGN style and close the file */
\r
9504 int i, offset, linelen, newblock;
\r
9508 int movelen, numlen, blank;
\r
9509 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9511 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9513 tm = time((time_t *) NULL);
\r
9515 PrintPGNTags(f, &gameInfo);
\r
9517 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9518 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9519 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9520 fprintf(f, "\n{--------------\n");
\r
9521 PrintPosition(f, backwardMostMove);
\r
9522 fprintf(f, "--------------}\n");
\r
9526 /* [AS] Out of book annotation */
\r
9527 if( appData.saveOutOfBookInfo ) {
\r
9530 GetOutOfBookInfo( buf );
\r
9532 if( buf[0] != '\0' ) {
\r
9533 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9540 i = backwardMostMove;
\r
9544 while (i < forwardMostMove) {
\r
9545 /* Print comments preceding this move */
\r
9546 if (commentList[i] != NULL) {
\r
9547 if (linelen > 0) fprintf(f, "\n");
\r
9548 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9553 /* Format move number */
\r
9554 if ((i % 2) == 0) {
\r
9555 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9558 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9560 numtext[0] = NULLCHAR;
\r
9563 numlen = strlen(numtext);
\r
9566 /* Print move number */
\r
9567 blank = linelen > 0 && numlen > 0;
\r
9568 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9577 fprintf(f, numtext);
\r
9578 linelen += numlen;
\r
9581 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9584 blank = linelen > 0 && movelen > 0;
\r
9585 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9594 fprintf(f, parseList[i]);
\r
9595 linelen += movelen;
\r
9597 /* [AS] Add PV info if present */
\r
9598 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9599 /* [HGM] add time */
\r
9600 char buf[MSG_SIZ]; int seconds = 0;
\r
9603 if(i >= backwardMostMove) {
\r
9604 if(WhiteOnMove(i))
\r
9605 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9606 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9608 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9609 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9611 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9613 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9615 if (appData.debugMode,0) {
\r
9616 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9617 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9618 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9622 if( seconds <= 0) buf[0] = 0; else
\r
9623 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9624 seconds = (seconds + 4)/10; // round to full seconds
\r
9625 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9626 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9629 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9630 pvInfoList[i].score >= 0 ? "+" : "",
\r
9631 pvInfoList[i].score / 100.0,
\r
9632 pvInfoList[i].depth,
\r
9635 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9637 /* Print score/depth */
\r
9638 blank = linelen > 0 && movelen > 0;
\r
9639 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9648 fprintf(f, move_buffer);
\r
9649 linelen += movelen;
\r
9655 /* Start a new line */
\r
9656 if (linelen > 0) fprintf(f, "\n");
\r
9658 /* Print comments after last move */
\r
9659 if (commentList[i] != NULL) {
\r
9660 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9663 /* Print result */
\r
9664 if (gameInfo.resultDetails != NULL &&
\r
9665 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9666 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9667 PGNResult(gameInfo.result));
\r
9669 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9676 /* Save game in old style and close the file */
\r
9678 SaveGameOldStyle(f)
\r
9684 tm = time((time_t *) NULL);
\r
9686 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9687 PrintOpponents(f);
\r
9689 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9690 fprintf(f, "\n[--------------\n");
\r
9691 PrintPosition(f, backwardMostMove);
\r
9692 fprintf(f, "--------------]\n");
\r
9697 i = backwardMostMove;
\r
9698 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9700 while (i < forwardMostMove) {
\r
9701 if (commentList[i] != NULL) {
\r
9702 fprintf(f, "[%s]\n", commentList[i]);
\r
9705 if ((i % 2) == 1) {
\r
9706 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9709 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9711 if (commentList[i] != NULL) {
\r
9715 if (i >= forwardMostMove) {
\r
9719 fprintf(f, "%s\n", parseList[i]);
\r
9724 if (commentList[i] != NULL) {
\r
9725 fprintf(f, "[%s]\n", commentList[i]);
\r
9728 /* This isn't really the old style, but it's close enough */
\r
9729 if (gameInfo.resultDetails != NULL &&
\r
9730 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9731 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9732 gameInfo.resultDetails);
\r
9734 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9741 /* Save the current game to open file f and close the file */
\r
9743 SaveGame(f, dummy, dummy2)
\r
9748 if (gameMode == EditPosition) EditPositionDone();
\r
9749 if (appData.oldSaveStyle)
\r
9750 return SaveGameOldStyle(f);
\r
9752 return SaveGamePGN(f);
\r
9755 /* Save the current position to the given file */
\r
9757 SavePositionToFile(filename)
\r
9761 char buf[MSG_SIZ];
\r
9763 if (strcmp(filename, "-") == 0) {
\r
9764 return SavePosition(stdout, 0, NULL);
\r
9766 f = fopen(filename, "a");
\r
9768 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9769 DisplayError(buf, errno);
\r
9772 SavePosition(f, 0, NULL);
\r
9778 /* Save the current position to the given open file and close the file */
\r
9780 SavePosition(f, dummy, dummy2)
\r
9788 if (appData.oldSaveStyle) {
\r
9789 tm = time((time_t *) NULL);
\r
9791 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9792 PrintOpponents(f);
\r
9793 fprintf(f, "[--------------\n");
\r
9794 PrintPosition(f, currentMove);
\r
9795 fprintf(f, "--------------]\n");
\r
9797 fen = PositionToFEN(currentMove, 1);
\r
9798 fprintf(f, "%s\n", fen);
\r
9806 ReloadCmailMsgEvent(unregister)
\r
9810 static char *inFilename = NULL;
\r
9811 static char *outFilename;
\r
9813 struct stat inbuf, outbuf;
\r
9816 /* Any registered moves are unregistered if unregister is set, */
\r
9817 /* i.e. invoked by the signal handler */
\r
9819 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9820 cmailMoveRegistered[i] = FALSE;
\r
9821 if (cmailCommentList[i] != NULL) {
\r
9822 free(cmailCommentList[i]);
\r
9823 cmailCommentList[i] = NULL;
\r
9826 nCmailMovesRegistered = 0;
\r
9829 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9830 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9832 nCmailResults = 0;
\r
9834 if (inFilename == NULL) {
\r
9835 /* Because the filenames are static they only get malloced once */
\r
9836 /* and they never get freed */
\r
9837 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9838 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9840 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9841 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9844 status = stat(outFilename, &outbuf);
\r
9846 cmailMailedMove = FALSE;
\r
9848 status = stat(inFilename, &inbuf);
\r
9849 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9852 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9853 counts the games, notes how each one terminated, etc.
\r
9855 It would be nice to remove this kludge and instead gather all
\r
9856 the information while building the game list. (And to keep it
\r
9857 in the game list nodes instead of having a bunch of fixed-size
\r
9858 parallel arrays.) Note this will require getting each game's
\r
9859 termination from the PGN tags, as the game list builder does
\r
9860 not process the game moves. --mann
\r
9862 cmailMsgLoaded = TRUE;
\r
9863 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9865 /* Load first game in the file or popup game menu */
\r
9866 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9868 #endif /* !WIN32 */
\r
9876 char string[MSG_SIZ];
\r
9878 if ( cmailMailedMove
\r
9879 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9880 return TRUE; /* Allow free viewing */
\r
9883 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9884 /* with the move registered when the conditions for registering no */
\r
9886 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9887 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9888 nCmailMovesRegistered --;
\r
9890 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9892 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9893 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9897 if (cmailOldMove == -1) {
\r
9898 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9902 if (currentMove > cmailOldMove + 1) {
\r
9903 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9907 if (currentMove < cmailOldMove) {
\r
9908 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9912 if (forwardMostMove > currentMove) {
\r
9913 /* Silently truncate extra moves */
\r
9917 if ( (currentMove == cmailOldMove + 1)
\r
9918 || ( (currentMove == cmailOldMove)
\r
9919 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9920 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9921 if (gameInfo.result != GameUnfinished) {
\r
9922 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9925 if (commentList[currentMove] != NULL) {
\r
9926 cmailCommentList[lastLoadGameNumber - 1]
\r
9927 = StrSave(commentList[currentMove]);
\r
9929 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9931 if (appData.debugMode)
\r
9932 fprintf(debugFP, "Saving %s for game %d\n",
\r
9933 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9936 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9938 f = fopen(string, "w");
\r
9939 if (appData.oldSaveStyle) {
\r
9940 SaveGameOldStyle(f); /* also closes the file */
\r
9942 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9943 f = fopen(string, "w");
\r
9944 SavePosition(f, 0, NULL); /* also closes the file */
\r
9946 fprintf(f, "{--------------\n");
\r
9947 PrintPosition(f, currentMove);
\r
9948 fprintf(f, "--------------}\n\n");
\r
9950 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9953 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9954 nCmailMovesRegistered ++;
\r
9955 } else if (nCmailGames == 1) {
\r
9956 DisplayError(_("You have not made a move yet"), 0);
\r
9967 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9968 FILE *commandOutput;
\r
9969 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9970 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9976 if (! cmailMsgLoaded) {
\r
9977 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9981 if (nCmailGames == nCmailResults) {
\r
9982 DisplayError(_("No unfinished games"), 0);
\r
9986 #if CMAIL_PROHIBIT_REMAIL
\r
9987 if (cmailMailedMove) {
\r
9988 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
9989 DisplayError(msg, 0);
\r
9994 if (! (cmailMailedMove || RegisterMove())) return;
\r
9996 if ( cmailMailedMove
\r
9997 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9998 sprintf(string, partCommandString,
\r
9999 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10000 commandOutput = popen(string, "r");
\r
10002 if (commandOutput == NULL) {
\r
10003 DisplayError(_("Failed to invoke cmail"), 0);
\r
10005 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10006 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10008 if (nBuffers > 1) {
\r
10009 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10010 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10011 nBytes = MSG_SIZ - 1;
\r
10013 (void) memcpy(msg, buffer, nBytes);
\r
10015 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10017 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10018 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10021 for (i = 0; i < nCmailGames; i ++) {
\r
10022 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10023 archived = FALSE;
\r
10027 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10029 sprintf(buffer, "%s/%s.%s.archive",
\r
10031 appData.cmailGameName,
\r
10033 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10034 cmailMsgLoaded = FALSE;
\r
10038 DisplayInformation(msg);
\r
10039 pclose(commandOutput);
\r
10042 if ((*cmailMsg) != '\0') {
\r
10043 DisplayInformation(cmailMsg);
\r
10048 #endif /* !WIN32 */
\r
10057 int prependComma = 0;
\r
10059 char string[MSG_SIZ]; /* Space for game-list */
\r
10062 if (!cmailMsgLoaded) return "";
\r
10064 if (cmailMailedMove) {
\r
10065 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10067 /* Create a list of games left */
\r
10068 sprintf(string, "[");
\r
10069 for (i = 0; i < nCmailGames; i ++) {
\r
10070 if (! ( cmailMoveRegistered[i]
\r
10071 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10072 if (prependComma) {
\r
10073 sprintf(number, ",%d", i + 1);
\r
10075 sprintf(number, "%d", i + 1);
\r
10076 prependComma = 1;
\r
10079 strcat(string, number);
\r
10082 strcat(string, "]");
\r
10084 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10085 switch (nCmailGames) {
\r
10087 sprintf(cmailMsg,
\r
10088 _("Still need to make move for game\n"));
\r
10092 sprintf(cmailMsg,
\r
10093 _("Still need to make moves for both games\n"));
\r
10097 sprintf(cmailMsg,
\r
10098 _("Still need to make moves for all %d games\n"),
\r
10103 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10105 sprintf(cmailMsg,
\r
10106 _("Still need to make a move for game %s\n"),
\r
10111 if (nCmailResults == nCmailGames) {
\r
10112 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10114 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10119 sprintf(cmailMsg,
\r
10120 _("Still need to make moves for games %s\n"),
\r
10126 #endif /* WIN32 */
\r
10132 if (gameMode == Training)
\r
10133 SetTrainingModeOff();
\r
10135 Reset(TRUE, TRUE);
\r
10136 cmailMsgLoaded = FALSE;
\r
10137 if (appData.icsActive) {
\r
10138 SendToICS(ics_prefix);
\r
10139 SendToICS("refresh\n");
\r
10144 ExitEvent(status)
\r
10148 if (exiting > 2) {
\r
10149 /* Give up on clean exit */
\r
10152 if (exiting > 1) {
\r
10153 /* Keep trying for clean exit */
\r
10157 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10159 if (telnetISR != NULL) {
\r
10160 RemoveInputSource(telnetISR);
\r
10162 if (icsPR != NoProc) {
\r
10163 DestroyChildProcess(icsPR, TRUE);
\r
10166 /* Save game if resource set and not already saved by GameEnds() */
\r
10167 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10168 && forwardMostMove > 0) {
\r
10169 if (*appData.saveGameFile != NULLCHAR) {
\r
10170 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10171 } else if (appData.autoSaveGames) {
\r
10174 if (*appData.savePositionFile != NULLCHAR) {
\r
10175 SavePositionToFile(appData.savePositionFile);
\r
10178 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10180 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10181 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10183 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10184 /* make sure this other one finishes before killing it! */
\r
10185 if(endingGame) { int count = 0;
\r
10186 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10187 while(endingGame && count++ < 10) DoSleep(1);
\r
10188 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10191 /* Kill off chess programs */
\r
10192 if (first.pr != NoProc) {
\r
10193 ExitAnalyzeMode();
\r
10195 DoSleep( appData.delayBeforeQuit );
\r
10196 SendToProgram("quit\n", &first);
\r
10197 DoSleep( appData.delayAfterQuit );
\r
10198 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10200 if (second.pr != NoProc) {
\r
10201 DoSleep( appData.delayBeforeQuit );
\r
10202 SendToProgram("quit\n", &second);
\r
10203 DoSleep( appData.delayAfterQuit );
\r
10204 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10206 if (first.isr != NULL) {
\r
10207 RemoveInputSource(first.isr);
\r
10209 if (second.isr != NULL) {
\r
10210 RemoveInputSource(second.isr);
\r
10213 ShutDownFrontEnd();
\r
10220 if (appData.debugMode)
\r
10221 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10225 if (gameMode == MachinePlaysWhite ||
\r
10226 gameMode == MachinePlaysBlack) {
\r
10229 DisplayBothClocks();
\r
10231 if (gameMode == PlayFromGameFile) {
\r
10232 if (appData.timeDelay >= 0)
\r
10233 AutoPlayGameLoop();
\r
10234 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10235 Reset(FALSE, TRUE);
\r
10236 SendToICS(ics_prefix);
\r
10237 SendToICS("refresh\n");
\r
10238 } else if (currentMove < forwardMostMove) {
\r
10239 ForwardInner(forwardMostMove);
\r
10241 pauseExamInvalid = FALSE;
\r
10243 switch (gameMode) {
\r
10246 case IcsExamining:
\r
10247 pauseExamForwardMostMove = forwardMostMove;
\r
10248 pauseExamInvalid = FALSE;
\r
10249 /* fall through */
\r
10250 case IcsObserving:
\r
10251 case IcsPlayingWhite:
\r
10252 case IcsPlayingBlack:
\r
10256 case PlayFromGameFile:
\r
10257 (void) StopLoadGameTimer();
\r
10261 case BeginningOfGame:
\r
10262 if (appData.icsActive) return;
\r
10263 /* else fall through */
\r
10264 case MachinePlaysWhite:
\r
10265 case MachinePlaysBlack:
\r
10266 case TwoMachinesPlay:
\r
10267 if (forwardMostMove == 0)
\r
10268 return; /* don't pause if no one has moved */
\r
10269 if ((gameMode == MachinePlaysWhite &&
\r
10270 !WhiteOnMove(forwardMostMove)) ||
\r
10271 (gameMode == MachinePlaysBlack &&
\r
10272 WhiteOnMove(forwardMostMove))) {
\r
10283 EditCommentEvent()
\r
10285 char title[MSG_SIZ];
\r
10287 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10288 strcpy(title, _("Edit comment"));
\r
10290 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10291 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10292 parseList[currentMove - 1]);
\r
10295 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10302 char *tags = PGNTags(&gameInfo);
\r
10303 EditTagsPopUp(tags);
\r
10308 AnalyzeModeEvent()
\r
10310 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10313 if (gameMode != AnalyzeFile) {
\r
10314 if (!appData.icsEngineAnalyze) {
\r
10316 if (gameMode != EditGame) return;
\r
10318 ResurrectChessProgram();
\r
10319 SendToProgram("analyze\n", &first);
\r
10320 first.analyzing = TRUE;
\r
10321 /*first.maybeThinking = TRUE;*/
\r
10322 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10323 AnalysisPopUp(_("Analysis"),
\r
10324 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10326 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10331 StartAnalysisClock();
\r
10332 GetTimeMark(&lastNodeCountTime);
\r
10333 lastNodeCount = 0;
\r
10337 AnalyzeFileEvent()
\r
10339 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10342 if (gameMode != AnalyzeMode) {
\r
10344 if (gameMode != EditGame) return;
\r
10345 ResurrectChessProgram();
\r
10346 SendToProgram("analyze\n", &first);
\r
10347 first.analyzing = TRUE;
\r
10348 /*first.maybeThinking = TRUE;*/
\r
10349 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10350 AnalysisPopUp(_("Analysis"),
\r
10351 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10353 gameMode = AnalyzeFile;
\r
10358 StartAnalysisClock();
\r
10359 GetTimeMark(&lastNodeCountTime);
\r
10360 lastNodeCount = 0;
\r
10364 MachineWhiteEvent()
\r
10366 char buf[MSG_SIZ];
\r
10367 char *bookHit = NULL;
\r
10369 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10373 if (gameMode == PlayFromGameFile ||
\r
10374 gameMode == TwoMachinesPlay ||
\r
10375 gameMode == Training ||
\r
10376 gameMode == AnalyzeMode ||
\r
10377 gameMode == EndOfGame)
\r
10380 if (gameMode == EditPosition)
\r
10381 EditPositionDone();
\r
10383 if (!WhiteOnMove(currentMove)) {
\r
10384 DisplayError(_("It is not White's turn"), 0);
\r
10388 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10389 ExitAnalyzeMode();
\r
10391 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10392 gameMode == AnalyzeFile)
\r
10395 ResurrectChessProgram(); /* in case it isn't running */
\r
10396 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10397 gameMode = MachinePlaysWhite;
\r
10400 gameMode = MachinePlaysWhite;
\r
10404 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10405 DisplayTitle(buf);
\r
10406 if (first.sendName) {
\r
10407 sprintf(buf, "name %s\n", gameInfo.black);
\r
10408 SendToProgram(buf, &first);
\r
10410 if (first.sendTime) {
\r
10411 if (first.useColors) {
\r
10412 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10414 SendTimeRemaining(&first, TRUE);
\r
10416 if (first.useColors) {
\r
10417 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10419 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10420 SetMachineThinkingEnables();
\r
10421 first.maybeThinking = TRUE;
\r
10424 if (appData.autoFlipView && !flipView) {
\r
10425 flipView = !flipView;
\r
10426 DrawPosition(FALSE, NULL);
\r
10427 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10430 if(bookHit) { // [HGM] book: simulate book reply
\r
10431 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10433 programStats.depth = programStats.nodes = programStats.time =
\r
10434 programStats.score = programStats.got_only_move = 0;
\r
10435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10437 strcpy(bookMove, "move ");
\r
10438 strcat(bookMove, bookHit);
\r
10439 HandleMachineMove(bookMove, &first);
\r
10444 MachineBlackEvent()
\r
10446 char buf[MSG_SIZ];
\r
10447 char *bookHit = NULL;
\r
10449 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10453 if (gameMode == PlayFromGameFile ||
\r
10454 gameMode == TwoMachinesPlay ||
\r
10455 gameMode == Training ||
\r
10456 gameMode == AnalyzeMode ||
\r
10457 gameMode == EndOfGame)
\r
10460 if (gameMode == EditPosition)
\r
10461 EditPositionDone();
\r
10463 if (WhiteOnMove(currentMove)) {
\r
10464 DisplayError(_("It is not Black's turn"), 0);
\r
10468 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10469 ExitAnalyzeMode();
\r
10471 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10472 gameMode == AnalyzeFile)
\r
10475 ResurrectChessProgram(); /* in case it isn't running */
\r
10476 gameMode = MachinePlaysBlack;
\r
10480 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10481 DisplayTitle(buf);
\r
10482 if (first.sendName) {
\r
10483 sprintf(buf, "name %s\n", gameInfo.white);
\r
10484 SendToProgram(buf, &first);
\r
10486 if (first.sendTime) {
\r
10487 if (first.useColors) {
\r
10488 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10490 SendTimeRemaining(&first, FALSE);
\r
10492 if (first.useColors) {
\r
10493 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10495 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10496 SetMachineThinkingEnables();
\r
10497 first.maybeThinking = TRUE;
\r
10500 if (appData.autoFlipView && flipView) {
\r
10501 flipView = !flipView;
\r
10502 DrawPosition(FALSE, NULL);
\r
10503 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10505 if(bookHit) { // [HGM] book: simulate book reply
\r
10506 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10508 programStats.depth = programStats.nodes = programStats.time =
\r
10509 programStats.score = programStats.got_only_move = 0;
\r
10510 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10512 strcpy(bookMove, "move ");
\r
10513 strcat(bookMove, bookHit);
\r
10514 HandleMachineMove(bookMove, &first);
\r
10520 DisplayTwoMachinesTitle()
\r
10522 char buf[MSG_SIZ];
\r
10523 if (appData.matchGames > 0) {
\r
10524 if (first.twoMachinesColor[0] == 'w') {
\r
10525 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10526 gameInfo.white, gameInfo.black,
\r
10527 first.matchWins, second.matchWins,
\r
10528 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10530 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10531 gameInfo.white, gameInfo.black,
\r
10532 second.matchWins, first.matchWins,
\r
10533 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10536 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10538 DisplayTitle(buf);
\r
10542 TwoMachinesEvent P((void))
\r
10545 char buf[MSG_SIZ];
\r
10546 ChessProgramState *onmove;
\r
10547 char *bookHit = NULL;
\r
10549 if (appData.noChessProgram) return;
\r
10551 switch (gameMode) {
\r
10552 case TwoMachinesPlay:
\r
10554 case MachinePlaysWhite:
\r
10555 case MachinePlaysBlack:
\r
10556 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10557 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10560 /* fall through */
\r
10561 case BeginningOfGame:
\r
10562 case PlayFromGameFile:
\r
10565 if (gameMode != EditGame) return;
\r
10567 case EditPosition:
\r
10568 EditPositionDone();
\r
10570 case AnalyzeMode:
\r
10571 case AnalyzeFile:
\r
10572 ExitAnalyzeMode();
\r
10579 forwardMostMove = currentMove;
\r
10580 ResurrectChessProgram(); /* in case first program isn't running */
\r
10582 if (second.pr == NULL) {
\r
10583 StartChessProgram(&second);
\r
10584 if (second.protocolVersion == 1) {
\r
10585 TwoMachinesEventIfReady();
\r
10587 /* kludge: allow timeout for initial "feature" command */
\r
10589 DisplayMessage("", _("Starting second chess program"));
\r
10590 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10594 DisplayMessage("", "");
\r
10595 InitChessProgram(&second, FALSE);
\r
10596 SendToProgram("force\n", &second);
\r
10597 if (startedFromSetupPosition) {
\r
10598 SendBoard(&second, backwardMostMove);
\r
10599 if (appData.debugMode) {
\r
10600 fprintf(debugFP, "Two Machines\n");
\r
10603 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10604 SendMoveToProgram(i, &second);
\r
10607 gameMode = TwoMachinesPlay;
\r
10611 DisplayTwoMachinesTitle();
\r
10612 firstMove = TRUE;
\r
10613 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10616 onmove = &second;
\r
10619 SendToProgram(first.computerString, &first);
\r
10620 if (first.sendName) {
\r
10621 sprintf(buf, "name %s\n", second.tidy);
\r
10622 SendToProgram(buf, &first);
\r
10624 SendToProgram(second.computerString, &second);
\r
10625 if (second.sendName) {
\r
10626 sprintf(buf, "name %s\n", first.tidy);
\r
10627 SendToProgram(buf, &second);
\r
10631 if (!first.sendTime || !second.sendTime) {
\r
10632 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10633 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10635 if (onmove->sendTime) {
\r
10636 if (onmove->useColors) {
\r
10637 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10639 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10641 if (onmove->useColors) {
\r
10642 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10644 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10645 // SendToProgram("go\n", onmove);
\r
10646 onmove->maybeThinking = TRUE;
\r
10647 SetMachineThinkingEnables();
\r
10651 if(bookHit) { // [HGM] book: simulate book reply
\r
10652 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10654 programStats.depth = programStats.nodes = programStats.time =
\r
10655 programStats.score = programStats.got_only_move = 0;
\r
10656 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10658 strcpy(bookMove, "move ");
\r
10659 strcat(bookMove, bookHit);
\r
10660 HandleMachineMove(bookMove, &first);
\r
10667 if (gameMode == Training) {
\r
10668 SetTrainingModeOff();
\r
10669 gameMode = PlayFromGameFile;
\r
10670 DisplayMessage("", _("Training mode off"));
\r
10672 gameMode = Training;
\r
10673 animateTraining = appData.animate;
\r
10675 /* make sure we are not already at the end of the game */
\r
10676 if (currentMove < forwardMostMove) {
\r
10677 SetTrainingModeOn();
\r
10678 DisplayMessage("", _("Training mode on"));
\r
10680 gameMode = PlayFromGameFile;
\r
10681 DisplayError(_("Already at end of game"), 0);
\r
10690 if (!appData.icsActive) return;
\r
10691 switch (gameMode) {
\r
10692 case IcsPlayingWhite:
\r
10693 case IcsPlayingBlack:
\r
10694 case IcsObserving:
\r
10696 case BeginningOfGame:
\r
10697 case IcsExamining:
\r
10703 case EditPosition:
\r
10704 EditPositionDone();
\r
10707 case AnalyzeMode:
\r
10708 case AnalyzeFile:
\r
10709 ExitAnalyzeMode();
\r
10717 gameMode = IcsIdle;
\r
10728 switch (gameMode) {
\r
10730 SetTrainingModeOff();
\r
10732 case MachinePlaysWhite:
\r
10733 case MachinePlaysBlack:
\r
10734 case BeginningOfGame:
\r
10735 SendToProgram("force\n", &first);
\r
10736 SetUserThinkingEnables();
\r
10738 case PlayFromGameFile:
\r
10739 (void) StopLoadGameTimer();
\r
10740 if (gameFileFP != NULL) {
\r
10741 gameFileFP = NULL;
\r
10744 case EditPosition:
\r
10745 EditPositionDone();
\r
10747 case AnalyzeMode:
\r
10748 case AnalyzeFile:
\r
10749 ExitAnalyzeMode();
\r
10750 SendToProgram("force\n", &first);
\r
10752 case TwoMachinesPlay:
\r
10753 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10754 ResurrectChessProgram();
\r
10755 SetUserThinkingEnables();
\r
10758 ResurrectChessProgram();
\r
10760 case IcsPlayingBlack:
\r
10761 case IcsPlayingWhite:
\r
10762 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10764 case IcsObserving:
\r
10765 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10767 case IcsExamining:
\r
10768 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10779 first.offeredDraw = second.offeredDraw = 0;
\r
10781 if (gameMode == PlayFromGameFile) {
\r
10782 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10783 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10784 DisplayTitle("");
\r
10787 if (gameMode == MachinePlaysWhite ||
\r
10788 gameMode == MachinePlaysBlack ||
\r
10789 gameMode == TwoMachinesPlay ||
\r
10790 gameMode == EndOfGame) {
\r
10791 i = forwardMostMove;
\r
10792 while (i > currentMove) {
\r
10793 SendToProgram("undo\n", &first);
\r
10796 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10797 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10798 DisplayBothClocks();
\r
10799 if (whiteFlag || blackFlag) {
\r
10800 whiteFlag = blackFlag = 0;
\r
10802 DisplayTitle("");
\r
10805 gameMode = EditGame;
\r
10812 EditPositionEvent()
\r
10814 if (gameMode == EditPosition) {
\r
10820 if (gameMode != EditGame) return;
\r
10822 gameMode = EditPosition;
\r
10825 if (currentMove > 0)
\r
10826 CopyBoard(boards[0], boards[currentMove]);
\r
10828 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10830 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10831 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10836 ExitAnalyzeMode()
\r
10838 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10839 if (appData.icsEngineAnalyze) {
\r
10840 appData.icsEngineAnalyze = FALSE;
\r
10842 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10844 if (first.analysisSupport && first.analyzing) {
\r
10845 SendToProgram("exit\n", &first);
\r
10846 first.analyzing = FALSE;
\r
10848 AnalysisPopDown();
\r
10849 thinkOutput[0] = NULLCHAR;
\r
10853 EditPositionDone()
\r
10855 startedFromSetupPosition = TRUE;
\r
10856 InitChessProgram(&first, FALSE);
\r
10857 SendToProgram("force\n", &first);
\r
10858 if (blackPlaysFirst) {
\r
10859 strcpy(moveList[0], "");
\r
10860 strcpy(parseList[0], "");
\r
10861 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10862 CopyBoard(boards[1], boards[0]);
\r
10863 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10865 epStatus[1] = epStatus[0];
\r
10866 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10869 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10871 SendBoard(&first, forwardMostMove);
\r
10872 if (appData.debugMode) {
\r
10873 fprintf(debugFP, "EditPosDone\n");
\r
10875 DisplayTitle("");
\r
10876 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10877 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10878 gameMode = EditGame;
\r
10880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10881 ClearHighlights(); /* [AS] */
\r
10884 /* Pause for `ms' milliseconds */
\r
10885 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10892 GetTimeMark(&m1);
\r
10894 GetTimeMark(&m2);
\r
10895 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10900 SendMultiLineToICS(buf)
\r
10903 char temp[MSG_SIZ+1], *p;
\r
10906 len = strlen(buf);
\r
10907 if (len > MSG_SIZ)
\r
10910 strncpy(temp, buf, len);
\r
10915 if (*p == '\n' || *p == '\r')
\r
10920 strcat(temp, "\n");
\r
10922 SendToPlayer(temp, strlen(temp));
\r
10926 SetWhiteToPlayEvent()
\r
10928 if (gameMode == EditPosition) {
\r
10929 blackPlaysFirst = FALSE;
\r
10930 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10931 } else if (gameMode == IcsExamining) {
\r
10932 SendToICS(ics_prefix);
\r
10933 SendToICS("tomove white\n");
\r
10938 SetBlackToPlayEvent()
\r
10940 if (gameMode == EditPosition) {
\r
10941 blackPlaysFirst = TRUE;
\r
10942 currentMove = 1; /* kludge */
\r
10943 DisplayBothClocks();
\r
10945 } else if (gameMode == IcsExamining) {
\r
10946 SendToICS(ics_prefix);
\r
10947 SendToICS("tomove black\n");
\r
10952 EditPositionMenuEvent(selection, x, y)
\r
10953 ChessSquare selection;
\r
10956 char buf[MSG_SIZ];
\r
10957 ChessSquare piece = boards[0][y][x];
\r
10959 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10961 switch (selection) {
\r
10963 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10964 SendToICS(ics_prefix);
\r
10965 SendToICS("bsetup clear\n");
\r
10966 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10967 SendToICS(ics_prefix);
\r
10968 SendToICS("clearboard\n");
\r
10970 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10971 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10972 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10973 if (gameMode == IcsExamining) {
\r
10974 if (boards[currentMove][y][x] != EmptySquare) {
\r
10975 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10976 AAA + x, ONE + y);
\r
10980 boards[0][y][x] = p;
\r
10985 if (gameMode == EditPosition) {
\r
10986 DrawPosition(FALSE, boards[0]);
\r
10991 SetWhiteToPlayEvent();
\r
10995 SetBlackToPlayEvent();
\r
10998 case EmptySquare:
\r
10999 if (gameMode == IcsExamining) {
\r
11000 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11003 boards[0][y][x] = EmptySquare;
\r
11004 DrawPosition(FALSE, boards[0]);
\r
11008 case PromotePiece:
\r
11009 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11010 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11011 selection = (ChessSquare) (PROMOTED piece);
\r
11012 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11013 else selection = (ChessSquare)((int)piece - 1);
\r
11014 goto defaultlabel;
\r
11016 case DemotePiece:
\r
11017 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11018 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11019 selection = (ChessSquare) (DEMOTED piece);
\r
11020 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11021 else selection = (ChessSquare)((int)piece + 1);
\r
11022 goto defaultlabel;
\r
11026 if(gameInfo.variant == VariantShatranj ||
\r
11027 gameInfo.variant == VariantXiangqi ||
\r
11028 gameInfo.variant == VariantCourier )
\r
11029 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11030 goto defaultlabel;
\r
11034 if(gameInfo.variant == VariantXiangqi)
\r
11035 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11036 if(gameInfo.variant == VariantKnightmate)
\r
11037 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11040 if (gameMode == IcsExamining) {
\r
11041 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11042 PieceToChar(selection), AAA + x, ONE + y);
\r
11045 boards[0][y][x] = selection;
\r
11046 DrawPosition(FALSE, boards[0]);
\r
11054 DropMenuEvent(selection, x, y)
\r
11055 ChessSquare selection;
\r
11058 ChessMove moveType;
\r
11060 switch (gameMode) {
\r
11061 case IcsPlayingWhite:
\r
11062 case MachinePlaysBlack:
\r
11063 if (!WhiteOnMove(currentMove)) {
\r
11064 DisplayMoveError(_("It is Black's turn"));
\r
11067 moveType = WhiteDrop;
\r
11069 case IcsPlayingBlack:
\r
11070 case MachinePlaysWhite:
\r
11071 if (WhiteOnMove(currentMove)) {
\r
11072 DisplayMoveError(_("It is White's turn"));
\r
11075 moveType = BlackDrop;
\r
11078 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11084 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11085 selection = (ChessSquare) ((int) selection
\r
11086 + (int) BlackPawn - (int) WhitePawn);
\r
11088 if (boards[currentMove][y][x] != EmptySquare) {
\r
11089 DisplayMoveError(_("That square is occupied"));
\r
11093 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11099 /* Accept a pending offer of any kind from opponent */
\r
11101 if (appData.icsActive) {
\r
11102 SendToICS(ics_prefix);
\r
11103 SendToICS("accept\n");
\r
11104 } else if (cmailMsgLoaded) {
\r
11105 if (currentMove == cmailOldMove &&
\r
11106 commentList[cmailOldMove] != NULL &&
\r
11107 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11108 "Black offers a draw" : "White offers a draw")) {
\r
11110 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11111 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11113 DisplayError(_("There is no pending offer on this move"), 0);
\r
11114 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11117 /* Not used for offers from chess program */
\r
11124 /* Decline a pending offer of any kind from opponent */
\r
11126 if (appData.icsActive) {
\r
11127 SendToICS(ics_prefix);
\r
11128 SendToICS("decline\n");
\r
11129 } else if (cmailMsgLoaded) {
\r
11130 if (currentMove == cmailOldMove &&
\r
11131 commentList[cmailOldMove] != NULL &&
\r
11132 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11133 "Black offers a draw" : "White offers a draw")) {
\r
11135 AppendComment(cmailOldMove, "Draw declined");
\r
11136 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11137 #endif /*NOTDEF*/
\r
11139 DisplayError(_("There is no pending offer on this move"), 0);
\r
11142 /* Not used for offers from chess program */
\r
11149 /* Issue ICS rematch command */
\r
11150 if (appData.icsActive) {
\r
11151 SendToICS(ics_prefix);
\r
11152 SendToICS("rematch\n");
\r
11159 /* Call your opponent's flag (claim a win on time) */
\r
11160 if (appData.icsActive) {
\r
11161 SendToICS(ics_prefix);
\r
11162 SendToICS("flag\n");
\r
11164 switch (gameMode) {
\r
11167 case MachinePlaysWhite:
\r
11170 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11173 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11175 DisplayError(_("Your opponent is not out of time"), 0);
\r
11178 case MachinePlaysBlack:
\r
11181 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11184 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11186 DisplayError(_("Your opponent is not out of time"), 0);
\r
11196 /* Offer draw or accept pending draw offer from opponent */
\r
11198 if (appData.icsActive) {
\r
11199 /* Note: tournament rules require draw offers to be
\r
11200 made after you make your move but before you punch
\r
11201 your clock. Currently ICS doesn't let you do that;
\r
11202 instead, you immediately punch your clock after making
\r
11203 a move, but you can offer a draw at any time. */
\r
11205 SendToICS(ics_prefix);
\r
11206 SendToICS("draw\n");
\r
11207 } else if (cmailMsgLoaded) {
\r
11208 if (currentMove == cmailOldMove &&
\r
11209 commentList[cmailOldMove] != NULL &&
\r
11210 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11211 "Black offers a draw" : "White offers a draw")) {
\r
11212 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11213 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11214 } else if (currentMove == cmailOldMove + 1) {
\r
11215 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11216 "White offers a draw" : "Black offers a draw";
\r
11217 AppendComment(currentMove, offer);
\r
11218 DisplayComment(currentMove - 1, offer);
\r
11219 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11221 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11222 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11224 } else if (first.offeredDraw) {
\r
11225 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11227 if (first.sendDrawOffers) {
\r
11228 SendToProgram("draw\n", &first);
\r
11229 userOfferedDraw = TRUE;
\r
11237 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11239 if (appData.icsActive) {
\r
11240 SendToICS(ics_prefix);
\r
11241 SendToICS("adjourn\n");
\r
11243 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11251 /* Offer Abort or accept pending Abort offer from opponent */
\r
11253 if (appData.icsActive) {
\r
11254 SendToICS(ics_prefix);
\r
11255 SendToICS("abort\n");
\r
11257 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11264 /* Resign. You can do this even if it's not your turn. */
\r
11266 if (appData.icsActive) {
\r
11267 SendToICS(ics_prefix);
\r
11268 SendToICS("resign\n");
\r
11270 switch (gameMode) {
\r
11271 case MachinePlaysWhite:
\r
11272 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11274 case MachinePlaysBlack:
\r
11275 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11278 if (cmailMsgLoaded) {
\r
11280 if (WhiteOnMove(cmailOldMove)) {
\r
11281 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11283 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11285 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11296 StopObservingEvent()
\r
11298 /* Stop observing current games */
\r
11299 SendToICS(ics_prefix);
\r
11300 SendToICS("unobserve\n");
\r
11304 StopExaminingEvent()
\r
11306 /* Stop observing current game */
\r
11307 SendToICS(ics_prefix);
\r
11308 SendToICS("unexamine\n");
\r
11312 ForwardInner(target)
\r
11317 if (appData.debugMode)
\r
11318 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11319 target, currentMove, forwardMostMove);
\r
11321 if (gameMode == EditPosition)
\r
11324 if (gameMode == PlayFromGameFile && !pausing)
\r
11327 if (gameMode == IcsExamining && pausing)
\r
11328 limit = pauseExamForwardMostMove;
\r
11330 limit = forwardMostMove;
\r
11332 if (target > limit) target = limit;
\r
11334 if (target > 0 && moveList[target - 1][0]) {
\r
11335 int fromX, fromY, toX, toY;
\r
11336 toX = moveList[target - 1][2] - AAA;
\r
11337 toY = moveList[target - 1][3] - ONE;
\r
11338 if (moveList[target - 1][1] == '@') {
\r
11339 if (appData.highlightLastMove) {
\r
11340 SetHighlights(-1, -1, toX, toY);
\r
11343 fromX = moveList[target - 1][0] - AAA;
\r
11344 fromY = moveList[target - 1][1] - ONE;
\r
11345 if (target == currentMove + 1) {
\r
11346 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11348 if (appData.highlightLastMove) {
\r
11349 SetHighlights(fromX, fromY, toX, toY);
\r
11353 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11354 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11355 gameMode == AnalyzeFile) {
\r
11356 while (currentMove < target) {
\r
11357 SendMoveToProgram(currentMove++, &first);
\r
11360 currentMove = target;
\r
11363 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11364 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11365 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11367 DisplayBothClocks();
\r
11368 DisplayMove(currentMove - 1);
\r
11369 DrawPosition(FALSE, boards[currentMove]);
\r
11370 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11371 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11372 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11380 if (gameMode == IcsExamining && !pausing) {
\r
11381 SendToICS(ics_prefix);
\r
11382 SendToICS("forward\n");
\r
11384 ForwardInner(currentMove + 1);
\r
11391 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11392 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11393 * the remaining moves to the engine. Otherwise we get analysis output
\r
11394 * after each move.
\r
11396 if (first.analysisSupport) {
\r
11397 SendToProgram("exit\nforce\n", &first);
\r
11398 first.analyzing = FALSE;
\r
11402 if (gameMode == IcsExamining && !pausing) {
\r
11403 SendToICS(ics_prefix);
\r
11404 SendToICS("forward 999999\n");
\r
11406 ForwardInner(forwardMostMove);
\r
11409 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11410 /* we have fed all the moves, so reactivate analysis mode */
\r
11411 SendToProgram("analyze\n", &first);
\r
11412 first.analyzing = TRUE;
\r
11413 /*first.maybeThinking = TRUE;*/
\r
11414 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11419 BackwardInner(target)
\r
11422 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11424 if (appData.debugMode)
\r
11425 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11426 target, currentMove, forwardMostMove);
\r
11428 if (gameMode == EditPosition) return;
\r
11429 if (currentMove <= backwardMostMove) {
\r
11430 ClearHighlights();
\r
11431 DrawPosition(full_redraw, boards[currentMove]);
\r
11434 if (gameMode == PlayFromGameFile && !pausing)
\r
11437 if (moveList[target][0]) {
\r
11438 int fromX, fromY, toX, toY;
\r
11439 toX = moveList[target][2] - AAA;
\r
11440 toY = moveList[target][3] - ONE;
\r
11441 if (moveList[target][1] == '@') {
\r
11442 if (appData.highlightLastMove) {
\r
11443 SetHighlights(-1, -1, toX, toY);
\r
11446 fromX = moveList[target][0] - AAA;
\r
11447 fromY = moveList[target][1] - ONE;
\r
11448 if (target == currentMove - 1) {
\r
11449 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11451 if (appData.highlightLastMove) {
\r
11452 SetHighlights(fromX, fromY, toX, toY);
\r
11456 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11457 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11458 while (currentMove > target) {
\r
11459 SendToProgram("undo\n", &first);
\r
11463 currentMove = target;
\r
11466 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11467 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11468 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11470 DisplayBothClocks();
\r
11471 DisplayMove(currentMove - 1);
\r
11472 DrawPosition(full_redraw, boards[currentMove]);
\r
11473 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11474 // [HGM] PV info: routine tests if comment empty
\r
11475 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11481 if (gameMode == IcsExamining && !pausing) {
\r
11482 SendToICS(ics_prefix);
\r
11483 SendToICS("backward\n");
\r
11485 BackwardInner(currentMove - 1);
\r
11492 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11493 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11494 * all the moves. Otherwise we get analysis output after each undo.
\r
11496 if (first.analysisSupport) {
\r
11497 SendToProgram("exit\nforce\n", &first);
\r
11498 first.analyzing = FALSE;
\r
11502 if (gameMode == IcsExamining && !pausing) {
\r
11503 SendToICS(ics_prefix);
\r
11504 SendToICS("backward 999999\n");
\r
11506 BackwardInner(backwardMostMove);
\r
11509 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11510 /* we have fed all the moves, so reactivate analysis mode */
\r
11511 SendToProgram("analyze\n", &first);
\r
11512 first.analyzing = TRUE;
\r
11513 /*first.maybeThinking = TRUE;*/
\r
11514 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11519 ToNrEvent(int to)
\r
11521 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11522 if (to >= forwardMostMove) to = forwardMostMove;
\r
11523 if (to <= backwardMostMove) to = backwardMostMove;
\r
11524 if (to < currentMove) {
\r
11525 BackwardInner(to);
\r
11527 ForwardInner(to);
\r
11534 if (gameMode != IcsExamining) {
\r
11535 DisplayError(_("You are not examining a game"), 0);
\r
11539 DisplayError(_("You can't revert while pausing"), 0);
\r
11542 SendToICS(ics_prefix);
\r
11543 SendToICS("revert\n");
\r
11547 RetractMoveEvent()
\r
11549 switch (gameMode) {
\r
11550 case MachinePlaysWhite:
\r
11551 case MachinePlaysBlack:
\r
11552 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11553 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11556 if (forwardMostMove < 2) return;
\r
11557 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11558 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11559 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11560 DisplayBothClocks();
\r
11561 DisplayMove(currentMove - 1);
\r
11562 ClearHighlights();/*!! could figure this out*/
\r
11563 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11564 SendToProgram("remove\n", &first);
\r
11565 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11568 case BeginningOfGame:
\r
11572 case IcsPlayingWhite:
\r
11573 case IcsPlayingBlack:
\r
11574 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11575 SendToICS(ics_prefix);
\r
11576 SendToICS("takeback 2\n");
\r
11578 SendToICS(ics_prefix);
\r
11579 SendToICS("takeback 1\n");
\r
11588 ChessProgramState *cps;
\r
11590 switch (gameMode) {
\r
11591 case MachinePlaysWhite:
\r
11592 if (!WhiteOnMove(forwardMostMove)) {
\r
11593 DisplayError(_("It is your turn"), 0);
\r
11598 case MachinePlaysBlack:
\r
11599 if (WhiteOnMove(forwardMostMove)) {
\r
11600 DisplayError(_("It is your turn"), 0);
\r
11605 case TwoMachinesPlay:
\r
11606 if (WhiteOnMove(forwardMostMove) ==
\r
11607 (first.twoMachinesColor[0] == 'w')) {
\r
11613 case BeginningOfGame:
\r
11617 SendToProgram("?\n", cps);
\r
11621 TruncateGameEvent()
\r
11624 if (gameMode != EditGame) return;
\r
11631 if (forwardMostMove > currentMove) {
\r
11632 if (gameInfo.resultDetails != NULL) {
\r
11633 free(gameInfo.resultDetails);
\r
11634 gameInfo.resultDetails = NULL;
\r
11635 gameInfo.result = GameUnfinished;
\r
11637 forwardMostMove = currentMove;
\r
11638 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11646 if (appData.noChessProgram) return;
\r
11647 switch (gameMode) {
\r
11648 case MachinePlaysWhite:
\r
11649 if (WhiteOnMove(forwardMostMove)) {
\r
11650 DisplayError(_("Wait until your turn"), 0);
\r
11654 case BeginningOfGame:
\r
11655 case MachinePlaysBlack:
\r
11656 if (!WhiteOnMove(forwardMostMove)) {
\r
11657 DisplayError(_("Wait until your turn"), 0);
\r
11662 DisplayError(_("No hint available"), 0);
\r
11665 SendToProgram("hint\n", &first);
\r
11666 hintRequested = TRUE;
\r
11672 if (appData.noChessProgram) return;
\r
11673 switch (gameMode) {
\r
11674 case MachinePlaysWhite:
\r
11675 if (WhiteOnMove(forwardMostMove)) {
\r
11676 DisplayError(_("Wait until your turn"), 0);
\r
11680 case BeginningOfGame:
\r
11681 case MachinePlaysBlack:
\r
11682 if (!WhiteOnMove(forwardMostMove)) {
\r
11683 DisplayError(_("Wait until your turn"), 0);
\r
11687 case EditPosition:
\r
11688 EditPositionDone();
\r
11690 case TwoMachinesPlay:
\r
11695 SendToProgram("bk\n", &first);
\r
11696 bookOutput[0] = NULLCHAR;
\r
11697 bookRequested = TRUE;
\r
11703 char *tags = PGNTags(&gameInfo);
\r
11704 TagsPopUp(tags, CmailMsg());
\r
11708 /* end button procedures */
\r
11711 PrintPosition(fp, move)
\r
11717 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11718 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11719 char c = PieceToChar(boards[move][i][j]);
\r
11720 fputc(c == 'x' ? '.' : c, fp);
\r
11721 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11724 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11725 fprintf(fp, "white to play\n");
\r
11727 fprintf(fp, "black to play\n");
\r
11731 PrintOpponents(fp)
\r
11734 if (gameInfo.white != NULL) {
\r
11735 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11737 fprintf(fp, "\n");
\r
11741 /* Find last component of program's own name, using some heuristics */
\r
11743 TidyProgramName(prog, host, buf)
\r
11744 char *prog, *host, buf[MSG_SIZ];
\r
11747 int local = (strcmp(host, "localhost") == 0);
\r
11748 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11750 while (*p == ' ') p++;
\r
11753 if (*prog == '"' || *prog == '\'') {
\r
11754 q = strchr(prog + 1, *prog);
\r
11756 q = strchr(prog, ' ');
\r
11758 if (q == NULL) q = prog + strlen(prog);
\r
11760 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11762 if(p == prog && *p == '"') p++;
\r
11763 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11764 memcpy(buf, p, q - p);
\r
11765 buf[q - p] = NULLCHAR;
\r
11767 strcat(buf, "@");
\r
11768 strcat(buf, host);
\r
11773 TimeControlTagValue()
\r
11775 char buf[MSG_SIZ];
\r
11776 if (!appData.clockMode) {
\r
11777 strcpy(buf, "-");
\r
11778 } else if (movesPerSession > 0) {
\r
11779 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11780 } else if (timeIncrement == 0) {
\r
11781 sprintf(buf, "%ld", timeControl/1000);
\r
11783 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11785 return StrSave(buf);
\r
11791 /* This routine is used only for certain modes */
\r
11792 VariantClass v = gameInfo.variant;
\r
11793 ClearGameInfo(&gameInfo);
\r
11794 gameInfo.variant = v;
\r
11796 switch (gameMode) {
\r
11797 case MachinePlaysWhite:
\r
11798 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11799 gameInfo.site = StrSave(HostName());
\r
11800 gameInfo.date = PGNDate();
\r
11801 gameInfo.round = StrSave("-");
\r
11802 gameInfo.white = StrSave(first.tidy);
\r
11803 gameInfo.black = StrSave(UserName());
\r
11804 gameInfo.timeControl = TimeControlTagValue();
\r
11807 case MachinePlaysBlack:
\r
11808 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11809 gameInfo.site = StrSave(HostName());
\r
11810 gameInfo.date = PGNDate();
\r
11811 gameInfo.round = StrSave("-");
\r
11812 gameInfo.white = StrSave(UserName());
\r
11813 gameInfo.black = StrSave(first.tidy);
\r
11814 gameInfo.timeControl = TimeControlTagValue();
\r
11817 case TwoMachinesPlay:
\r
11818 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11819 gameInfo.site = StrSave(HostName());
\r
11820 gameInfo.date = PGNDate();
\r
11821 if (matchGame > 0) {
\r
11822 char buf[MSG_SIZ];
\r
11823 sprintf(buf, "%d", matchGame);
\r
11824 gameInfo.round = StrSave(buf);
\r
11826 gameInfo.round = StrSave("-");
\r
11828 if (first.twoMachinesColor[0] == 'w') {
\r
11829 gameInfo.white = StrSave(first.tidy);
\r
11830 gameInfo.black = StrSave(second.tidy);
\r
11832 gameInfo.white = StrSave(second.tidy);
\r
11833 gameInfo.black = StrSave(first.tidy);
\r
11835 gameInfo.timeControl = TimeControlTagValue();
\r
11839 gameInfo.event = StrSave("Edited game");
\r
11840 gameInfo.site = StrSave(HostName());
\r
11841 gameInfo.date = PGNDate();
\r
11842 gameInfo.round = StrSave("-");
\r
11843 gameInfo.white = StrSave("-");
\r
11844 gameInfo.black = StrSave("-");
\r
11847 case EditPosition:
\r
11848 gameInfo.event = StrSave("Edited position");
\r
11849 gameInfo.site = StrSave(HostName());
\r
11850 gameInfo.date = PGNDate();
\r
11851 gameInfo.round = StrSave("-");
\r
11852 gameInfo.white = StrSave("-");
\r
11853 gameInfo.black = StrSave("-");
\r
11856 case IcsPlayingWhite:
\r
11857 case IcsPlayingBlack:
\r
11858 case IcsObserving:
\r
11859 case IcsExamining:
\r
11862 case PlayFromGameFile:
\r
11863 gameInfo.event = StrSave("Game from non-PGN file");
\r
11864 gameInfo.site = StrSave(HostName());
\r
11865 gameInfo.date = PGNDate();
\r
11866 gameInfo.round = StrSave("-");
\r
11867 gameInfo.white = StrSave("?");
\r
11868 gameInfo.black = StrSave("?");
\r
11877 ReplaceComment(index, text)
\r
11883 while (*text == '\n') text++;
\r
11884 len = strlen(text);
\r
11885 while (len > 0 && text[len - 1] == '\n') len--;
\r
11887 if (commentList[index] != NULL)
\r
11888 free(commentList[index]);
\r
11891 commentList[index] = NULL;
\r
11894 commentList[index] = (char *) malloc(len + 2);
\r
11895 strncpy(commentList[index], text, len);
\r
11896 commentList[index][len] = '\n';
\r
11897 commentList[index][len + 1] = NULLCHAR;
\r
11910 if (ch == '\r') continue;
\r
11912 } while (ch != '\0');
\r
11916 AppendComment(index, text)
\r
11923 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11926 while (*text == '\n') text++;
\r
11927 len = strlen(text);
\r
11928 while (len > 0 && text[len - 1] == '\n') len--;
\r
11930 if (len == 0) return;
\r
11932 if (commentList[index] != NULL) {
\r
11933 old = commentList[index];
\r
11934 oldlen = strlen(old);
\r
11935 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11936 strcpy(commentList[index], old);
\r
11938 strncpy(&commentList[index][oldlen], text, len);
\r
11939 commentList[index][oldlen + len] = '\n';
\r
11940 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11942 commentList[index] = (char *) malloc(len + 2);
\r
11943 strncpy(commentList[index], text, len);
\r
11944 commentList[index][len] = '\n';
\r
11945 commentList[index][len + 1] = NULLCHAR;
\r
11949 static char * FindStr( char * text, char * sub_text )
\r
11951 char * result = strstr( text, sub_text );
\r
11953 if( result != NULL ) {
\r
11954 result += strlen( sub_text );
\r
11960 /* [AS] Try to extract PV info from PGN comment */
\r
11961 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11962 char *GetInfoFromComment( int index, char * text )
\r
11964 char * sep = text;
\r
11966 if( text != NULL && index > 0 ) {
\r
11969 int time = -1, sec = 0, deci;
\r
11970 char * s_eval = FindStr( text, "[%eval " );
\r
11971 char * s_emt = FindStr( text, "[%emt " );
\r
11973 if( s_eval != NULL || s_emt != NULL ) {
\r
11977 if( s_eval != NULL ) {
\r
11978 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11982 if( delim != ']' ) {
\r
11987 if( s_emt != NULL ) {
\r
11991 /* We expect something like: [+|-]nnn.nn/dd */
\r
11992 int score_lo = 0;
\r
11994 sep = strchr( text, '/' );
\r
11995 if( sep == NULL || sep < (text+4) ) {
\r
11999 time = -1; sec = -1; deci = -1;
\r
12000 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12001 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12002 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12003 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12007 if( score_lo < 0 || score_lo >= 100 ) {
\r
12011 if(sec >= 0) time = 600*time + 10*sec; else
\r
12012 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12014 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12016 /* [HGM] PV time: now locate end of PV info */
\r
12017 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12019 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12021 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12023 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12024 while(*sep == ' ') sep++;
\r
12027 if( depth <= 0 ) {
\r
12035 pvInfoList[index-1].depth = depth;
\r
12036 pvInfoList[index-1].score = score;
\r
12037 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12043 SendToProgram(message, cps)
\r
12045 ChessProgramState *cps;
\r
12047 int count, outCount, error;
\r
12048 char buf[MSG_SIZ];
\r
12050 if (cps->pr == NULL) return;
\r
12053 if (appData.debugMode) {
\r
12055 GetTimeMark(&now);
\r
12056 fprintf(debugFP, "%ld >%-6s: %s",
\r
12057 SubtractTimeMarks(&now, &programStartTime),
\r
12058 cps->which, message);
\r
12061 count = strlen(message);
\r
12062 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12063 if (outCount < count && !exiting
\r
12064 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12065 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12066 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12067 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12068 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12069 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12071 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12073 gameInfo.resultDetails = buf;
\r
12075 DisplayFatalError(buf, error, 1);
\r
12080 ReceiveFromProgram(isr, closure, message, count, error)
\r
12081 InputSourceRef isr;
\r
12082 VOIDSTAR closure;
\r
12088 char buf[MSG_SIZ];
\r
12089 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12091 if (isr != cps->isr) return; /* Killed intentionally */
\r
12092 if (count <= 0) {
\r
12093 if (count == 0) {
\r
12095 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12096 cps->which, cps->program);
\r
12097 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12098 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12099 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12100 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12102 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12104 gameInfo.resultDetails = buf;
\r
12106 RemoveInputSource(cps->isr);
\r
12107 DisplayFatalError(buf, 0, 1);
\r
12110 _("Error reading from %s chess program (%s)"),
\r
12111 cps->which, cps->program);
\r
12112 RemoveInputSource(cps->isr);
\r
12114 /* [AS] Program is misbehaving badly... kill it */
\r
12115 if( count == -2 ) {
\r
12116 DestroyChildProcess( cps->pr, 9 );
\r
12117 cps->pr = NoProc;
\r
12120 DisplayFatalError(buf, error, 1);
\r
12125 if ((end_str = strchr(message, '\r')) != NULL)
\r
12126 *end_str = NULLCHAR;
\r
12127 if ((end_str = strchr(message, '\n')) != NULL)
\r
12128 *end_str = NULLCHAR;
\r
12130 if (appData.debugMode) {
\r
12131 TimeMark now; int print = 1;
\r
12132 char *quote = ""; char c; int i;
\r
12134 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12135 char start = message[0];
\r
12136 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12137 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12138 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12139 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12140 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12141 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12142 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12143 { quote = "# "; print = (appData.engineComments == 2); }
\r
12144 message[0] = start; // restore original message
\r
12147 GetTimeMark(&now);
\r
12148 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12149 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12155 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12156 if (appData.icsEngineAnalyze) {
\r
12157 if (strstr(message, "whisper") != NULL ||
\r
12158 strstr(message, "kibitz") != NULL ||
\r
12159 strstr(message, "tellics") != NULL) return;
\r
12162 HandleMachineMove(message, cps);
\r
12167 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12168 ChessProgramState *cps;
\r
12169 int mps, inc, sd, st;
\r
12172 char buf[MSG_SIZ];
\r
12175 if( timeControl_2 > 0 ) {
\r
12176 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12177 tc = timeControl_2;
\r
12180 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12181 inc /= cps->timeOdds;
\r
12182 st /= cps->timeOdds;
\r
12184 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12187 /* Set exact time per move, normally using st command */
\r
12188 if (cps->stKludge) {
\r
12189 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12190 seconds = st % 60;
\r
12191 if (seconds == 0) {
\r
12192 sprintf(buf, "level 1 %d\n", st/60);
\r
12194 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12197 sprintf(buf, "st %d\n", st);
\r
12200 /* Set conventional or incremental time control, using level command */
\r
12201 if (seconds == 0) {
\r
12202 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12203 Fixed in later versions, but still avoid :seconds
\r
12204 when seconds is 0. */
\r
12205 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12207 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12208 seconds, inc/1000);
\r
12211 SendToProgram(buf, cps);
\r
12213 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12214 /* Orthogonally, limit search to given depth */
\r
12216 if (cps->sdKludge) {
\r
12217 sprintf(buf, "depth\n%d\n", sd);
\r
12219 sprintf(buf, "sd %d\n", sd);
\r
12221 SendToProgram(buf, cps);
\r
12224 if(cps->nps > 0) { /* [HGM] nps */
\r
12225 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12227 sprintf(buf, "nps %d\n", cps->nps);
\r
12228 SendToProgram(buf, cps);
\r
12233 ChessProgramState *WhitePlayer()
\r
12234 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12236 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12237 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12243 SendTimeRemaining(cps, machineWhite)
\r
12244 ChessProgramState *cps;
\r
12245 int /*boolean*/ machineWhite;
\r
12247 char message[MSG_SIZ];
\r
12248 long time, otime;
\r
12250 /* Note: this routine must be called when the clocks are stopped
\r
12251 or when they have *just* been set or switched; otherwise
\r
12252 it will be off by the time since the current tick started.
\r
12254 if (machineWhite) {
\r
12255 time = whiteTimeRemaining / 10;
\r
12256 otime = blackTimeRemaining / 10;
\r
12258 time = blackTimeRemaining / 10;
\r
12259 otime = whiteTimeRemaining / 10;
\r
12261 /* [HGM] translate opponent's time by time-odds factor */
\r
12262 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12263 if (appData.debugMode) {
\r
12264 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12267 if (time <= 0) time = 1;
\r
12268 if (otime <= 0) otime = 1;
\r
12270 sprintf(message, "time %ld\n", time);
\r
12271 SendToProgram(message, cps);
\r
12273 sprintf(message, "otim %ld\n", otime);
\r
12274 SendToProgram(message, cps);
\r
12278 BoolFeature(p, name, loc, cps)
\r
12282 ChessProgramState *cps;
\r
12284 char buf[MSG_SIZ];
\r
12285 int len = strlen(name);
\r
12287 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12289 sscanf(*p, "%d", &val);
\r
12290 *loc = (val != 0);
\r
12291 while (**p && **p != ' ') (*p)++;
\r
12292 sprintf(buf, "accepted %s\n", name);
\r
12293 SendToProgram(buf, cps);
\r
12300 IntFeature(p, name, loc, cps)
\r
12304 ChessProgramState *cps;
\r
12306 char buf[MSG_SIZ];
\r
12307 int len = strlen(name);
\r
12308 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12310 sscanf(*p, "%d", loc);
\r
12311 while (**p && **p != ' ') (*p)++;
\r
12312 sprintf(buf, "accepted %s\n", name);
\r
12313 SendToProgram(buf, cps);
\r
12320 StringFeature(p, name, loc, cps)
\r
12324 ChessProgramState *cps;
\r
12326 char buf[MSG_SIZ];
\r
12327 int len = strlen(name);
\r
12328 if (strncmp((*p), name, len) == 0
\r
12329 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12331 sscanf(*p, "%[^\"]", loc);
\r
12332 while (**p && **p != '\"') (*p)++;
\r
12333 if (**p == '\"') (*p)++;
\r
12334 sprintf(buf, "accepted %s\n", name);
\r
12335 SendToProgram(buf, cps);
\r
12342 ParseOption(Option *opt, ChessProgramState *cps)
\r
12343 // [HGM] options: process the string that defines an engine option, and determine
\r
12344 // name, type, default value, and allowed value range
\r
12346 char *p, *q, buf[MSG_SIZ];
\r
12347 int n, min = (-1)<<31, max = 1<<31, def;
\r
12349 if(p = strstr(opt->name, " -spin ")) {
\r
12350 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12351 if(max < min) max = min; // enforce consistency
\r
12352 if(def < min) def = min;
\r
12353 if(def > max) def = max;
\r
12354 opt->value = def;
\r
12357 opt->type = Spin;
\r
12358 } else if(p = strstr(opt->name, " -string ")) {
\r
12359 opt->textValue = p+9;
\r
12360 opt->type = TextBox;
\r
12361 } else if(p = strstr(opt->name, " -check ")) {
\r
12362 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12363 opt->value = (def != 0);
\r
12364 opt->type = CheckBox;
\r
12365 } else if(p = strstr(opt->name, " -combo ")) {
\r
12366 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12367 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12368 opt->value = n = 0;
\r
12369 while(q = StrStr(q, " /// ")) {
\r
12370 n++; *q = 0; // count choices, and null-terminate each of them
\r
12372 if(*q == '*') { // remember default, which is marked with * prefix
\r
12376 cps->comboList[cps->comboCnt++] = q;
\r
12378 cps->comboList[cps->comboCnt++] = NULL;
\r
12379 opt->max = n + 1;
\r
12380 opt->type = ComboBox;
\r
12381 } else if(p = strstr(opt->name, " -button")) {
\r
12382 opt->type = Button;
\r
12383 } else if(p = strstr(opt->name, " -save")) {
\r
12384 opt->type = SaveButton;
\r
12385 } else return FALSE;
\r
12386 *p = 0; // terminate option name
\r
12387 // now look if the command-line options define a setting for this engine option.
\r
12388 p = strstr(cps->optionSettings, opt->name);
\r
12389 if(p == cps->optionSettings || p[-1] == ',') {
\r
12390 sprintf(buf, "option %s", p);
\r
12391 if(p = strstr(buf, ",")) *p = 0;
\r
12392 strcat(buf, "\n");
\r
12393 SendToProgram(buf, cps);
\r
12399 FeatureDone(cps, val)
\r
12400 ChessProgramState* cps;
\r
12403 DelayedEventCallback cb = GetDelayedEvent();
\r
12404 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12405 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12406 CancelDelayedEvent();
\r
12407 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12409 cps->initDone = val;
\r
12412 /* Parse feature command from engine */
\r
12414 ParseFeatures(args, cps)
\r
12416 ChessProgramState *cps;
\r
12421 char buf[MSG_SIZ];
\r
12424 while (*p == ' ') p++;
\r
12425 if (*p == NULLCHAR) return;
\r
12427 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12428 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12429 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12430 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12431 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12432 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12433 /* Engine can disable reuse, but can't enable it if user said no */
\r
12434 if (!val) cps->reuse = FALSE;
\r
12437 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12438 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12439 if (gameMode == TwoMachinesPlay) {
\r
12440 DisplayTwoMachinesTitle();
\r
12442 DisplayTitle("");
\r
12446 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12447 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12448 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12449 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12450 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12451 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12452 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12453 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12454 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12455 if (IntFeature(&p, "done", &val, cps)) {
\r
12456 FeatureDone(cps, val);
\r
12459 /* Added by Tord: */
\r
12460 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12461 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12462 /* End of additions by Tord */
\r
12464 /* [HGM] added features: */
\r
12465 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12466 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12467 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12468 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12469 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12470 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12471 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12472 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12473 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12474 cps->nrOptions--;
\r
12475 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12476 DisplayError(buf, 0);
\r
12480 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12481 /* End of additions by HGM */
\r
12483 /* unknown feature: complain and skip */
\r
12485 while (*q && *q != '=') q++;
\r
12486 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12487 SendToProgram(buf, cps);
\r
12491 if (*p == '\"') {
\r
12493 while (*p && *p != '\"') p++;
\r
12494 if (*p == '\"') p++;
\r
12496 while (*p && *p != ' ') p++;
\r
12504 PeriodicUpdatesEvent(newState)
\r
12507 if (newState == appData.periodicUpdates)
\r
12510 appData.periodicUpdates=newState;
\r
12512 /* Display type changes, so update it now */
\r
12513 DisplayAnalysis();
\r
12515 /* Get the ball rolling again... */
\r
12517 AnalysisPeriodicEvent(1);
\r
12518 StartAnalysisClock();
\r
12523 PonderNextMoveEvent(newState)
\r
12526 if (newState == appData.ponderNextMove) return;
\r
12527 if (gameMode == EditPosition) EditPositionDone();
\r
12529 SendToProgram("hard\n", &first);
\r
12530 if (gameMode == TwoMachinesPlay) {
\r
12531 SendToProgram("hard\n", &second);
\r
12534 SendToProgram("easy\n", &first);
\r
12535 thinkOutput[0] = NULLCHAR;
\r
12536 if (gameMode == TwoMachinesPlay) {
\r
12537 SendToProgram("easy\n", &second);
\r
12540 appData.ponderNextMove = newState;
\r
12544 NewSettingEvent(option, command, value)
\r
12546 int option, value;
\r
12548 char buf[MSG_SIZ];
\r
12550 if (gameMode == EditPosition) EditPositionDone();
\r
12551 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12552 SendToProgram(buf, &first);
\r
12553 if (gameMode == TwoMachinesPlay) {
\r
12554 SendToProgram(buf, &second);
\r
12559 ShowThinkingEvent()
\r
12560 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12562 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12563 int newState = appData.showThinking
\r
12564 // [HGM] thinking: other features now need thinking output as well
\r
12565 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12567 if (oldState == newState) return;
\r
12568 oldState = newState;
\r
12569 if (gameMode == EditPosition) EditPositionDone();
\r
12571 SendToProgram("post\n", &first);
\r
12572 if (gameMode == TwoMachinesPlay) {
\r
12573 SendToProgram("post\n", &second);
\r
12576 SendToProgram("nopost\n", &first);
\r
12577 thinkOutput[0] = NULLCHAR;
\r
12578 if (gameMode == TwoMachinesPlay) {
\r
12579 SendToProgram("nopost\n", &second);
\r
12582 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12586 AskQuestionEvent(title, question, replyPrefix, which)
\r
12587 char *title; char *question; char *replyPrefix; char *which;
\r
12589 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12590 if (pr == NoProc) return;
\r
12591 AskQuestion(title, question, replyPrefix, pr);
\r
12595 DisplayMove(moveNumber)
\r
12598 char message[MSG_SIZ];
\r
12599 char res[MSG_SIZ];
\r
12600 char cpThinkOutput[MSG_SIZ];
\r
12602 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12604 if (moveNumber == forwardMostMove - 1 ||
\r
12605 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12607 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12609 if (strchr(cpThinkOutput, '\n')) {
\r
12610 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12613 *cpThinkOutput = NULLCHAR;
\r
12616 /* [AS] Hide thinking from human user */
\r
12617 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12618 *cpThinkOutput = NULLCHAR;
\r
12619 if( thinkOutput[0] != NULLCHAR ) {
\r
12622 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12623 cpThinkOutput[i] = '.';
\r
12625 cpThinkOutput[i] = NULLCHAR;
\r
12626 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12630 if (moveNumber == forwardMostMove - 1 &&
\r
12631 gameInfo.resultDetails != NULL) {
\r
12632 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12633 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12635 sprintf(res, " {%s} %s",
\r
12636 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12639 res[0] = NULLCHAR;
\r
12642 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12643 DisplayMessage(res, cpThinkOutput);
\r
12645 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12646 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12647 parseList[moveNumber], res);
\r
12648 DisplayMessage(message, cpThinkOutput);
\r
12653 DisplayAnalysisText(text)
\r
12656 char buf[MSG_SIZ];
\r
12658 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12659 || appData.icsEngineAnalyze) {
\r
12660 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12661 AnalysisPopUp(buf, text);
\r
12666 only_one_move(str)
\r
12669 while (*str && isspace(*str)) ++str;
\r
12670 while (*str && !isspace(*str)) ++str;
\r
12671 if (!*str) return 1;
\r
12672 while (*str && isspace(*str)) ++str;
\r
12673 if (!*str) return 1;
\r
12678 DisplayAnalysis()
\r
12680 char buf[MSG_SIZ];
\r
12681 char lst[MSG_SIZ / 2];
\r
12683 static char *xtra[] = { "", " (--)", " (++)" };
\r
12686 if (programStats.time == 0) {
\r
12687 programStats.time = 1;
\r
12690 if (programStats.got_only_move) {
\r
12691 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12693 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12695 nps = (u64ToDouble(programStats.nodes) /
\r
12696 ((double)programStats.time /100.0));
\r
12698 cs = programStats.time % 100;
\r
12699 s = programStats.time / 100;
\r
12700 h = (s / (60*60));
\r
12705 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12706 if (programStats.move_name[0] != NULLCHAR) {
\r
12707 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12708 programStats.depth,
\r
12709 programStats.nr_moves-programStats.moves_left,
\r
12710 programStats.nr_moves, programStats.move_name,
\r
12711 ((float)programStats.score)/100.0, lst,
\r
12712 only_one_move(lst)?
\r
12713 xtra[programStats.got_fail] : "",
\r
12714 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12716 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12717 programStats.depth,
\r
12718 programStats.nr_moves-programStats.moves_left,
\r
12719 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12721 only_one_move(lst)?
\r
12722 xtra[programStats.got_fail] : "",
\r
12723 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12726 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12727 programStats.depth,
\r
12728 ((float)programStats.score)/100.0,
\r
12730 only_one_move(lst)?
\r
12731 xtra[programStats.got_fail] : "",
\r
12732 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12735 DisplayAnalysisText(buf);
\r
12739 DisplayComment(moveNumber, text)
\r
12743 char title[MSG_SIZ];
\r
12744 char buf[8000]; // comment can be long!
\r
12745 int score, depth;
\r
12747 if( appData.autoDisplayComment ) {
\r
12748 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12749 strcpy(title, "Comment");
\r
12751 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12752 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12753 parseList[moveNumber]);
\r
12755 } else title[0] = 0;
\r
12757 // [HGM] PV info: display PV info together with (or as) comment
\r
12758 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12759 if(text == NULL) text = "";
\r
12760 score = pvInfoList[moveNumber].score;
\r
12761 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12762 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12763 CommentPopUp(title, buf);
\r
12765 if (text != NULL)
\r
12766 CommentPopUp(title, text);
\r
12769 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12770 * might be busy thinking or pondering. It can be omitted if your
\r
12771 * gnuchess is configured to stop thinking immediately on any user
\r
12772 * input. However, that gnuchess feature depends on the FIONREAD
\r
12773 * ioctl, which does not work properly on some flavors of Unix.
\r
12777 ChessProgramState *cps;
\r
12780 if (!cps->useSigint) return;
\r
12781 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12782 switch (gameMode) {
\r
12783 case MachinePlaysWhite:
\r
12784 case MachinePlaysBlack:
\r
12785 case TwoMachinesPlay:
\r
12786 case IcsPlayingWhite:
\r
12787 case IcsPlayingBlack:
\r
12788 case AnalyzeMode:
\r
12789 case AnalyzeFile:
\r
12790 /* Skip if we know it isn't thinking */
\r
12791 if (!cps->maybeThinking) return;
\r
12792 if (appData.debugMode)
\r
12793 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12794 InterruptChildProcess(cps->pr);
\r
12795 cps->maybeThinking = FALSE;
\r
12800 #endif /*ATTENTION*/
\r
12806 if (whiteTimeRemaining <= 0) {
\r
12807 if (!whiteFlag) {
\r
12808 whiteFlag = TRUE;
\r
12809 if (appData.icsActive) {
\r
12810 if (appData.autoCallFlag &&
\r
12811 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12812 SendToICS(ics_prefix);
\r
12813 SendToICS("flag\n");
\r
12817 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12819 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12820 if (appData.autoCallFlag) {
\r
12821 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12828 if (blackTimeRemaining <= 0) {
\r
12829 if (!blackFlag) {
\r
12830 blackFlag = TRUE;
\r
12831 if (appData.icsActive) {
\r
12832 if (appData.autoCallFlag &&
\r
12833 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12834 SendToICS(ics_prefix);
\r
12835 SendToICS("flag\n");
\r
12839 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12841 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12842 if (appData.autoCallFlag) {
\r
12843 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12854 CheckTimeControl()
\r
12856 if (!appData.clockMode || appData.icsActive ||
\r
12857 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12860 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12862 if ( !WhiteOnMove(forwardMostMove) )
\r
12863 /* White made time control */
\r
12864 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12865 /* [HGM] time odds: correct new time quota for time odds! */
\r
12866 / WhitePlayer()->timeOdds;
\r
12868 /* Black made time control */
\r
12869 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12870 / WhitePlayer()->other->timeOdds;
\r
12874 DisplayBothClocks()
\r
12876 int wom = gameMode == EditPosition ?
\r
12877 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12878 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12879 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12883 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12884 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12885 to use other calls if you don't. Clocks will be less accurate if
\r
12886 you have neither ftime nor gettimeofday.
\r
12889 /* Get the current time as a TimeMark */
\r
12894 #if HAVE_GETTIMEOFDAY
\r
12896 struct timeval timeVal;
\r
12897 struct timezone timeZone;
\r
12899 gettimeofday(&timeVal, &timeZone);
\r
12900 tm->sec = (long) timeVal.tv_sec;
\r
12901 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12903 #else /*!HAVE_GETTIMEOFDAY*/
\r
12906 #include <sys/timeb.h>
\r
12907 struct timeb timeB;
\r
12910 tm->sec = (long) timeB.time;
\r
12911 tm->ms = (int) timeB.millitm;
\r
12913 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12914 tm->sec = (long) time(NULL);
\r
12920 /* Return the difference in milliseconds between two
\r
12921 time marks. We assume the difference will fit in a long!
\r
12924 SubtractTimeMarks(tm2, tm1)
\r
12925 TimeMark *tm2, *tm1;
\r
12927 return 1000L*(tm2->sec - tm1->sec) +
\r
12928 (long) (tm2->ms - tm1->ms);
\r
12933 * Code to manage the game clocks.
\r
12935 * In tournament play, black starts the clock and then white makes a move.
\r
12936 * We give the human user a slight advantage if he is playing white---the
\r
12937 * clocks don't run until he makes his first move, so it takes zero time.
\r
12938 * Also, we don't account for network lag, so we could get out of sync
\r
12939 * with GNU Chess's clock -- but then, referees are always right.
\r
12942 static TimeMark tickStartTM;
\r
12943 static long intendedTickLength;
\r
12946 NextTickLength(timeRemaining)
\r
12947 long timeRemaining;
\r
12949 long nominalTickLength, nextTickLength;
\r
12951 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12952 nominalTickLength = 100L;
\r
12954 nominalTickLength = 1000L;
\r
12955 nextTickLength = timeRemaining % nominalTickLength;
\r
12956 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12958 return nextTickLength;
\r
12961 /* Adjust clock one minute up or down */
\r
12963 AdjustClock(Boolean which, int dir)
\r
12965 if(which) blackTimeRemaining += 60000*dir;
\r
12966 else whiteTimeRemaining += 60000*dir;
\r
12967 DisplayBothClocks();
\r
12970 /* Stop clocks and reset to a fresh time control */
\r
12974 (void) StopClockTimer();
\r
12975 if (appData.icsActive) {
\r
12976 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12977 } else { /* [HGM] correct new time quote for time odds */
\r
12978 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12979 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12981 if (whiteFlag || blackFlag) {
\r
12982 DisplayTitle("");
\r
12983 whiteFlag = blackFlag = FALSE;
\r
12985 DisplayBothClocks();
\r
12988 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12990 /* Decrement running clock by amount of time that has passed */
\r
12992 DecrementClocks()
\r
12994 long timeRemaining;
\r
12995 long lastTickLength, fudge;
\r
12998 if (!appData.clockMode) return;
\r
12999 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13001 GetTimeMark(&now);
\r
13003 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13005 /* Fudge if we woke up a little too soon */
\r
13006 fudge = intendedTickLength - lastTickLength;
\r
13007 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13009 if (WhiteOnMove(forwardMostMove)) {
\r
13010 if(whiteNPS >= 0) lastTickLength = 0;
\r
13011 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13012 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13013 WhiteOnMove(currentMove));
\r
13015 if(blackNPS >= 0) lastTickLength = 0;
\r
13016 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13017 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13018 !WhiteOnMove(currentMove));
\r
13021 if (CheckFlags()) return;
\r
13023 tickStartTM = now;
\r
13024 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13025 StartClockTimer(intendedTickLength);
\r
13027 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13028 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13029 * with increment) the time remaining has increased to a level above the
\r
13030 * threshold, reset the alarm so it can sound again.
\r
13033 if (appData.icsActive && appData.icsAlarm) {
\r
13035 /* make sure we are dealing with the user's clock */
\r
13036 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13037 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13040 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13041 alarmSounded = FALSE;
\r
13042 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13043 PlayAlarmSound();
\r
13044 alarmSounded = TRUE;
\r
13050 /* A player has just moved, so stop the previously running
\r
13051 clock and (if in clock mode) start the other one.
\r
13052 We redisplay both clocks in case we're in ICS mode, because
\r
13053 ICS gives us an update to both clocks after every move.
\r
13054 Note that this routine is called *after* forwardMostMove
\r
13055 is updated, so the last fractional tick must be subtracted
\r
13056 from the color that is *not* on move now.
\r
13061 long lastTickLength;
\r
13063 int flagged = FALSE;
\r
13065 GetTimeMark(&now);
\r
13067 if (StopClockTimer() && appData.clockMode) {
\r
13068 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13069 if (WhiteOnMove(forwardMostMove)) {
\r
13070 if(blackNPS >= 0) lastTickLength = 0;
\r
13071 blackTimeRemaining -= lastTickLength;
\r
13072 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13073 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13074 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13075 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13077 if(whiteNPS >= 0) lastTickLength = 0;
\r
13078 whiteTimeRemaining -= lastTickLength;
\r
13079 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13080 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13081 pvInfoList[forwardMostMove-1].time =
\r
13082 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13084 flagged = CheckFlags();
\r
13086 CheckTimeControl();
\r
13088 if (flagged || !appData.clockMode) return;
\r
13090 switch (gameMode) {
\r
13091 case MachinePlaysBlack:
\r
13092 case MachinePlaysWhite:
\r
13093 case BeginningOfGame:
\r
13094 if (pausing) return;
\r
13098 case PlayFromGameFile:
\r
13099 case IcsExamining:
\r
13106 tickStartTM = now;
\r
13107 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13108 whiteTimeRemaining : blackTimeRemaining);
\r
13109 StartClockTimer(intendedTickLength);
\r
13113 /* Stop both clocks */
\r
13117 long lastTickLength;
\r
13120 if (!StopClockTimer()) return;
\r
13121 if (!appData.clockMode) return;
\r
13123 GetTimeMark(&now);
\r
13125 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13126 if (WhiteOnMove(forwardMostMove)) {
\r
13127 if(whiteNPS >= 0) lastTickLength = 0;
\r
13128 whiteTimeRemaining -= lastTickLength;
\r
13129 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13131 if(blackNPS >= 0) lastTickLength = 0;
\r
13132 blackTimeRemaining -= lastTickLength;
\r
13133 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13138 /* Start clock of player on move. Time may have been reset, so
\r
13139 if clock is already running, stop and restart it. */
\r
13143 (void) StopClockTimer(); /* in case it was running already */
\r
13144 DisplayBothClocks();
\r
13145 if (CheckFlags()) return;
\r
13147 if (!appData.clockMode) return;
\r
13148 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13150 GetTimeMark(&tickStartTM);
\r
13151 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13152 whiteTimeRemaining : blackTimeRemaining);
\r
13154 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13155 whiteNPS = blackNPS = -1;
\r
13156 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13157 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13158 whiteNPS = first.nps;
\r
13159 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13160 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13161 blackNPS = first.nps;
\r
13162 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13163 whiteNPS = second.nps;
\r
13164 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13165 blackNPS = second.nps;
\r
13166 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13168 StartClockTimer(intendedTickLength);
\r
13175 long second, minute, hour, day;
\r
13177 static char buf[32];
\r
13179 if (ms > 0 && ms <= 9900) {
\r
13180 /* convert milliseconds to tenths, rounding up */
\r
13181 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13183 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13187 /* convert milliseconds to seconds, rounding up */
\r
13188 /* use floating point to avoid strangeness of integer division
\r
13189 with negative dividends on many machines */
\r
13190 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13192 if (second < 0) {
\r
13194 second = -second;
\r
13197 day = second / (60 * 60 * 24);
\r
13198 second = second % (60 * 60 * 24);
\r
13199 hour = second / (60 * 60);
\r
13200 second = second % (60 * 60);
\r
13201 minute = second / 60;
\r
13202 second = second % 60;
\r
13205 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13206 sign, day, hour, minute, second);
\r
13207 else if (hour > 0)
\r
13208 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13210 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13217 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13220 StrStr(string, match)
\r
13221 char *string, *match;
\r
13225 length = strlen(match);
\r
13227 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13228 if (!strncmp(match, string, length))
\r
13235 StrCaseStr(string, match)
\r
13236 char *string, *match;
\r
13238 int i, j, length;
\r
13240 length = strlen(match);
\r
13242 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13243 for (j = 0; j < length; j++) {
\r
13244 if (ToLower(match[j]) != ToLower(string[j]))
\r
13247 if (j == length) return string;
\r
13253 #ifndef _amigados
\r
13255 StrCaseCmp(s1, s2)
\r
13261 c1 = ToLower(*s1++);
\r
13262 c2 = ToLower(*s2++);
\r
13263 if (c1 > c2) return 1;
\r
13264 if (c1 < c2) return -1;
\r
13265 if (c1 == NULLCHAR) return 0;
\r
13274 return isupper(c) ? tolower(c) : c;
\r
13282 return islower(c) ? toupper(c) : c;
\r
13284 #endif /* !_amigados */
\r
13292 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13299 StrSavePtr(s, savePtr)
\r
13300 char *s, **savePtr;
\r
13305 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13306 strcpy(*savePtr, s);
\r
13308 return(*savePtr);
\r
13316 char buf[MSG_SIZ];
\r
13318 clock = time((time_t *)NULL);
\r
13319 tm = localtime(&clock);
\r
13320 sprintf(buf, "%04d.%02d.%02d",
\r
13321 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13322 return StrSave(buf);
\r
13327 PositionToFEN(move, useFEN960)
\r
13331 int i, j, fromX, fromY, toX, toY;
\r
13336 ChessSquare piece;
\r
13338 whiteToPlay = (gameMode == EditPosition) ?
\r
13339 !blackPlaysFirst : (move % 2 == 0);
\r
13342 /* Piece placement data */
\r
13343 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13345 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13346 if (boards[move][i][j] == EmptySquare) {
\r
13348 } else { ChessSquare piece = boards[move][i][j];
\r
13349 if (emptycount > 0) {
\r
13350 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13351 *p++ = '0' + emptycount;
\r
13352 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13355 if(PieceToChar(piece) == '+') {
\r
13356 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13358 piece = (ChessSquare)(DEMOTED piece);
\r
13360 *p++ = PieceToChar(piece);
\r
13361 if(p[-1] == '~') {
\r
13362 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13363 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13368 if (emptycount > 0) {
\r
13369 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13370 *p++ = '0' + emptycount;
\r
13371 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13378 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13379 if( gameInfo.holdingsWidth ) {
\r
13380 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13382 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13383 piece = boards[move][i][BOARD_WIDTH-1];
\r
13384 if( piece != EmptySquare )
\r
13385 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13386 *p++ = PieceToChar(piece);
\r
13388 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13389 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13390 if( piece != EmptySquare )
\r
13391 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13392 *p++ = PieceToChar(piece);
\r
13395 if( q == p ) *p++ = '-';
\r
13400 /* Active color */
\r
13401 *p++ = whiteToPlay ? 'w' : 'b';
\r
13404 if(nrCastlingRights) {
\r
13406 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13407 /* [HGM] write directly from rights */
\r
13408 if(castlingRights[move][2] >= 0 &&
\r
13409 castlingRights[move][0] >= 0 )
\r
13410 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13411 if(castlingRights[move][2] >= 0 &&
\r
13412 castlingRights[move][1] >= 0 )
\r
13413 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13414 if(castlingRights[move][5] >= 0 &&
\r
13415 castlingRights[move][3] >= 0 )
\r
13416 *p++ = castlingRights[move][3] + AAA;
\r
13417 if(castlingRights[move][5] >= 0 &&
\r
13418 castlingRights[move][4] >= 0 )
\r
13419 *p++ = castlingRights[move][4] + AAA;
\r
13422 /* [HGM] write true castling rights */
\r
13423 if( nrCastlingRights == 6 ) {
\r
13424 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13425 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13426 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13427 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13428 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13429 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13430 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13431 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13434 if (q == p) *p++ = '-'; /* No castling rights */
\r
13438 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13439 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13440 /* En passant target square */
\r
13441 if (move > backwardMostMove) {
\r
13442 fromX = moveList[move - 1][0] - AAA;
\r
13443 fromY = moveList[move - 1][1] - ONE;
\r
13444 toX = moveList[move - 1][2] - AAA;
\r
13445 toY = moveList[move - 1][3] - ONE;
\r
13446 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13447 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13448 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13450 /* 2-square pawn move just happened */
\r
13451 *p++ = toX + AAA;
\r
13452 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13462 /* [HGM] find reversible plies */
\r
13463 { int i = 0, j=move;
\r
13465 if (appData.debugMode) { int k;
\r
13466 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13467 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13468 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13472 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13473 if( j == backwardMostMove ) i += initialRulePlies;
\r
13474 sprintf(p, "%d ", i);
\r
13475 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13477 /* Fullmove number */
\r
13478 sprintf(p, "%d", (move / 2) + 1);
\r
13480 return StrSave(buf);
\r
13484 ParseFEN(board, blackPlaysFirst, fen)
\r
13486 int *blackPlaysFirst;
\r
13492 ChessSquare piece;
\r
13496 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13497 if(gameInfo.holdingsWidth) {
\r
13498 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13499 board[i][0] = EmptySquare; /* black holdings */
\r
13500 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13501 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13502 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13506 /* Piece placement data */
\r
13507 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13510 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13511 if (*p == '/') p++;
\r
13512 emptycount = gameInfo.boardWidth - j;
\r
13513 while (emptycount--)
\r
13514 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13516 #if(BOARD_SIZE >= 10)
\r
13517 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13518 p++; emptycount=10;
\r
13519 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13520 while (emptycount--)
\r
13521 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13523 } else if (isdigit(*p)) {
\r
13524 emptycount = *p++ - '0';
\r
13525 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13526 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13527 while (emptycount--)
\r
13528 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13529 } else if (*p == '+' || isalpha(*p)) {
\r
13530 if (j >= gameInfo.boardWidth) return FALSE;
\r
13532 piece = CharToPiece(*++p);
\r
13533 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13534 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13535 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13536 } else piece = CharToPiece(*p++);
\r
13538 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13539 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13540 piece = (ChessSquare) (PROMOTED piece);
\r
13541 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13544 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13550 while (*p == '/' || *p == ' ') p++;
\r
13552 /* [HGM] look for Crazyhouse holdings here */
\r
13553 while(*p==' ') p++;
\r
13554 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13555 if(*p == '[') p++;
\r
13556 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13557 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13558 /* if we would allow FEN reading to set board size, we would */
\r
13559 /* have to add holdings and shift the board read so far here */
\r
13560 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13562 if((int) piece >= (int) BlackPawn ) {
\r
13563 i = (int)piece - (int)BlackPawn;
\r
13564 i = PieceToNumber((ChessSquare)i);
\r
13565 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13566 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13567 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13569 i = (int)piece - (int)WhitePawn;
\r
13570 i = PieceToNumber((ChessSquare)i);
\r
13571 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13572 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13573 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13577 if(*p == ']') *p++;
\r
13580 while(*p == ' ') p++;
\r
13582 /* Active color */
\r
13585 *blackPlaysFirst = FALSE;
\r
13588 *blackPlaysFirst = TRUE;
\r
13594 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13595 /* return the extra info in global variiables */
\r
13597 /* set defaults in case FEN is incomplete */
\r
13598 FENepStatus = EP_UNKNOWN;
\r
13599 for(i=0; i<nrCastlingRights; i++ ) {
\r
13600 FENcastlingRights[i] =
\r
13601 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13602 } /* assume possible unless obviously impossible */
\r
13603 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13604 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13605 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13606 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13607 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13608 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13609 FENrulePlies = 0;
\r
13611 while(*p==' ') p++;
\r
13612 if(nrCastlingRights) {
\r
13613 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13614 /* castling indicator present, so default becomes no castlings */
\r
13615 for(i=0; i<nrCastlingRights; i++ ) {
\r
13616 FENcastlingRights[i] = -1;
\r
13619 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13620 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13621 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13622 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13623 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13625 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13626 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13627 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13631 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13632 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13633 FENcastlingRights[2] = whiteKingFile;
\r
13636 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13637 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13638 FENcastlingRights[2] = whiteKingFile;
\r
13641 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13642 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13643 FENcastlingRights[5] = blackKingFile;
\r
13646 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13647 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13648 FENcastlingRights[5] = blackKingFile;
\r
13651 default: /* FRC castlings */
\r
13652 if(c >= 'a') { /* black rights */
\r
13653 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13654 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13655 if(i == BOARD_RGHT) break;
\r
13656 FENcastlingRights[5] = i;
\r
13658 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13659 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13661 FENcastlingRights[3] = c;
\r
13663 FENcastlingRights[4] = c;
\r
13664 } else { /* white rights */
\r
13665 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13666 if(board[0][i] == WhiteKing) break;
\r
13667 if(i == BOARD_RGHT) break;
\r
13668 FENcastlingRights[2] = i;
\r
13669 c -= AAA - 'a' + 'A';
\r
13670 if(board[0][c] >= WhiteKing) break;
\r
13672 FENcastlingRights[0] = c;
\r
13674 FENcastlingRights[1] = c;
\r
13678 if (appData.debugMode) {
\r
13679 fprintf(debugFP, "FEN castling rights:");
\r
13680 for(i=0; i<nrCastlingRights; i++)
\r
13681 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13682 fprintf(debugFP, "\n");
\r
13685 while(*p==' ') p++;
\r
13688 /* read e.p. field in games that know e.p. capture */
\r
13689 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13690 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13692 p++; FENepStatus = EP_NONE;
\r
13694 char c = *p++ - AAA;
\r
13696 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13697 if(*p >= '0' && *p <='9') *p++;
\r
13703 if(sscanf(p, "%d", &i) == 1) {
\r
13704 FENrulePlies = i; /* 50-move ply counter */
\r
13705 /* (The move number is still ignored) */
\r
13712 EditPositionPasteFEN(char *fen)
\r
13714 if (fen != NULL) {
\r
13715 Board initial_position;
\r
13717 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13718 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13721 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13722 EditPositionEvent();
\r
13723 blackPlaysFirst = savedBlackPlaysFirst;
\r
13724 CopyBoard(boards[0], initial_position);
\r
13725 /* [HGM] copy FEN attributes as well */
\r
13727 initialRulePlies = FENrulePlies;
\r
13728 epStatus[0] = FENepStatus;
\r
13729 for( i=0; i<nrCastlingRights; i++ )
\r
13730 castlingRights[0][i] = FENcastlingRights[i];
\r
13732 EditPositionDone();
\r
13733 DisplayBothClocks();
\r
13734 DrawPosition(FALSE, boards[currentMove]);
\r