2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard,
\r
6 * Massachusetts. Enhancements Copyright
\r
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
\r
10 * The following terms apply to Digital Equipment Corporation's copyright
\r
11 * interest in XBoard:
\r
12 * ------------------------------------------------------------------------
\r
13 * All Rights Reserved
\r
15 * Permission to use, copy, modify, and distribute this software and its
\r
16 * documentation for any purpose and without fee is hereby granted,
\r
17 * provided that the above copyright notice appear in all copies and that
\r
18 * both that copyright notice and this permission notice appear in
\r
19 * supporting documentation, and that the name of Digital not be
\r
20 * used in advertising or publicity pertaining to distribution of the
\r
21 * software without specific, written prior permission.
\r
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
30 * ------------------------------------------------------------------------
\r
32 * The following terms apply to the enhanced version of XBoard
\r
33 * distributed by the Free Software Foundation:
\r
34 * ------------------------------------------------------------------------
\r
36 * GNU XBoard is free software: you can redistribute it and/or modify
\r
37 * it under the terms of the GNU General Public License as published by
\r
38 * the Free Software Foundation, either version 3 of the License, or (at
\r
39 * your option) any later version.
\r
41 * GNU XBoard is distributed in the hope that it will be useful, but
\r
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
\r
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
44 * General Public License for more details.
\r
46 * You should have received a copy of the GNU General Public License
\r
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
\r
49 *------------------------------------------------------------------------
\r
50 ** See the file ChangeLog for a revision history. */
\r
52 /* [AS] Also useful here for debugging */
\r
54 #include <windows.h>
\r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
70 #include <sys/types.h>
\r
71 #include <sys/stat.h>
\r
76 # include <stdlib.h>
\r
77 # include <string.h>
\r
78 #else /* not STDC_HEADERS */
\r
80 # include <string.h>
\r
81 # else /* not HAVE_STRING_H */
\r
82 # include <strings.h>
\r
83 # endif /* not HAVE_STRING_H */
\r
84 #endif /* not STDC_HEADERS */
\r
86 #if HAVE_SYS_FCNTL_H
\r
87 # include <sys/fcntl.h>
\r
88 #else /* not HAVE_SYS_FCNTL_H */
\r
91 # endif /* HAVE_FCNTL_H */
\r
92 #endif /* not HAVE_SYS_FCNTL_H */
\r
94 #if TIME_WITH_SYS_TIME
\r
95 # include <sys/time.h>
\r
98 # if HAVE_SYS_TIME_H
\r
99 # include <sys/time.h>
\r
105 #if defined(_amigados) && !defined(__GNUC__)
\r
107 int tz_minuteswest;
\r
110 extern int gettimeofday(struct timeval *, struct timezone *);
\r
114 # include <unistd.h>
\r
117 #include "common.h"
\r
118 #include "frontend.h"
\r
119 #include "backend.h"
\r
120 #include "parser.h"
\r
123 # include "zippy.h"
\r
125 #include "backendz.h"
\r
126 #include "gettext.h"
\r
129 # define _(s) gettext (s)
\r
130 # define N_(s) gettext_noop (s)
\r
137 /* A point in time */
\r
139 long sec; /* Assuming this is >= 32 bits */
\r
140 int ms; /* Assuming this is >= 16 bits */
\r
143 int establish P((void));
\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
145 char *buf, int count, int error));
\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
147 char *buf, int count, int error));
\r
148 void SendToICS P((char *s));
\r
149 void SendToICSDelayed P((char *s, long msdelay));
\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
151 int toX, int toY));
\r
152 void InitPosition P((int redraw));
\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
154 int AutoPlayOneMove P((void));
\r
155 int LoadGameOneMove P((ChessMove readAhead));
\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
158 int SavePositionToFile P((char *filename));
\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
164 /*char*/int promoChar));
\r
165 void BackwardInner P((int target));
\r
166 void ForwardInner P((int target));
\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
168 void EditPositionDone P((void));
\r
169 void PrintOpponents P((FILE *fp));
\r
170 void PrintPosition P((FILE *fp, int move));
\r
171 void StartChessProgram P((ChessProgramState *cps));
\r
172 void SendToProgram P((char *message, ChessProgramState *cps));
\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
175 char *buf, int count, int error));
\r
176 void SendTimeControl P((ChessProgramState *cps,
\r
177 int mps, long tc, int inc, int sd, int st));
\r
178 char *TimeControlTagValue P((void));
\r
179 void Attention P((ChessProgramState *cps));
\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
181 void ResurrectChessProgram P((void));
\r
182 void DisplayComment P((int moveNumber, char *text));
\r
183 void DisplayMove P((int moveNumber));
\r
184 void DisplayAnalysis P((void));
\r
186 void ParseGameHistory P((char *game));
\r
187 void ParseBoard12 P((char *string));
\r
188 void StartClocks P((void));
\r
189 void SwitchClocks P((void));
\r
190 void StopClocks P((void));
\r
191 void ResetClocks P((void));
\r
192 char *PGNDate P((void));
\r
193 void SetGameInfo P((void));
\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
195 int RegisterMove P((void));
\r
196 void MakeRegisteredMove P((void));
\r
197 void TruncateGame P((void));
\r
198 int looking_at P((char *, int *, char *));
\r
199 void CopyPlayerNameIntoFileName P((char **, char *));
\r
200 char *SavePart P((char *));
\r
201 int SaveGameOldStyle P((FILE *));
\r
202 int SaveGamePGN P((FILE *));
\r
203 void GetTimeMark P((TimeMark *));
\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
205 int CheckFlags P((void));
\r
206 long NextTickLength P((long));
\r
207 void CheckTimeControl P((void));
\r
208 void show_bytes P((FILE *, char *, int));
\r
209 int string_to_rating P((char *str));
\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
211 void InitBackEnd3 P((void));
\r
212 void FeatureDone P((ChessProgramState* cps, int val));
\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
214 void OutputKibitz(int window, char *text);
\r
215 int PerpetualChase(int first, int last);
\r
216 int EngineOutputIsUp();
\r
217 void InitDrawingSizes(int x, int y);
\r
220 extern void ConsoleCreate();
\r
223 ChessProgramState *WhitePlayer();
\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
225 int VerifyDisplayMode P(());
\r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
231 extern char installDir[MSG_SIZ];
\r
233 extern int tinyLayout, smallLayout;
\r
234 ChessProgramStats programStats;
\r
235 static int exiting = 0; /* [HGM] moved to top */
\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
\r
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
242 int opponentKibitzes;
\r
244 /* States for ics_getting_history */
\r
246 #define H_REQUESTED 1
\r
247 #define H_GOT_REQ_HEADER 2
\r
248 #define H_GOT_UNREQ_HEADER 3
\r
249 #define H_GETTING_MOVES 4
\r
250 #define H_GOT_UNWANTED_HEADER 5
\r
252 /* whosays values for GameEnds */
\r
254 #define GE_ENGINE 1
\r
255 #define GE_PLAYER 2
\r
257 #define GE_XBOARD 4
\r
258 #define GE_ENGINE1 5
\r
259 #define GE_ENGINE2 6
\r
261 /* Maximum number of games in a cmail message */
\r
262 #define CMAIL_MAX_GAMES 20
\r
264 /* Different types of move when calling RegisterMove */
\r
265 #define CMAIL_MOVE 0
\r
266 #define CMAIL_RESIGN 1
\r
267 #define CMAIL_DRAW 2
\r
268 #define CMAIL_ACCEPT 3
\r
270 /* Different types of result to remember for each game */
\r
271 #define CMAIL_NOT_RESULT 0
\r
272 #define CMAIL_OLD_RESULT 1
\r
273 #define CMAIL_NEW_RESULT 2
\r
275 /* Telnet protocol constants */
\r
276 #define TN_WILL 0373
\r
277 #define TN_WONT 0374
\r
279 #define TN_DONT 0376
\r
280 #define TN_IAC 0377
\r
281 #define TN_ECHO 0001
\r
282 #define TN_SGA 0003
\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
288 assert( dst != NULL );
\r
289 assert( src != NULL );
\r
290 assert( count > 0 );
\r
292 strncpy( dst, src, count );
\r
293 dst[ count-1 ] = '\0';
\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.
\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
303 assert( dst != NULL );
\r
304 assert( src != NULL );
\r
305 assert( count > 0 );
\r
307 dst_len = strlen(dst);
\r
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
311 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
317 /* Some compiler can't cast u64 to double
\r
318 * This function do the job for us:
\r
320 * We use the highest bit for cast, this only
\r
321 * works if the highest bit is not
\r
322 * in use (This should not happen)
\r
324 * We used this for all compiler
\r
327 u64ToDouble(u64 value)
\r
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
331 r = (double)(s64)tmp;
\r
332 if (value & u64Const(0x8000000000000000))
\r
333 r += 9.2233720368547758080e18; /* 2^63 */
\r
337 /* Fake up flags for now, as we aren't keeping track of castling
\r
338 availability yet. [HGM] Change of logic: the flag now only
\r
339 indicates the type of castlings allowed by the rule of the game.
\r
340 The actual rights themselves are maintained in the array
\r
341 castlingRights, as part of the game history, and are not probed
\r
347 int flags = F_ALL_CASTLE_OK;
\r
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
349 switch (gameInfo.variant) {
\r
350 case VariantSuicide:
\r
351 flags &= ~F_ALL_CASTLE_OK;
\r
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
353 flags |= F_IGNORE_CHECK;
\r
354 case VariantLosers:
\r
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
\r
357 case VariantAtomic:
\r
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
360 case VariantKriegspiel:
\r
361 flags |= F_KRIEGSPIEL_CAPTURE;
\r
363 case VariantCapaRandom:
\r
364 case VariantFischeRandom:
\r
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
366 case VariantNoCastle:
\r
367 case VariantShatranj:
\r
368 case VariantCourier:
\r
369 flags &= ~F_ALL_CASTLE_OK;
\r
377 FILE *gameFileFP, *debugFP;
\r
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
381 into a fixed-size buffer. Because of this, we must be prepared to
\r
382 receive strings as long as the size of the input buffer, which is currently
\r
383 set to 4K for Windows and 8K for the rest.
\r
384 So, we must either allocate sufficiently large buffers here, or
\r
385 reduce the size of the input buffer in the input reading part.
\r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
390 char thinkOutput1[MSG_SIZ*10];
\r
392 ChessProgramState first, second;
\r
394 /* premove variables */
\r
395 int premoveToX = 0;
\r
396 int premoveToY = 0;
\r
397 int premoveFromX = 0;
\r
398 int premoveFromY = 0;
\r
399 int premovePromoChar = 0;
\r
400 int gotPremove = 0;
\r
401 Boolean alarmSounded;
\r
402 /* end premove variables */
\r
404 char *ics_prefix = "$";
\r
405 int ics_type = ICS_GENERIC;
\r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
408 int pauseExamForwardMostMove = 0;
\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
415 int whiteFlag = FALSE, blackFlag = FALSE;
\r
416 int userOfferedDraw = FALSE;
\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
419 int cmailMoveType[CMAIL_MAX_GAMES];
\r
420 long ics_clock_paused = 0;
\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
423 GameMode gameMode = BeginningOfGame;
\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
427 int hiddenThinkOutputState = 0; /* [AS] */
\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
429 int adjudicateLossPlies = 6;
\r
430 char white_holding[64], black_holding[64];
\r
431 TimeMark lastNodeCountTime;
\r
432 long lastNodeCount=0;
\r
433 int have_sent_ICS_logon = 0;
\r
434 int movesPerSession;
\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
436 long timeControl_2; /* [AS] Allow separate time controls */
\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
438 long timeRemaining[2][MAX_MOVES];
\r
440 TimeMark programStartTime;
\r
441 char ics_handle[MSG_SIZ];
\r
442 int have_set_title = 0;
\r
444 /* animateTraining preserves the state of appData.animate
\r
445 * when Training mode is activated. This allows the
\r
446 * response to be animated when appData.animate == TRUE and
\r
447 * appData.animateDragging == TRUE.
\r
449 Boolean animateTraining;
\r
455 Board boards[MAX_MOVES];
\r
456 /* [HGM] Following 7 needed for accurate legality tests: */
\r
457 char epStatus[MAX_MOVES];
\r
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
462 int initialRulePlies, FENrulePlies;
\r
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
466 int shuffleOpenings;
\r
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
479 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
504 #if (BOARD_SIZE>=10)
\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
548 #define GothicArray CapablancaArray
\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
559 #define FalconArray CapablancaArray
\r
562 #else // !(BOARD_SIZE>=10)
\r
563 #define XiangqiPosition FIDEArray
\r
564 #define CapablancaArray FIDEArray
\r
565 #define GothicArray FIDEArray
\r
566 #define GreatArray FIDEArray
\r
567 #endif // !(BOARD_SIZE>=10)
\r
569 #if (BOARD_SIZE>=12)
\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
576 #else // !(BOARD_SIZE>=12)
\r
577 #define CourierArray CapablancaArray
\r
578 #endif // !(BOARD_SIZE>=12)
\r
581 Board initialPosition;
\r
584 /* Convert str to a rating. Checks for special cases of "----",
\r
586 "++++", etc. Also strips ()'s */
\r
588 string_to_rating(str)
\r
591 while(*str && !isdigit(*str)) ++str;
\r
593 return 0; /* One of the special "no rating" cases */
\r
599 ClearProgramStats()
\r
601 /* Init programStats */
\r
602 programStats.movelist[0] = 0;
\r
603 programStats.depth = 0;
\r
604 programStats.nr_moves = 0;
\r
605 programStats.moves_left = 0;
\r
606 programStats.nodes = 0;
\r
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
608 programStats.score = 0;
\r
609 programStats.got_only_move = 0;
\r
610 programStats.got_fail = 0;
\r
611 programStats.line_is_book = 0;
\r
617 int matched, min, sec;
\r
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
621 GetTimeMark(&programStartTime);
\r
623 ClearProgramStats();
\r
624 programStats.ok_to_send = 1;
\r
625 programStats.seen_stat = 0;
\r
628 * Initialize game list
\r
630 ListNew(&gameList);
\r
634 * Internet chess server status
\r
636 if (appData.icsActive) {
\r
637 appData.matchMode = FALSE;
\r
638 appData.matchGames = 0;
\r
640 appData.noChessProgram = !appData.zippyPlay;
\r
642 appData.zippyPlay = FALSE;
\r
643 appData.zippyTalk = FALSE;
\r
644 appData.noChessProgram = TRUE;
\r
646 if (*appData.icsHelper != NULLCHAR) {
\r
647 appData.useTelnet = TRUE;
\r
648 appData.telnetProgram = appData.icsHelper;
\r
651 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
654 /* [AS] Initialize pv info list [HGM] and game state */
\r
658 for( i=0; i<MAX_MOVES; i++ ) {
\r
659 pvInfoList[i].depth = -1;
\r
660 epStatus[i]=EP_NONE;
\r
661 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
666 * Parse timeControl resource
\r
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
669 appData.movesPerSession)) {
\r
671 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
672 DisplayFatalError(buf, 0, 2);
\r
676 * Parse searchTime resource
\r
678 if (*appData.searchTime != NULLCHAR) {
\r
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
680 if (matched == 1) {
\r
681 searchTime = min * 60;
\r
682 } else if (matched == 2) {
\r
683 searchTime = min * 60 + sec;
\r
686 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
687 DisplayFatalError(buf, 0, 2);
\r
691 /* [AS] Adjudication threshold */
\r
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
694 first.which = "first";
\r
695 second.which = "second";
\r
696 first.maybeThinking = second.maybeThinking = FALSE;
\r
697 first.pr = second.pr = NoProc;
\r
698 first.isr = second.isr = NULL;
\r
699 first.sendTime = second.sendTime = 2;
\r
700 first.sendDrawOffers = 1;
\r
701 if (appData.firstPlaysBlack) {
\r
702 first.twoMachinesColor = "black\n";
\r
703 second.twoMachinesColor = "white\n";
\r
705 first.twoMachinesColor = "white\n";
\r
706 second.twoMachinesColor = "black\n";
\r
708 first.program = appData.firstChessProgram;
\r
709 second.program = appData.secondChessProgram;
\r
710 first.host = appData.firstHost;
\r
711 second.host = appData.secondHost;
\r
712 first.dir = appData.firstDirectory;
\r
713 second.dir = appData.secondDirectory;
\r
714 first.other = &second;
\r
715 second.other = &first;
\r
716 first.initString = appData.initString;
\r
717 second.initString = appData.secondInitString;
\r
718 first.computerString = appData.firstComputerString;
\r
719 second.computerString = appData.secondComputerString;
\r
720 first.useSigint = second.useSigint = TRUE;
\r
721 first.useSigterm = second.useSigterm = TRUE;
\r
722 first.reuse = appData.reuseFirst;
\r
723 second.reuse = appData.reuseSecond;
\r
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
725 second.nps = appData.secondNPS;
\r
726 first.useSetboard = second.useSetboard = FALSE;
\r
727 first.useSAN = second.useSAN = FALSE;
\r
728 first.usePing = second.usePing = FALSE;
\r
729 first.lastPing = second.lastPing = 0;
\r
730 first.lastPong = second.lastPong = 0;
\r
731 first.usePlayother = second.usePlayother = FALSE;
\r
732 first.useColors = second.useColors = TRUE;
\r
733 first.useUsermove = second.useUsermove = FALSE;
\r
734 first.sendICS = second.sendICS = FALSE;
\r
735 first.sendName = second.sendName = appData.icsActive;
\r
736 first.sdKludge = second.sdKludge = FALSE;
\r
737 first.stKludge = second.stKludge = FALSE;
\r
738 TidyProgramName(first.program, first.host, first.tidy);
\r
739 TidyProgramName(second.program, second.host, second.tidy);
\r
740 first.matchWins = second.matchWins = 0;
\r
741 strcpy(first.variants, appData.variant);
\r
742 strcpy(second.variants, appData.variant);
\r
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
744 first.analyzing = second.analyzing = FALSE;
\r
745 first.initDone = second.initDone = FALSE;
\r
747 /* New features added by Tord: */
\r
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
750 /* End of new features added by Tord. */
\r
752 /* [HGM] time odds: set factor for each machine */
\r
753 first.timeOdds = appData.firstTimeOdds;
\r
754 second.timeOdds = appData.secondTimeOdds;
\r
756 if(appData.timeOddsMode) {
\r
757 norm = first.timeOdds;
\r
758 if(norm > second.timeOdds) norm = second.timeOdds;
\r
760 first.timeOdds /= norm;
\r
761 second.timeOdds /= norm;
\r
764 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
765 first.accumulateTC = appData.firstAccumulateTC;
\r
766 second.accumulateTC = appData.secondAccumulateTC;
\r
767 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
770 first.debug = second.debug = FALSE;
\r
771 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
773 /* [HGM] options */
\r
774 first.optionSettings = appData.firstOptions;
\r
775 second.optionSettings = appData.secondOptions;
\r
777 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
778 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
779 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
780 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
781 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
782 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
784 if (appData.firstProtocolVersion > PROTOVER ||
\r
785 appData.firstProtocolVersion < 1) {
\r
787 sprintf(buf, _("protocol version %d not supported"),
\r
788 appData.firstProtocolVersion);
\r
789 DisplayFatalError(buf, 0, 2);
\r
791 first.protocolVersion = appData.firstProtocolVersion;
\r
794 if (appData.secondProtocolVersion > PROTOVER ||
\r
795 appData.secondProtocolVersion < 1) {
\r
797 sprintf(buf, _("protocol version %d not supported"),
\r
798 appData.secondProtocolVersion);
\r
799 DisplayFatalError(buf, 0, 2);
\r
801 second.protocolVersion = appData.secondProtocolVersion;
\r
804 if (appData.icsActive) {
\r
805 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
806 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
807 appData.clockMode = FALSE;
\r
808 first.sendTime = second.sendTime = 0;
\r
812 /* Override some settings from environment variables, for backward
\r
813 compatibility. Unfortunately it's not feasible to have the env
\r
814 vars just set defaults, at least in xboard. Ugh.
\r
816 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
821 if (appData.noChessProgram) {
\r
822 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
823 + strlen(PATCHLEVEL));
\r
824 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
829 while (*q != ' ' && *q != NULLCHAR) q++;
\r
831 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
832 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
833 + strlen(PATCHLEVEL) + (q - p));
\r
834 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
835 strncat(programVersion, p, q - p);
\r
837 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
838 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
839 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
840 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
844 if (!appData.icsActive) {
\r
846 /* Check for variants that are supported only in ICS mode,
\r
847 or not at all. Some that are accepted here nevertheless
\r
848 have bugs; see comments below.
\r
850 VariantClass variant = StringToVariant(appData.variant);
\r
852 case VariantBughouse: /* need four players and two boards */
\r
853 case VariantKriegspiel: /* need to hide pieces and move details */
\r
854 /* case VariantFischeRandom: (Fabien: moved below) */
\r
855 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
856 DisplayFatalError(buf, 0, 2);
\r
859 case VariantUnknown:
\r
860 case VariantLoadable:
\r
870 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
871 DisplayFatalError(buf, 0, 2);
\r
874 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
875 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
876 case VariantGothic: /* [HGM] should work */
\r
877 case VariantCapablanca: /* [HGM] should work */
\r
878 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
879 case VariantShogi: /* [HGM] drops not tested for legality */
\r
880 case VariantKnightmate: /* [HGM] should work */
\r
881 case VariantCylinder: /* [HGM] untested */
\r
882 case VariantFalcon: /* [HGM] untested */
\r
883 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
884 offboard interposition not understood */
\r
885 case VariantNormal: /* definitely works! */
\r
886 case VariantWildCastle: /* pieces not automatically shuffled */
\r
887 case VariantNoCastle: /* pieces not automatically shuffled */
\r
888 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
889 case VariantLosers: /* should work except for win condition,
\r
890 and doesn't know captures are mandatory */
\r
891 case VariantSuicide: /* should work except for win condition,
\r
892 and doesn't know captures are mandatory */
\r
893 case VariantGiveaway: /* should work except for win condition,
\r
894 and doesn't know captures are mandatory */
\r
895 case VariantTwoKings: /* should work */
\r
896 case VariantAtomic: /* should work except for win condition */
\r
897 case Variant3Check: /* should work except for win condition */
\r
898 case VariantShatranj: /* should work except for all win conditions */
\r
899 case VariantBerolina: /* might work if TestLegality is off */
\r
900 case VariantCapaRandom: /* should work */
\r
901 case VariantJanus: /* should work */
\r
902 case VariantSuper: /* experimental */
\r
903 case VariantGreat: /* experimental, requires legality testing to be off */
\r
908 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
909 InitEngineUCI( installDir, &second );
\r
912 int NextIntegerFromString( char ** str, long * value )
\r
917 while( *s == ' ' || *s == '\t' ) {
\r
923 if( *s >= '0' && *s <= '9' ) {
\r
924 while( *s >= '0' && *s <= '9' ) {
\r
925 *value = *value * 10 + (*s - '0');
\r
937 int NextTimeControlFromString( char ** str, long * value )
\r
940 int result = NextIntegerFromString( str, &temp );
\r
942 if( result == 0 ) {
\r
943 *value = temp * 60; /* Minutes */
\r
944 if( **str == ':' ) {
\r
946 result = NextIntegerFromString( str, &temp );
\r
947 *value += temp; /* Seconds */
\r
954 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
955 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
956 int result = -1; long temp, temp2;
\r
958 if(**str != '+') return -1; // old params remain in force!
\r
960 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
963 /* time only: incremental or sudden-death time control */
\r
964 if(**str == '+') { /* increment follows; read it */
\r
966 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
967 *inc = temp2 * 1000;
\r
969 *moves = 0; *tc = temp * 1000;
\r
971 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
973 (*str)++; /* classical time control */
\r
974 result = NextTimeControlFromString( str, &temp2);
\r
977 *tc = temp2 * 1000;
\r
983 int GetTimeQuota(int movenr)
\r
984 { /* [HGM] get time to add from the multi-session time-control string */
\r
985 int moves=1; /* kludge to force reading of first session */
\r
986 long time, increment;
\r
987 char *s = fullTimeControlString;
\r
989 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
991 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
992 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
993 if(movenr == -1) return time; /* last move before new session */
\r
994 if(!moves) return increment; /* current session is incremental */
\r
995 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
996 } while(movenr >= -1); /* try again for next session */
\r
998 return 0; // no new time quota on this move
\r
1002 ParseTimeControl(tc, ti, mps)
\r
1008 int matched, min, sec;
\r
1010 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1011 if (matched == 1) {
\r
1012 timeControl = min * 60 * 1000;
\r
1013 } else if (matched == 2) {
\r
1014 timeControl = (min * 60 + sec) * 1000;
\r
1021 char buf[MSG_SIZ];
\r
1023 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1026 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1027 else sprintf(buf, "+%s+%d", tc, ti);
\r
1030 sprintf(buf, "+%d/%s", mps, tc);
\r
1031 else sprintf(buf, "+%s", tc);
\r
1033 fullTimeControlString = StrSave(buf);
\r
1035 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1039 if( *tc == '/' ) {
\r
1040 /* Parse second time control */
\r
1043 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1051 timeControl_2 = tc2 * 1000;
\r
1054 timeControl_2 = 0;
\r
1061 timeControl = tc1 * 1000;
\r
1065 timeIncrement = ti * 1000; /* convert to ms */
\r
1066 movesPerSession = 0;
\r
1068 timeIncrement = 0;
\r
1069 movesPerSession = mps;
\r
1077 if (appData.debugMode) {
\r
1078 fprintf(debugFP, "%s\n", programVersion);
\r
1081 if (appData.matchGames > 0) {
\r
1082 appData.matchMode = TRUE;
\r
1083 } else if (appData.matchMode) {
\r
1084 appData.matchGames = 1;
\r
1086 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1087 appData.matchGames = appData.sameColorGames;
\r
1088 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1089 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1090 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1092 Reset(TRUE, FALSE);
\r
1093 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1096 /* kludge: allow timeout for initial "feature" commands */
\r
1098 DisplayMessage("", _("Starting chess program"));
\r
1099 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1104 InitBackEnd3 P((void))
\r
1106 GameMode initialMode;
\r
1107 char buf[MSG_SIZ];
\r
1110 InitChessProgram(&first, startedFromSetupPosition);
\r
1113 if (appData.icsActive) {
\r
1115 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1118 err = establish();
\r
1120 if (*appData.icsCommPort != NULLCHAR) {
\r
1121 sprintf(buf, _("Could not open comm port %s"),
\r
1122 appData.icsCommPort);
\r
1124 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1125 appData.icsHost, appData.icsPort);
\r
1127 DisplayFatalError(buf, err, 1);
\r
1132 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1134 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1135 } else if (appData.noChessProgram) {
\r
1141 if (*appData.cmailGameName != NULLCHAR) {
\r
1143 OpenLoopback(&cmailPR);
\r
1145 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1149 DisplayMessage("", "");
\r
1150 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1151 initialMode = BeginningOfGame;
\r
1152 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1153 initialMode = TwoMachinesPlay;
\r
1154 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1155 initialMode = AnalyzeFile;
\r
1156 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1157 initialMode = AnalyzeMode;
\r
1158 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1159 initialMode = MachinePlaysWhite;
\r
1160 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1161 initialMode = MachinePlaysBlack;
\r
1162 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1163 initialMode = EditGame;
\r
1164 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1165 initialMode = EditPosition;
\r
1166 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1167 initialMode = Training;
\r
1169 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1170 DisplayFatalError(buf, 0, 2);
\r
1174 if (appData.matchMode) {
\r
1175 /* Set up machine vs. machine match */
\r
1176 if (appData.noChessProgram) {
\r
1177 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1183 if (*appData.loadGameFile != NULLCHAR) {
\r
1184 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1185 if(index<0) lastIndex = index = 1;
\r
1186 if (!LoadGameFromFile(appData.loadGameFile,
\r
1188 appData.loadGameFile, FALSE)) {
\r
1189 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1192 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1193 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1194 if(index<0) lastIndex = index = 1;
\r
1195 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1197 appData.loadPositionFile)) {
\r
1198 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1202 TwoMachinesEvent();
\r
1203 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1204 /* Set up cmail mode */
\r
1205 ReloadCmailMsgEvent(TRUE);
\r
1207 /* Set up other modes */
\r
1208 if (initialMode == AnalyzeFile) {
\r
1209 if (*appData.loadGameFile == NULLCHAR) {
\r
1210 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1214 if (*appData.loadGameFile != NULLCHAR) {
\r
1215 (void) LoadGameFromFile(appData.loadGameFile,
\r
1216 appData.loadGameIndex,
\r
1217 appData.loadGameFile, TRUE);
\r
1218 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1219 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1220 appData.loadPositionIndex,
\r
1221 appData.loadPositionFile);
\r
1222 /* [HGM] try to make self-starting even after FEN load */
\r
1223 /* to allow automatic setup of fairy variants with wtm */
\r
1224 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1225 gameMode = BeginningOfGame;
\r
1226 setboardSpoiledMachineBlack = 1;
\r
1228 /* [HGM] loadPos: make that every new game uses the setup */
\r
1229 /* from file as long as we do not switch variant */
\r
1230 if(!blackPlaysFirst) { int i;
\r
1231 startedFromPositionFile = TRUE;
\r
1232 CopyBoard(filePosition, boards[0]);
\r
1233 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1236 if (initialMode == AnalyzeMode) {
\r
1237 if (appData.noChessProgram) {
\r
1238 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1241 if (appData.icsActive) {
\r
1242 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1245 AnalyzeModeEvent();
\r
1246 } else if (initialMode == AnalyzeFile) {
\r
1247 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1248 ShowThinkingEvent();
\r
1249 AnalyzeFileEvent();
\r
1250 AnalysisPeriodicEvent(1);
\r
1251 } else if (initialMode == MachinePlaysWhite) {
\r
1252 if (appData.noChessProgram) {
\r
1253 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1257 if (appData.icsActive) {
\r
1258 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1262 MachineWhiteEvent();
\r
1263 } else if (initialMode == MachinePlaysBlack) {
\r
1264 if (appData.noChessProgram) {
\r
1265 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1269 if (appData.icsActive) {
\r
1270 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1274 MachineBlackEvent();
\r
1275 } else if (initialMode == TwoMachinesPlay) {
\r
1276 if (appData.noChessProgram) {
\r
1277 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1281 if (appData.icsActive) {
\r
1282 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1286 TwoMachinesEvent();
\r
1287 } else if (initialMode == EditGame) {
\r
1289 } else if (initialMode == EditPosition) {
\r
1290 EditPositionEvent();
\r
1291 } else if (initialMode == Training) {
\r
1292 if (*appData.loadGameFile == NULLCHAR) {
\r
1293 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1302 * Establish will establish a contact to a remote host.port.
\r
1303 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1304 * used to talk to the host.
\r
1305 * Returns 0 if okay, error code if not.
\r
1310 char buf[MSG_SIZ];
\r
1312 if (*appData.icsCommPort != NULLCHAR) {
\r
1313 /* Talk to the host through a serial comm port */
\r
1314 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1316 } else if (*appData.gateway != NULLCHAR) {
\r
1317 if (*appData.remoteShell == NULLCHAR) {
\r
1318 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1319 sprintf(buf, "%s %s %s",
\r
1320 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1321 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1324 /* Use the rsh program to run telnet program on a gateway host */
\r
1325 if (*appData.remoteUser == NULLCHAR) {
\r
1326 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1327 appData.gateway, appData.telnetProgram,
\r
1328 appData.icsHost, appData.icsPort);
\r
1330 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1331 appData.remoteShell, appData.gateway,
\r
1332 appData.remoteUser, appData.telnetProgram,
\r
1333 appData.icsHost, appData.icsPort);
\r
1335 return StartChildProcess(buf, "", &icsPR);
\r
1338 } else if (appData.useTelnet) {
\r
1339 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1342 /* TCP socket interface differs somewhat between
\r
1343 Unix and NT; handle details in the front end.
\r
1345 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1350 show_bytes(fp, buf, count)
\r
1356 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1357 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1366 /* Returns an errno value */
\r
1368 OutputMaybeTelnet(pr, message, count, outError)
\r
1374 char buf[8192], *p, *q, *buflim;
\r
1375 int left, newcount, outcount;
\r
1377 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1378 *appData.gateway != NULLCHAR) {
\r
1379 if (appData.debugMode) {
\r
1380 fprintf(debugFP, ">ICS: ");
\r
1381 show_bytes(debugFP, message, count);
\r
1382 fprintf(debugFP, "\n");
\r
1384 return OutputToProcess(pr, message, count, outError);
\r
1387 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1393 if (q >= buflim) {
\r
1394 if (appData.debugMode) {
\r
1395 fprintf(debugFP, ">ICS: ");
\r
1396 show_bytes(debugFP, buf, newcount);
\r
1397 fprintf(debugFP, "\n");
\r
1399 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1400 if (outcount < newcount) return -1; /* to be sure */
\r
1407 } else if (((unsigned char) *p) == TN_IAC) {
\r
1408 *q++ = (char) TN_IAC;
\r
1415 if (appData.debugMode) {
\r
1416 fprintf(debugFP, ">ICS: ");
\r
1417 show_bytes(debugFP, buf, newcount);
\r
1418 fprintf(debugFP, "\n");
\r
1420 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1421 if (outcount < newcount) return -1; /* to be sure */
\r
1426 read_from_player(isr, closure, message, count, error)
\r
1427 InputSourceRef isr;
\r
1433 int outError, outCount;
\r
1434 static int gotEof = 0;
\r
1436 /* Pass data read from player on to ICS */
\r
1439 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1440 if (outCount < count) {
\r
1441 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1443 } else if (count < 0) {
\r
1444 RemoveInputSource(isr);
\r
1445 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1446 } else if (gotEof++ > 0) {
\r
1447 RemoveInputSource(isr);
\r
1448 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1456 int count, outCount, outError;
\r
1458 if (icsPR == NULL) return;
\r
1460 count = strlen(s);
\r
1461 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1462 if (outCount < count) {
\r
1463 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1467 /* This is used for sending logon scripts to the ICS. Sending
\r
1468 without a delay causes problems when using timestamp on ICC
\r
1469 (at least on my machine). */
\r
1471 SendToICSDelayed(s,msdelay)
\r
1475 int count, outCount, outError;
\r
1477 if (icsPR == NULL) return;
\r
1479 count = strlen(s);
\r
1480 if (appData.debugMode) {
\r
1481 fprintf(debugFP, ">ICS: ");
\r
1482 show_bytes(debugFP, s, count);
\r
1483 fprintf(debugFP, "\n");
\r
1485 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1487 if (outCount < count) {
\r
1488 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1493 /* Remove all highlighting escape sequences in s
\r
1494 Also deletes any suffix starting with '('
\r
1497 StripHighlightAndTitle(s)
\r
1500 static char retbuf[MSG_SIZ];
\r
1503 while (*s != NULLCHAR) {
\r
1504 while (*s == '\033') {
\r
1505 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1506 if (*s != NULLCHAR) s++;
\r
1508 while (*s != NULLCHAR && *s != '\033') {
\r
1509 if (*s == '(' || *s == '[') {
\r
1520 /* Remove all highlighting escape sequences in s */
\r
1525 static char retbuf[MSG_SIZ];
\r
1528 while (*s != NULLCHAR) {
\r
1529 while (*s == '\033') {
\r
1530 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1531 if (*s != NULLCHAR) s++;
\r
1533 while (*s != NULLCHAR && *s != '\033') {
\r
1541 char *variantNames[] = VARIANT_NAMES;
\r
1546 return variantNames[v];
\r
1550 /* Identify a variant from the strings the chess servers use or the
\r
1551 PGN Variant tag names we use. */
\r
1553 StringToVariant(e)
\r
1558 VariantClass v = VariantNormal;
\r
1559 int i, found = FALSE;
\r
1560 char buf[MSG_SIZ];
\r
1564 /* [HGM] skip over optional board-size prefixes */
\r
1565 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1566 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1567 while( *e++ != '_');
\r
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1571 if (StrCaseStr(e, variantNames[i])) {
\r
1572 v = (VariantClass) i;
\r
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1580 || StrCaseStr(e, "wild/fr")
\r
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1582 v = VariantFischeRandom;
\r
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1584 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1587 if (isdigit(*p)) {
\r
1593 case 0: /* FICS only, actually */
\r
1595 /* Castling legal even if K starts on d-file */
\r
1596 v = VariantWildCastle;
\r
1601 /* Castling illegal even if K & R happen to start in
\r
1602 normal positions. */
\r
1603 v = VariantNoCastle;
\r
1616 /* Castling legal iff K & R start in normal positions */
\r
1617 v = VariantNormal;
\r
1622 /* Special wilds for position setup; unclear what to do here */
\r
1623 v = VariantLoadable;
\r
1626 /* Bizarre ICC game */
\r
1627 v = VariantTwoKings;
\r
1630 v = VariantKriegspiel;
\r
1633 v = VariantLosers;
\r
1636 v = VariantFischeRandom;
\r
1639 v = VariantCrazyhouse;
\r
1642 v = VariantBughouse;
\r
1645 v = Variant3Check;
\r
1648 /* Not quite the same as FICS suicide! */
\r
1649 v = VariantGiveaway;
\r
1652 v = VariantAtomic;
\r
1655 v = VariantShatranj;
\r
1658 /* Temporary names for future ICC types. The name *will* change in
\r
1659 the next xboard/WinBoard release after ICC defines it. */
\r
1688 v = VariantXiangqi;
\r
1691 v = VariantCourier;
\r
1694 v = VariantGothic;
\r
1697 v = VariantCapablanca;
\r
1700 v = VariantKnightmate;
\r
1706 v = VariantCylinder;
\r
1709 v = VariantFalcon;
\r
1712 v = VariantCapaRandom;
\r
1715 v = VariantBerolina;
\r
1727 /* Found "wild" or "w" in the string but no number;
\r
1728 must assume it's normal chess. */
\r
1729 v = VariantNormal;
\r
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1733 DisplayError(buf, 0);
\r
1734 v = VariantUnknown;
\r
1739 if (appData.debugMode) {
\r
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1741 e, wnum, VariantName(v));
\r
1746 static int leftover_start = 0, leftover_len = 0;
\r
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1750 advance *index beyond it, and set leftover_start to the new value of
\r
1751 *index; else return FALSE. If pattern contains the character '*', it
\r
1752 matches any sequence of characters not containing '\r', '\n', or the
\r
1753 character following the '*' (if any), and the matched sequence(s) are
\r
1754 copied into star_match.
\r
1757 looking_at(buf, index, pattern)
\r
1762 char *bufp = &buf[*index], *patternp = pattern;
\r
1763 int star_count = 0;
\r
1764 char *matchp = star_match[0];
\r
1767 if (*patternp == NULLCHAR) {
\r
1768 *index = leftover_start = bufp - buf;
\r
1769 *matchp = NULLCHAR;
\r
1772 if (*bufp == NULLCHAR) return FALSE;
\r
1773 if (*patternp == '*') {
\r
1774 if (*bufp == *(patternp + 1)) {
\r
1775 *matchp = NULLCHAR;
\r
1776 matchp = star_match[++star_count];
\r
1780 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1782 if (*patternp == NULLCHAR)
\r
1787 *matchp++ = *bufp++;
\r
1791 if (*patternp != *bufp) return FALSE;
\r
1798 SendToPlayer(data, length)
\r
1802 int error, outCount;
\r
1803 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1804 if (outCount < length) {
\r
1805 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1810 PackHolding(packed, holding)
\r
1814 char *p = holding;
\r
1816 int runlength = 0;
\r
1822 switch (runlength) {
\r
1833 sprintf(q, "%d", runlength);
\r
1845 /* Telnet protocol requests from the front end */
\r
1847 TelnetRequest(ddww, option)
\r
1848 unsigned char ddww, option;
\r
1850 unsigned char msg[3];
\r
1851 int outCount, outError;
\r
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1855 if (appData.debugMode) {
\r
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1872 sprintf(buf1, "%d", ddww);
\r
1877 optionStr = "ECHO";
\r
1881 sprintf(buf2, "%d", option);
\r
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1890 if (outCount < 3) {
\r
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1898 if (!appData.icsActive) return;
\r
1899 TelnetRequest(TN_DO, TN_ECHO);
\r
1905 if (!appData.icsActive) return;
\r
1906 TelnetRequest(TN_DONT, TN_ECHO);
\r
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1912 /* put the holdings sent to us by the server on the board holdings area */
\r
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1915 ChessSquare piece;
\r
1917 if(gameInfo.holdingsWidth < 2) return;
\r
1919 if( (int)lowestPiece >= BlackPawn ) {
\r
1920 holdingsColumn = 0;
\r
1922 holdingsStartRow = BOARD_HEIGHT-1;
\r
1925 holdingsColumn = BOARD_WIDTH-1;
\r
1926 countsColumn = BOARD_WIDTH-2;
\r
1927 holdingsStartRow = 0;
\r
1931 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1932 board[i][holdingsColumn] = EmptySquare;
\r
1933 board[i][countsColumn] = (ChessSquare) 0;
\r
1935 while( (p=*holdings++) != NULLCHAR ) {
\r
1936 piece = CharToPiece( ToUpper(p) );
\r
1937 if(piece == EmptySquare) continue;
\r
1938 /*j = (int) piece - (int) WhitePawn;*/
\r
1939 j = PieceToNumber(piece);
\r
1940 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1941 if(j < 0) continue; /* should not happen */
\r
1942 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1943 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1944 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1951 VariantSwitch(Board board, VariantClass newVariant)
\r
1953 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1954 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1955 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1957 startedFromPositionFile = FALSE;
\r
1958 if(gameInfo.variant == newVariant) return;
\r
1960 /* [HGM] This routine is called each time an assignment is made to
\r
1961 * gameInfo.variant during a game, to make sure the board sizes
\r
1962 * are set to match the new variant. If that means adding or deleting
\r
1963 * holdings, we shift the playing board accordingly
\r
1964 * This kludge is needed because in ICS observe mode, we get boards
\r
1965 * of an ongoing game without knowing the variant, and learn about the
\r
1966 * latter only later. This can be because of the move list we requested,
\r
1967 * in which case the game history is refilled from the beginning anyway,
\r
1968 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1969 * case we want to add those holdings to the already received position.
\r
1973 if (appData.debugMode) {
\r
1974 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1975 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1976 setbuf(debugFP, NULL);
\r
1978 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1979 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1980 switch(newVariant) {
\r
1981 case VariantShogi:
\r
1982 newWidth = 9; newHeight = 9;
\r
1983 gameInfo.holdingsSize = 7;
\r
1984 case VariantBughouse:
\r
1985 case VariantCrazyhouse:
\r
1986 newHoldingsWidth = 2; break;
\r
1988 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1991 if(newWidth != gameInfo.boardWidth ||
\r
1992 newHeight != gameInfo.boardHeight ||
\r
1993 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1995 /* shift position to new playing area, if needed */
\r
1996 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1997 for(i=0; i<BOARD_HEIGHT; i++)
\r
1998 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1999 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2001 for(i=0; i<newHeight; i++) {
\r
2002 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2003 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2005 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2006 for(i=0; i<BOARD_HEIGHT; i++)
\r
2007 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2008 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2012 gameInfo.boardWidth = newWidth;
\r
2013 gameInfo.boardHeight = newHeight;
\r
2014 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2015 gameInfo.variant = newVariant;
\r
2016 InitDrawingSizes(-2, 0);
\r
2018 /* [HGM] The following should definitely be solved in a better way */
\r
2020 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2021 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2022 saveEP = epStatus[0];
\r
2024 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2026 epStatus[0] = saveEP;
\r
2027 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2028 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2030 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2032 forwardMostMove = oldForwardMostMove;
\r
2033 backwardMostMove = oldBackwardMostMove;
\r
2034 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2037 static int loggedOn = FALSE;
\r
2039 /*-- Game start info cache: --*/
\r
2041 char gs_kind[MSG_SIZ];
\r
2042 static char player1Name[128] = "";
\r
2043 static char player2Name[128] = "";
\r
2044 static int player1Rating = -1;
\r
2045 static int player2Rating = -1;
\r
2046 /*----------------------------*/
\r
2048 ColorClass curColor = ColorNormal;
\r
2049 int suppressKibitz = 0;
\r
2052 read_from_ics(isr, closure, data, count, error)
\r
2053 InputSourceRef isr;
\r
2059 #define BUF_SIZE 8192
\r
2060 #define STARTED_NONE 0
\r
2061 #define STARTED_MOVES 1
\r
2062 #define STARTED_BOARD 2
\r
2063 #define STARTED_OBSERVE 3
\r
2064 #define STARTED_HOLDINGS 4
\r
2065 #define STARTED_CHATTER 5
\r
2066 #define STARTED_COMMENT 6
\r
2067 #define STARTED_MOVES_NOHIDE 7
\r
2069 static int started = STARTED_NONE;
\r
2070 static char parse[20000];
\r
2071 static int parse_pos = 0;
\r
2072 static char buf[BUF_SIZE + 1];
\r
2073 static int firstTime = TRUE, intfSet = FALSE;
\r
2074 static ColorClass prevColor = ColorNormal;
\r
2075 static int savingComment = FALSE;
\r
2081 int backup; /* [DM] For zippy color lines */
\r
2084 if (appData.debugMode) {
\r
2086 fprintf(debugFP, "<ICS: ");
\r
2087 show_bytes(debugFP, data, count);
\r
2088 fprintf(debugFP, "\n");
\r
2092 if (appData.debugMode) { int f = forwardMostMove;
\r
2093 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2094 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2097 /* If last read ended with a partial line that we couldn't parse,
\r
2098 prepend it to the new read and try again. */
\r
2099 if (leftover_len > 0) {
\r
2100 for (i=0; i<leftover_len; i++)
\r
2101 buf[i] = buf[leftover_start + i];
\r
2104 /* Copy in new characters, removing nulls and \r's */
\r
2105 buf_len = leftover_len;
\r
2106 for (i = 0; i < count; i++) {
\r
2107 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2108 buf[buf_len++] = data[i];
\r
2109 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2110 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2111 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2114 buf[buf_len] = NULLCHAR;
\r
2115 next_out = leftover_len;
\r
2116 leftover_start = 0;
\r
2119 while (i < buf_len) {
\r
2120 /* Deal with part of the TELNET option negotiation
\r
2121 protocol. We refuse to do anything beyond the
\r
2122 defaults, except that we allow the WILL ECHO option,
\r
2123 which ICS uses to turn off password echoing when we are
\r
2124 directly connected to it. We reject this option
\r
2125 if localLineEditing mode is on (always on in xboard)
\r
2126 and we are talking to port 23, which might be a real
\r
2127 telnet server that will try to keep WILL ECHO on permanently.
\r
2129 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2130 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2131 unsigned char option;
\r
2133 switch ((unsigned char) buf[++i]) {
\r
2135 if (appData.debugMode)
\r
2136 fprintf(debugFP, "\n<WILL ");
\r
2137 switch (option = (unsigned char) buf[++i]) {
\r
2139 if (appData.debugMode)
\r
2140 fprintf(debugFP, "ECHO ");
\r
2141 /* Reply only if this is a change, according
\r
2142 to the protocol rules. */
\r
2143 if (remoteEchoOption) break;
\r
2144 if (appData.localLineEditing &&
\r
2145 atoi(appData.icsPort) == TN_PORT) {
\r
2146 TelnetRequest(TN_DONT, TN_ECHO);
\r
2149 TelnetRequest(TN_DO, TN_ECHO);
\r
2150 remoteEchoOption = TRUE;
\r
2154 if (appData.debugMode)
\r
2155 fprintf(debugFP, "%d ", option);
\r
2156 /* Whatever this is, we don't want it. */
\r
2157 TelnetRequest(TN_DONT, option);
\r
2162 if (appData.debugMode)
\r
2163 fprintf(debugFP, "\n<WONT ");
\r
2164 switch (option = (unsigned char) buf[++i]) {
\r
2166 if (appData.debugMode)
\r
2167 fprintf(debugFP, "ECHO ");
\r
2168 /* Reply only if this is a change, according
\r
2169 to the protocol rules. */
\r
2170 if (!remoteEchoOption) break;
\r
2172 TelnetRequest(TN_DONT, TN_ECHO);
\r
2173 remoteEchoOption = FALSE;
\r
2176 if (appData.debugMode)
\r
2177 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2178 /* Whatever this is, it must already be turned
\r
2179 off, because we never agree to turn on
\r
2180 anything non-default, so according to the
\r
2181 protocol rules, we don't reply. */
\r
2186 if (appData.debugMode)
\r
2187 fprintf(debugFP, "\n<DO ");
\r
2188 switch (option = (unsigned char) buf[++i]) {
\r
2190 /* Whatever this is, we refuse to do it. */
\r
2191 if (appData.debugMode)
\r
2192 fprintf(debugFP, "%d ", option);
\r
2193 TelnetRequest(TN_WONT, option);
\r
2198 if (appData.debugMode)
\r
2199 fprintf(debugFP, "\n<DONT ");
\r
2200 switch (option = (unsigned char) buf[++i]) {
\r
2202 if (appData.debugMode)
\r
2203 fprintf(debugFP, "%d ", option);
\r
2204 /* Whatever this is, we are already not doing
\r
2205 it, because we never agree to do anything
\r
2206 non-default, so according to the protocol
\r
2207 rules, we don't reply. */
\r
2212 if (appData.debugMode)
\r
2213 fprintf(debugFP, "\n<IAC ");
\r
2214 /* Doubled IAC; pass it through */
\r
2218 if (appData.debugMode)
\r
2219 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2220 /* Drop all other telnet commands on the floor */
\r
2223 if (oldi > next_out)
\r
2224 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2225 if (++i > next_out)
\r
2230 /* OK, this at least will *usually* work */
\r
2231 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2235 if (loggedOn && !intfSet) {
\r
2236 if (ics_type == ICS_ICC) {
\r
2238 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2241 } else if (ics_type == ICS_CHESSNET) {
\r
2242 sprintf(str, "/style 12\n");
\r
2244 strcpy(str, "alias $ @\n$set interface ");
\r
2245 strcat(str, programVersion);
\r
2246 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2248 strcat(str, "$iset nohighlight 1\n");
\r
2250 strcat(str, "$iset lock 1\n$style 12\n");
\r
2256 if (started == STARTED_COMMENT) {
\r
2257 /* Accumulate characters in comment */
\r
2258 parse[parse_pos++] = buf[i];
\r
2259 if (buf[i] == '\n') {
\r
2260 parse[parse_pos] = NULLCHAR;
\r
2261 if(!suppressKibitz) // [HGM] kibitz
\r
2262 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2263 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2264 int nrDigit = 0, nrAlph = 0, i;
\r
2265 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2266 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2267 parse[parse_pos] = NULLCHAR;
\r
2268 // try to be smart: if it does not look like search info, it should go to
\r
2269 // ICS interaction window after all, not to engine-output window.
\r
2270 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2271 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2272 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2273 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2275 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2276 OutputKibitz(suppressKibitz, parse);
\r
2278 char tmp[MSG_SIZ];
\r
2279 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2280 SendToPlayer(tmp, strlen(tmp));
\r
2283 started = STARTED_NONE;
\r
2285 /* Don't match patterns against characters in chatter */
\r
2290 if (started == STARTED_CHATTER) {
\r
2291 if (buf[i] != '\n') {
\r
2292 /* Don't match patterns against characters in chatter */
\r
2296 started = STARTED_NONE;
\r
2299 /* Kludge to deal with rcmd protocol */
\r
2300 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2301 DisplayFatalError(&buf[1], 0, 1);
\r
2304 firstTime = FALSE;
\r
2307 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2308 ics_type = ICS_ICC;
\r
2310 if (appData.debugMode)
\r
2311 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2314 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2315 ics_type = ICS_FICS;
\r
2317 if (appData.debugMode)
\r
2318 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2321 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2322 ics_type = ICS_CHESSNET;
\r
2324 if (appData.debugMode)
\r
2325 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2330 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2331 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2332 looking_at(buf, &i, "will be \"*\""))) {
\r
2333 strcpy(ics_handle, star_match[0]);
\r
2337 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2338 char buf[MSG_SIZ];
\r
2339 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2340 DisplayIcsInteractionTitle(buf);
\r
2341 have_set_title = TRUE;
\r
2344 /* skip finger notes */
\r
2345 if (started == STARTED_NONE &&
\r
2346 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2347 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2348 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2349 started = STARTED_CHATTER;
\r
2354 /* skip formula vars */
\r
2355 if (started == STARTED_NONE &&
\r
2356 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2357 started = STARTED_CHATTER;
\r
2363 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2364 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2365 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2366 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2367 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2368 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2369 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2370 suppressKibitz = TRUE;
\r
2371 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2372 && (gameMode == IcsPlayingWhite)) ||
\r
2373 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2374 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2375 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2377 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2378 parse_pos = 0; parse[0] = NULLCHAR;
\r
2379 savingComment = TRUE;
\r
2380 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2381 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2385 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2386 started = STARTED_CHATTER;
\r
2387 suppressKibitz = TRUE;
\r
2389 } // [HGM] kibitz: end of patch
\r
2391 if (appData.zippyTalk || appData.zippyPlay) {
\r
2392 /* [DM] Backup address for color zippy lines */
\r
2396 if (loggedOn == TRUE)
\r
2397 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2398 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2400 if (ZippyControl(buf, &i) ||
\r
2401 ZippyConverse(buf, &i) ||
\r
2402 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2404 if (!appData.colorize) continue;
\r
2408 } // [DM] 'else { ' deleted
\r
2409 if (/* Don't color "message" or "messages" output */
\r
2410 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2411 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2412 looking_at(buf, &i, "--* (*:*): ") ||
\r
2413 /* Regular tells and says */
\r
2414 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2415 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2416 looking_at(buf, &i, "* says: ") ||
\r
2417 /* Message notifications (same color as tells) */
\r
2418 looking_at(buf, &i, "* has left a message ") ||
\r
2419 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2420 /* Whispers and kibitzes */
\r
2421 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2422 looking_at(buf, &i, "* kibitzes: ") ||
\r
2423 /* Channel tells */
\r
2424 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2426 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2427 /* Avoid "tells you:" spoofs in channels */
\r
2430 if (star_match[0][0] == NULLCHAR ||
\r
2431 strchr(star_match[0], ' ') ||
\r
2432 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2433 /* Reject bogus matches */
\r
2436 if (appData.colorize) {
\r
2437 if (oldi > next_out) {
\r
2438 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2443 Colorize(ColorTell, FALSE);
\r
2444 curColor = ColorTell;
\r
2447 Colorize(ColorKibitz, FALSE);
\r
2448 curColor = ColorKibitz;
\r
2451 p = strrchr(star_match[1], '(');
\r
2453 p = star_match[1];
\r
2457 if (atoi(p) == 1) {
\r
2458 Colorize(ColorChannel1, FALSE);
\r
2459 curColor = ColorChannel1;
\r
2461 Colorize(ColorChannel, FALSE);
\r
2462 curColor = ColorChannel;
\r
2466 curColor = ColorNormal;
\r
2470 if (started == STARTED_NONE && appData.autoComment &&
\r
2471 (gameMode == IcsObserving ||
\r
2472 gameMode == IcsPlayingWhite ||
\r
2473 gameMode == IcsPlayingBlack)) {
\r
2474 parse_pos = i - oldi;
\r
2475 memcpy(parse, &buf[oldi], parse_pos);
\r
2476 parse[parse_pos] = NULLCHAR;
\r
2477 started = STARTED_COMMENT;
\r
2478 savingComment = TRUE;
\r
2480 started = STARTED_CHATTER;
\r
2481 savingComment = FALSE;
\r
2488 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2489 looking_at(buf, &i, "* c-shouts: ")) {
\r
2490 if (appData.colorize) {
\r
2491 if (oldi > next_out) {
\r
2492 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2495 Colorize(ColorSShout, FALSE);
\r
2496 curColor = ColorSShout;
\r
2499 started = STARTED_CHATTER;
\r
2503 if (looking_at(buf, &i, "--->")) {
\r
2508 if (looking_at(buf, &i, "* shouts: ") ||
\r
2509 looking_at(buf, &i, "--> ")) {
\r
2510 if (appData.colorize) {
\r
2511 if (oldi > next_out) {
\r
2512 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2515 Colorize(ColorShout, FALSE);
\r
2516 curColor = ColorShout;
\r
2519 started = STARTED_CHATTER;
\r
2523 if (looking_at( buf, &i, "Challenge:")) {
\r
2524 if (appData.colorize) {
\r
2525 if (oldi > next_out) {
\r
2526 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2529 Colorize(ColorChallenge, FALSE);
\r
2530 curColor = ColorChallenge;
\r
2536 if (looking_at(buf, &i, "* offers you") ||
\r
2537 looking_at(buf, &i, "* offers to be") ||
\r
2538 looking_at(buf, &i, "* would like to") ||
\r
2539 looking_at(buf, &i, "* requests to") ||
\r
2540 looking_at(buf, &i, "Your opponent offers") ||
\r
2541 looking_at(buf, &i, "Your opponent requests")) {
\r
2543 if (appData.colorize) {
\r
2544 if (oldi > next_out) {
\r
2545 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2548 Colorize(ColorRequest, FALSE);
\r
2549 curColor = ColorRequest;
\r
2554 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2555 if (appData.colorize) {
\r
2556 if (oldi > next_out) {
\r
2557 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2560 Colorize(ColorSeek, FALSE);
\r
2561 curColor = ColorSeek;
\r
2566 if (looking_at(buf, &i, "\\ ")) {
\r
2567 if (prevColor != ColorNormal) {
\r
2568 if (oldi > next_out) {
\r
2569 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2572 Colorize(prevColor, TRUE);
\r
2573 curColor = prevColor;
\r
2575 if (savingComment) {
\r
2576 parse_pos = i - oldi;
\r
2577 memcpy(parse, &buf[oldi], parse_pos);
\r
2578 parse[parse_pos] = NULLCHAR;
\r
2579 started = STARTED_COMMENT;
\r
2581 started = STARTED_CHATTER;
\r
2586 if (looking_at(buf, &i, "Black Strength :") ||
\r
2587 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2588 looking_at(buf, &i, "<10>") ||
\r
2589 looking_at(buf, &i, "#@#")) {
\r
2590 /* Wrong board style */
\r
2592 SendToICS(ics_prefix);
\r
2593 SendToICS("set style 12\n");
\r
2594 SendToICS(ics_prefix);
\r
2595 SendToICS("refresh\n");
\r
2599 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2601 have_sent_ICS_logon = 1;
\r
2605 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2606 (looking_at(buf, &i, "\n<12> ") ||
\r
2607 looking_at(buf, &i, "<12> "))) {
\r
2609 if (oldi > next_out) {
\r
2610 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2613 started = STARTED_BOARD;
\r
2618 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2619 looking_at(buf, &i, "<b1> ")) {
\r
2620 if (oldi > next_out) {
\r
2621 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2624 started = STARTED_HOLDINGS;
\r
2629 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2631 /* Header for a move list -- first line */
\r
2633 switch (ics_getting_history) {
\r
2635 switch (gameMode) {
\r
2637 case BeginningOfGame:
\r
2638 /* User typed "moves" or "oldmoves" while we
\r
2639 were idle. Pretend we asked for these
\r
2640 moves and soak them up so user can step
\r
2641 through them and/or save them.
\r
2643 Reset(FALSE, TRUE);
\r
2644 gameMode = IcsObserving;
\r
2647 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2649 case EditGame: /*?*/
\r
2650 case EditPosition: /*?*/
\r
2651 /* Should above feature work in these modes too? */
\r
2652 /* For now it doesn't */
\r
2653 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2656 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2661 /* Is this the right one? */
\r
2662 if (gameInfo.white && gameInfo.black &&
\r
2663 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2664 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2666 ics_getting_history = H_GOT_REQ_HEADER;
\r
2669 case H_GOT_REQ_HEADER:
\r
2670 case H_GOT_UNREQ_HEADER:
\r
2671 case H_GOT_UNWANTED_HEADER:
\r
2672 case H_GETTING_MOVES:
\r
2673 /* Should not happen */
\r
2674 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2675 ics_getting_history = H_FALSE;
\r
2679 /* Save player ratings into gameInfo if needed */
\r
2680 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2681 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2682 (gameInfo.whiteRating == -1 ||
\r
2683 gameInfo.blackRating == -1)) {
\r
2685 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2686 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2687 if (appData.debugMode)
\r
2688 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2689 gameInfo.whiteRating, gameInfo.blackRating);
\r
2694 if (looking_at(buf, &i,
\r
2695 "* * match, initial time: * minute*, increment: * second")) {
\r
2696 /* Header for a move list -- second line */
\r
2697 /* Initial board will follow if this is a wild game */
\r
2698 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2699 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2700 gameInfo.event = StrSave(str);
\r
2701 /* [HGM] we switched variant. Translate boards if needed. */
\r
2702 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2706 if (looking_at(buf, &i, "Move ")) {
\r
2707 /* Beginning of a move list */
\r
2708 switch (ics_getting_history) {
\r
2710 /* Normally should not happen */
\r
2711 /* Maybe user hit reset while we were parsing */
\r
2714 /* Happens if we are ignoring a move list that is not
\r
2715 * the one we just requested. Common if the user
\r
2716 * tries to observe two games without turning off
\r
2719 case H_GETTING_MOVES:
\r
2720 /* Should not happen */
\r
2721 DisplayError(_("Error gathering move list: nested"), 0);
\r
2722 ics_getting_history = H_FALSE;
\r
2724 case H_GOT_REQ_HEADER:
\r
2725 ics_getting_history = H_GETTING_MOVES;
\r
2726 started = STARTED_MOVES;
\r
2728 if (oldi > next_out) {
\r
2729 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2732 case H_GOT_UNREQ_HEADER:
\r
2733 ics_getting_history = H_GETTING_MOVES;
\r
2734 started = STARTED_MOVES_NOHIDE;
\r
2737 case H_GOT_UNWANTED_HEADER:
\r
2738 ics_getting_history = H_FALSE;
\r
2744 if (looking_at(buf, &i, "% ") ||
\r
2745 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2746 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2747 savingComment = FALSE;
\r
2748 switch (started) {
\r
2749 case STARTED_MOVES:
\r
2750 case STARTED_MOVES_NOHIDE:
\r
2751 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2752 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2753 ParseGameHistory(parse);
\r
2755 if (appData.zippyPlay && first.initDone) {
\r
2756 FeedMovesToProgram(&first, forwardMostMove);
\r
2757 if (gameMode == IcsPlayingWhite) {
\r
2758 if (WhiteOnMove(forwardMostMove)) {
\r
2759 if (first.sendTime) {
\r
2760 if (first.useColors) {
\r
2761 SendToProgram("black\n", &first);
\r
2763 SendTimeRemaining(&first, TRUE);
\r
2766 if (first.useColors) {
\r
2767 SendToProgram("white\ngo\n", &first);
\r
2769 SendToProgram("go\n", &first);
\r
2772 if (first.useColors) {
\r
2773 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2775 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2777 first.maybeThinking = TRUE;
\r
2779 if (first.usePlayother) {
\r
2780 if (first.sendTime) {
\r
2781 SendTimeRemaining(&first, TRUE);
\r
2783 SendToProgram("playother\n", &first);
\r
2784 firstMove = FALSE;
\r
2789 } else if (gameMode == IcsPlayingBlack) {
\r
2790 if (!WhiteOnMove(forwardMostMove)) {
\r
2791 if (first.sendTime) {
\r
2792 if (first.useColors) {
\r
2793 SendToProgram("white\n", &first);
\r
2795 SendTimeRemaining(&first, FALSE);
\r
2798 if (first.useColors) {
\r
2799 SendToProgram("black\ngo\n", &first);
\r
2801 SendToProgram("go\n", &first);
\r
2804 if (first.useColors) {
\r
2805 SendToProgram("black\n", &first);
\r
2807 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2809 first.maybeThinking = TRUE;
\r
2811 if (first.usePlayother) {
\r
2812 if (first.sendTime) {
\r
2813 SendTimeRemaining(&first, FALSE);
\r
2815 SendToProgram("playother\n", &first);
\r
2816 firstMove = FALSE;
\r
2824 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2825 /* Moves came from oldmoves or moves command
\r
2826 while we weren't doing anything else.
\r
2828 currentMove = forwardMostMove;
\r
2829 ClearHighlights();/*!!could figure this out*/
\r
2830 flipView = appData.flipView;
\r
2831 DrawPosition(FALSE, boards[currentMove]);
\r
2832 DisplayBothClocks();
\r
2833 sprintf(str, "%s vs. %s",
\r
2834 gameInfo.white, gameInfo.black);
\r
2835 DisplayTitle(str);
\r
2836 gameMode = IcsIdle;
\r
2838 /* Moves were history of an active game */
\r
2839 if (gameInfo.resultDetails != NULL) {
\r
2840 free(gameInfo.resultDetails);
\r
2841 gameInfo.resultDetails = NULL;
\r
2844 HistorySet(parseList, backwardMostMove,
\r
2845 forwardMostMove, currentMove-1);
\r
2846 DisplayMove(currentMove - 1);
\r
2847 if (started == STARTED_MOVES) next_out = i;
\r
2848 started = STARTED_NONE;
\r
2849 ics_getting_history = H_FALSE;
\r
2852 case STARTED_OBSERVE:
\r
2853 started = STARTED_NONE;
\r
2854 SendToICS(ics_prefix);
\r
2855 SendToICS("refresh\n");
\r
2861 if(bookHit) { // [HGM] book: simulate book reply
\r
2862 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2864 programStats.nodes = programStats.depth = programStats.time =
\r
2865 programStats.score = programStats.got_only_move = 0;
\r
2866 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2868 strcpy(bookMove, "move ");
\r
2869 strcat(bookMove, bookHit);
\r
2870 HandleMachineMove(bookMove, &first);
\r
2875 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2876 started == STARTED_HOLDINGS ||
\r
2877 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2878 /* Accumulate characters in move list or board */
\r
2879 parse[parse_pos++] = buf[i];
\r
2882 /* Start of game messages. Mostly we detect start of game
\r
2883 when the first board image arrives. On some versions
\r
2884 of the ICS, though, we need to do a "refresh" after starting
\r
2885 to observe in order to get the current board right away. */
\r
2886 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2887 started = STARTED_OBSERVE;
\r
2891 /* Handle auto-observe */
\r
2892 if (appData.autoObserve &&
\r
2893 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2894 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2896 /* Choose the player that was highlighted, if any. */
\r
2897 if (star_match[0][0] == '\033' ||
\r
2898 star_match[1][0] != '\033') {
\r
2899 player = star_match[0];
\r
2901 player = star_match[2];
\r
2903 sprintf(str, "%sobserve %s\n",
\r
2904 ics_prefix, StripHighlightAndTitle(player));
\r
2907 /* Save ratings from notify string */
\r
2908 strcpy(player1Name, star_match[0]);
\r
2909 player1Rating = string_to_rating(star_match[1]);
\r
2910 strcpy(player2Name, star_match[2]);
\r
2911 player2Rating = string_to_rating(star_match[3]);
\r
2913 if (appData.debugMode)
\r
2915 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2916 player1Name, player1Rating,
\r
2917 player2Name, player2Rating);
\r
2922 /* Deal with automatic examine mode after a game,
\r
2923 and with IcsObserving -> IcsExamining transition */
\r
2924 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2925 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2927 int gamenum = atoi(star_match[0]);
\r
2928 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2929 gamenum == ics_gamenum) {
\r
2930 /* We were already playing or observing this game;
\r
2931 no need to refetch history */
\r
2932 gameMode = IcsExamining;
\r
2934 pauseExamForwardMostMove = forwardMostMove;
\r
2935 } else if (currentMove < forwardMostMove) {
\r
2936 ForwardInner(forwardMostMove);
\r
2939 /* I don't think this case really can happen */
\r
2940 SendToICS(ics_prefix);
\r
2941 SendToICS("refresh\n");
\r
2946 /* Error messages */
\r
2947 if (ics_user_moved) {
\r
2948 if (looking_at(buf, &i, "Illegal move") ||
\r
2949 looking_at(buf, &i, "Not a legal move") ||
\r
2950 looking_at(buf, &i, "Your king is in check") ||
\r
2951 looking_at(buf, &i, "It isn't your turn") ||
\r
2952 looking_at(buf, &i, "It is not your move")) {
\r
2953 /* Illegal move */
\r
2954 ics_user_moved = 0;
\r
2955 if (forwardMostMove > backwardMostMove) {
\r
2956 currentMove = --forwardMostMove;
\r
2957 DisplayMove(currentMove - 1); /* before DMError */
\r
2958 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2959 DrawPosition(FALSE, boards[currentMove]);
\r
2961 DisplayBothClocks();
\r
2967 if (looking_at(buf, &i, "still have time") ||
\r
2968 looking_at(buf, &i, "not out of time") ||
\r
2969 looking_at(buf, &i, "either player is out of time") ||
\r
2970 looking_at(buf, &i, "has timeseal; checking")) {
\r
2971 /* We must have called his flag a little too soon */
\r
2972 whiteFlag = blackFlag = FALSE;
\r
2976 if (looking_at(buf, &i, "added * seconds to") ||
\r
2977 looking_at(buf, &i, "seconds were added to")) {
\r
2978 /* Update the clocks */
\r
2979 SendToICS(ics_prefix);
\r
2980 SendToICS("refresh\n");
\r
2984 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2985 ics_clock_paused = TRUE;
\r
2990 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2991 ics_clock_paused = FALSE;
\r
2996 /* Grab player ratings from the Creating: message.
\r
2997 Note we have to check for the special case when
\r
2998 the ICS inserts things like [white] or [black]. */
\r
2999 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3000 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3002 0 player 1 name (not necessarily white)
\r
3004 2 empty, white, or black (IGNORED)
\r
3005 3 player 2 name (not necessarily black)
\r
3008 The names/ratings are sorted out when the game
\r
3009 actually starts (below).
\r
3011 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3012 player1Rating = string_to_rating(star_match[1]);
\r
3013 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3014 player2Rating = string_to_rating(star_match[4]);
\r
3016 if (appData.debugMode)
\r
3018 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3019 player1Name, player1Rating,
\r
3020 player2Name, player2Rating);
\r
3025 /* Improved generic start/end-of-game messages */
\r
3026 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3027 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3028 /* If tkind == 0: */
\r
3029 /* star_match[0] is the game number */
\r
3030 /* [1] is the white player's name */
\r
3031 /* [2] is the black player's name */
\r
3032 /* For end-of-game: */
\r
3033 /* [3] is the reason for the game end */
\r
3034 /* [4] is a PGN end game-token, preceded by " " */
\r
3035 /* For start-of-game: */
\r
3036 /* [3] begins with "Creating" or "Continuing" */
\r
3037 /* [4] is " *" or empty (don't care). */
\r
3038 int gamenum = atoi(star_match[0]);
\r
3039 char *whitename, *blackname, *why, *endtoken;
\r
3040 ChessMove endtype = (ChessMove) 0;
\r
3043 whitename = star_match[1];
\r
3044 blackname = star_match[2];
\r
3045 why = star_match[3];
\r
3046 endtoken = star_match[4];
\r
3048 whitename = star_match[1];
\r
3049 blackname = star_match[3];
\r
3050 why = star_match[5];
\r
3051 endtoken = star_match[6];
\r
3054 /* Game start messages */
\r
3055 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3056 strncmp(why, "Continuing ", 11) == 0) {
\r
3057 gs_gamenum = gamenum;
\r
3058 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3060 if (appData.zippyPlay) {
\r
3061 ZippyGameStart(whitename, blackname);
\r
3067 /* Game end messages */
\r
3068 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3069 ics_gamenum != gamenum) {
\r
3072 while (endtoken[0] == ' ') endtoken++;
\r
3073 switch (endtoken[0]) {
\r
3076 endtype = GameUnfinished;
\r
3079 endtype = BlackWins;
\r
3082 if (endtoken[1] == '/')
\r
3083 endtype = GameIsDrawn;
\r
3085 endtype = WhiteWins;
\r
3088 GameEnds(endtype, why, GE_ICS);
\r
3090 if (appData.zippyPlay && first.initDone) {
\r
3091 ZippyGameEnd(endtype, why);
\r
3092 if (first.pr == NULL) {
\r
3093 /* Start the next process early so that we'll
\r
3094 be ready for the next challenge */
\r
3095 StartChessProgram(&first);
\r
3097 /* Send "new" early, in case this command takes
\r
3098 a long time to finish, so that we'll be ready
\r
3099 for the next challenge. */
\r
3100 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3101 Reset(TRUE, TRUE);
\r
3107 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3108 looking_at(buf, &i, "no longer observing game *") ||
\r
3109 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3110 if (gameMode == IcsObserving &&
\r
3111 atoi(star_match[0]) == ics_gamenum)
\r
3113 /* icsEngineAnalyze */
\r
3114 if (appData.icsEngineAnalyze) {
\r
3115 ExitAnalyzeMode();
\r
3119 gameMode = IcsIdle;
\r
3121 ics_user_moved = FALSE;
\r
3126 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3127 if (gameMode == IcsExamining &&
\r
3128 atoi(star_match[0]) == ics_gamenum)
\r
3130 gameMode = IcsIdle;
\r
3132 ics_user_moved = FALSE;
\r
3137 /* Advance leftover_start past any newlines we find,
\r
3138 so only partial lines can get reparsed */
\r
3139 if (looking_at(buf, &i, "\n")) {
\r
3140 prevColor = curColor;
\r
3141 if (curColor != ColorNormal) {
\r
3142 if (oldi > next_out) {
\r
3143 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3146 Colorize(ColorNormal, FALSE);
\r
3147 curColor = ColorNormal;
\r
3149 if (started == STARTED_BOARD) {
\r
3150 started = STARTED_NONE;
\r
3151 parse[parse_pos] = NULLCHAR;
\r
3152 ParseBoard12(parse);
\r
3153 ics_user_moved = 0;
\r
3155 /* Send premove here */
\r
3156 if (appData.premove) {
\r
3157 char str[MSG_SIZ];
\r
3158 if (currentMove == 0 &&
\r
3159 gameMode == IcsPlayingWhite &&
\r
3160 appData.premoveWhite) {
\r
3161 sprintf(str, "%s%s\n", ics_prefix,
\r
3162 appData.premoveWhiteText);
\r
3163 if (appData.debugMode)
\r
3164 fprintf(debugFP, "Sending premove:\n");
\r
3166 } else if (currentMove == 1 &&
\r
3167 gameMode == IcsPlayingBlack &&
\r
3168 appData.premoveBlack) {
\r
3169 sprintf(str, "%s%s\n", ics_prefix,
\r
3170 appData.premoveBlackText);
\r
3171 if (appData.debugMode)
\r
3172 fprintf(debugFP, "Sending premove:\n");
\r
3174 } else if (gotPremove) {
\r
3176 ClearPremoveHighlights();
\r
3177 if (appData.debugMode)
\r
3178 fprintf(debugFP, "Sending premove:\n");
\r
3179 UserMoveEvent(premoveFromX, premoveFromY,
\r
3180 premoveToX, premoveToY,
\r
3181 premovePromoChar);
\r
3185 /* Usually suppress following prompt */
\r
3186 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3187 if (looking_at(buf, &i, "*% ")) {
\r
3188 savingComment = FALSE;
\r
3192 } else if (started == STARTED_HOLDINGS) {
\r
3194 char new_piece[MSG_SIZ];
\r
3195 started = STARTED_NONE;
\r
3196 parse[parse_pos] = NULLCHAR;
\r
3197 if (appData.debugMode)
\r
3198 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3199 parse, currentMove);
\r
3200 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3201 gamenum == ics_gamenum) {
\r
3202 if (gameInfo.variant == VariantNormal) {
\r
3203 /* [HGM] We seem to switch variant during a game!
\r
3204 * Presumably no holdings were displayed, so we have
\r
3205 * to move the position two files to the right to
\r
3206 * create room for them!
\r
3208 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3209 /* Get a move list just to see the header, which
\r
3210 will tell us whether this is really bug or zh */
\r
3211 if (ics_getting_history == H_FALSE) {
\r
3212 ics_getting_history = H_REQUESTED;
\r
3213 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3217 new_piece[0] = NULLCHAR;
\r
3218 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3219 &gamenum, white_holding, black_holding,
\r
3221 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3222 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3223 /* [HGM] copy holdings to board holdings area */
\r
3224 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3225 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3227 if (appData.zippyPlay && first.initDone) {
\r
3228 ZippyHoldings(white_holding, black_holding,
\r
3232 if (tinyLayout || smallLayout) {
\r
3233 char wh[16], bh[16];
\r
3234 PackHolding(wh, white_holding);
\r
3235 PackHolding(bh, black_holding);
\r
3236 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3237 gameInfo.white, gameInfo.black);
\r
3239 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3240 gameInfo.white, white_holding,
\r
3241 gameInfo.black, black_holding);
\r
3244 DrawPosition(FALSE, boards[currentMove]);
\r
3245 DisplayTitle(str);
\r
3247 /* Suppress following prompt */
\r
3248 if (looking_at(buf, &i, "*% ")) {
\r
3249 savingComment = FALSE;
\r
3256 i++; /* skip unparsed character and loop back */
\r
3259 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3260 started != STARTED_HOLDINGS && i > next_out) {
\r
3261 SendToPlayer(&buf[next_out], i - next_out);
\r
3264 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3266 leftover_len = buf_len - leftover_start;
\r
3267 /* if buffer ends with something we couldn't parse,
\r
3268 reparse it after appending the next read */
\r
3270 } else if (count == 0) {
\r
3271 RemoveInputSource(isr);
\r
3272 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3274 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3279 /* Board style 12 looks like this:
\r
3281 <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
3283 * The "<12> " is stripped before it gets to this routine. The two
\r
3284 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3285 * some chess servers may not have them, or may have only the first.
\r
3286 * Additional trailing fields may be added in the future.
\r
3289 #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
3291 #define RELATION_OBSERVING_PLAYED 0
\r
3292 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3293 #define RELATION_PLAYING_MYMOVE 1
\r
3294 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3295 #define RELATION_EXAMINING 2
\r
3296 #define RELATION_ISOLATED_BOARD -3
\r
3297 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3300 ParseBoard12(string)
\r
3303 GameMode newGameMode;
\r
3304 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3305 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3306 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3307 char to_play, board_chars[200];
\r
3308 char move_str[500], str[500], elapsed_time[500];
\r
3309 char black[32], white[32];
\r
3311 int prevMove = currentMove;
\r
3313 ChessMove moveType;
\r
3314 int fromX, fromY, toX, toY;
\r
3316 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3317 char *bookHit = NULL; // [HGM] book
\r
3319 fromX = fromY = toX = toY = -1;
\r
3323 if (appData.debugMode)
\r
3324 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3326 move_str[0] = NULLCHAR;
\r
3327 elapsed_time[0] = NULLCHAR;
\r
3328 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3330 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3331 if(string[i] == ' ') { ranks++; files = 0; }
\r
3335 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3336 board_chars[i] = '\0';
\r
3339 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3340 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3341 &gamenum, white, black, &relation, &basetime, &increment,
\r
3342 &white_stren, &black_stren, &white_time, &black_time,
\r
3343 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3347 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3348 DisplayError(str, 0);
\r
3352 /* Convert the move number to internal form */
\r
3353 moveNum = (moveNum - 1) * 2;
\r
3354 if (to_play == 'B') moveNum++;
\r
3355 if (moveNum >= MAX_MOVES) {
\r
3356 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3361 switch (relation) {
\r
3362 case RELATION_OBSERVING_PLAYED:
\r
3363 case RELATION_OBSERVING_STATIC:
\r
3364 if (gamenum == -1) {
\r
3365 /* Old ICC buglet */
\r
3366 relation = RELATION_OBSERVING_STATIC;
\r
3368 newGameMode = IcsObserving;
\r
3370 case RELATION_PLAYING_MYMOVE:
\r
3371 case RELATION_PLAYING_NOTMYMOVE:
\r
3373 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3374 IcsPlayingWhite : IcsPlayingBlack;
\r
3376 case RELATION_EXAMINING:
\r
3377 newGameMode = IcsExamining;
\r
3379 case RELATION_ISOLATED_BOARD:
\r
3381 /* Just display this board. If user was doing something else,
\r
3382 we will forget about it until the next board comes. */
\r
3383 newGameMode = IcsIdle;
\r
3385 case RELATION_STARTING_POSITION:
\r
3386 newGameMode = gameMode;
\r
3390 /* Modify behavior for initial board display on move listing
\r
3393 switch (ics_getting_history) {
\r
3397 case H_GOT_REQ_HEADER:
\r
3398 case H_GOT_UNREQ_HEADER:
\r
3399 /* This is the initial position of the current game */
\r
3400 gamenum = ics_gamenum;
\r
3401 moveNum = 0; /* old ICS bug workaround */
\r
3402 if (to_play == 'B') {
\r
3403 startedFromSetupPosition = TRUE;
\r
3404 blackPlaysFirst = TRUE;
\r
3406 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3407 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3408 if (currentMove == 0) currentMove = 1;
\r
3410 newGameMode = gameMode;
\r
3411 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3413 case H_GOT_UNWANTED_HEADER:
\r
3414 /* This is an initial board that we don't want */
\r
3416 case H_GETTING_MOVES:
\r
3417 /* Should not happen */
\r
3418 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3419 ics_getting_history = H_FALSE;
\r
3423 /* Take action if this is the first board of a new game, or of a
\r
3424 different game than is currently being displayed. */
\r
3425 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3426 relation == RELATION_ISOLATED_BOARD) {
\r
3428 /* Forget the old game and get the history (if any) of the new one */
\r
3429 if (gameMode != BeginningOfGame) {
\r
3430 Reset(FALSE, TRUE);
\r
3433 if (appData.autoRaiseBoard) BoardToTop();
\r
3435 if (gamenum == -1) {
\r
3436 newGameMode = IcsIdle;
\r
3437 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3438 appData.getMoveList) {
\r
3439 /* Need to get game history */
\r
3440 ics_getting_history = H_REQUESTED;
\r
3441 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3445 /* Initially flip the board to have black on the bottom if playing
\r
3446 black or if the ICS flip flag is set, but let the user change
\r
3447 it with the Flip View button. */
\r
3448 flipView = appData.autoFlipView ?
\r
3449 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3452 /* Done with values from previous mode; copy in new ones */
\r
3453 gameMode = newGameMode;
\r
3455 ics_gamenum = gamenum;
\r
3456 if (gamenum == gs_gamenum) {
\r
3457 int klen = strlen(gs_kind);
\r
3458 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3459 sprintf(str, "ICS %s", gs_kind);
\r
3460 gameInfo.event = StrSave(str);
\r
3462 gameInfo.event = StrSave("ICS game");
\r
3464 gameInfo.site = StrSave(appData.icsHost);
\r
3465 gameInfo.date = PGNDate();
\r
3466 gameInfo.round = StrSave("-");
\r
3467 gameInfo.white = StrSave(white);
\r
3468 gameInfo.black = StrSave(black);
\r
3469 timeControl = basetime * 60 * 1000;
\r
3470 timeControl_2 = 0;
\r
3471 timeIncrement = increment * 1000;
\r
3472 movesPerSession = 0;
\r
3473 gameInfo.timeControl = TimeControlTagValue();
\r
3474 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3475 if (appData.debugMode) {
\r
3476 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3477 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3478 setbuf(debugFP, NULL);
\r
3481 gameInfo.outOfBook = NULL;
\r
3483 /* Do we have the ratings? */
\r
3484 if (strcmp(player1Name, white) == 0 &&
\r
3485 strcmp(player2Name, black) == 0) {
\r
3486 if (appData.debugMode)
\r
3487 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3488 player1Rating, player2Rating);
\r
3489 gameInfo.whiteRating = player1Rating;
\r
3490 gameInfo.blackRating = player2Rating;
\r
3491 } else if (strcmp(player2Name, white) == 0 &&
\r
3492 strcmp(player1Name, black) == 0) {
\r
3493 if (appData.debugMode)
\r
3494 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3495 player2Rating, player1Rating);
\r
3496 gameInfo.whiteRating = player2Rating;
\r
3497 gameInfo.blackRating = player1Rating;
\r
3499 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3501 /* Silence shouts if requested */
\r
3502 if (appData.quietPlay &&
\r
3503 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3504 SendToICS(ics_prefix);
\r
3505 SendToICS("set shout 0\n");
\r
3509 /* Deal with midgame name changes */
\r
3511 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3512 if (gameInfo.white) free(gameInfo.white);
\r
3513 gameInfo.white = StrSave(white);
\r
3515 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3516 if (gameInfo.black) free(gameInfo.black);
\r
3517 gameInfo.black = StrSave(black);
\r
3521 /* Throw away game result if anything actually changes in examine mode */
\r
3522 if (gameMode == IcsExamining && !newGame) {
\r
3523 gameInfo.result = GameUnfinished;
\r
3524 if (gameInfo.resultDetails != NULL) {
\r
3525 free(gameInfo.resultDetails);
\r
3526 gameInfo.resultDetails = NULL;
\r
3530 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3531 in if they are in a different variation than we are. */
\r
3532 if (pauseExamInvalid) return;
\r
3533 if (pausing && gameMode == IcsExamining) {
\r
3534 if (moveNum <= pauseExamForwardMostMove) {
\r
3535 pauseExamInvalid = TRUE;
\r
3536 forwardMostMove = pauseExamForwardMostMove;
\r
3541 if (appData.debugMode) {
\r
3542 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3544 /* Parse the board */
\r
3545 for (k = 0; k < ranks; k++) {
\r
3546 for (j = 0; j < files; j++)
\r
3547 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3548 if(gameInfo.holdingsWidth > 1) {
\r
3549 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3550 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3553 CopyBoard(boards[moveNum], board);
\r
3554 if (moveNum == 0) {
\r
3555 startedFromSetupPosition =
\r
3556 !CompareBoards(board, initialPosition);
\r
3557 if(startedFromSetupPosition)
\r
3558 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3561 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3562 to make it also work for FRC opening positions. Note that board12
\r
3563 is really defective for later FRC positions, as it has no way to
\r
3564 indicate which Rook can castle if they are on the same side of King.
\r
3565 For the initial position we grant rights to the outermost Rooks,
\r
3566 and remember thos rights, and we then copy them on positions
\r
3567 later in an FRC game. This means WB might not recognize castlings with
\r
3568 Rooks that have moved back to their original position as illegal,
\r
3569 but in ICS mode that is not its job anyway.
\r
3571 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3572 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3574 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3575 if(board[0][i] == WhiteRook) j = i;
\r
3576 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3577 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3578 if(board[0][i] == WhiteRook) j = i;
\r
3579 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3580 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3581 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3582 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3583 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3584 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3585 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3587 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3588 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3589 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3590 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3591 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3592 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3594 r = castlingRights[moveNum][0] = initialRights[0];
\r
3595 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3596 r = castlingRights[moveNum][1] = initialRights[1];
\r
3597 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3598 r = castlingRights[moveNum][3] = initialRights[3];
\r
3599 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3600 r = castlingRights[moveNum][4] = initialRights[4];
\r
3601 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3602 /* wildcastle kludge: always assume King has rights */
\r
3603 r = castlingRights[moveNum][2] = initialRights[2];
\r
3604 r = castlingRights[moveNum][5] = initialRights[5];
\r
3606 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3607 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3610 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3611 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3612 /* This was an initial position from a move list, not
\r
3613 the current position */
\r
3617 /* Update currentMove and known move number limits */
\r
3618 newMove = newGame || moveNum > forwardMostMove;
\r
3620 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3621 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3622 takeback = forwardMostMove - moveNum;
\r
3623 for (i = 0; i < takeback; i++) {
\r
3624 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3625 SendToProgram("undo\n", &first);
\r
3630 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3631 if (gameMode == IcsExamining && moveNum == 0) {
\r
3632 /* Workaround for ICS limitation: we are not told the wild
\r
3633 type when starting to examine a game. But if we ask for
\r
3634 the move list, the move list header will tell us */
\r
3635 ics_getting_history = H_REQUESTED;
\r
3636 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3639 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3640 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3641 forwardMostMove = moveNum;
\r
3642 if (!pausing || currentMove > forwardMostMove)
\r
3643 currentMove = forwardMostMove;
\r
3645 /* New part of history that is not contiguous with old part */
\r
3646 if (pausing && gameMode == IcsExamining) {
\r
3647 pauseExamInvalid = TRUE;
\r
3648 forwardMostMove = pauseExamForwardMostMove;
\r
3651 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3652 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3653 ics_getting_history = H_REQUESTED;
\r
3654 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3659 /* Update the clocks */
\r
3660 if (strchr(elapsed_time, '.')) {
\r
3661 /* Time is in ms */
\r
3662 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3663 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3665 /* Time is in seconds */
\r
3666 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3667 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3672 if (appData.zippyPlay && newGame &&
\r
3673 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3674 gameMode != IcsExamining)
\r
3675 ZippyFirstBoard(moveNum, basetime, increment);
\r
3678 /* Put the move on the move list, first converting
\r
3679 to canonical algebraic form. */
\r
3680 if (moveNum > 0) {
\r
3681 if (appData.debugMode) {
\r
3682 if (appData.debugMode) { int f = forwardMostMove;
\r
3683 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3684 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3686 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3687 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3688 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3689 setbuf(debugFP, NULL);
\r
3691 if (moveNum <= backwardMostMove) {
\r
3692 /* We don't know what the board looked like before
\r
3693 this move. Punt. */
\r
3694 strcpy(parseList[moveNum - 1], move_str);
\r
3695 strcat(parseList[moveNum - 1], " ");
\r
3696 strcat(parseList[moveNum - 1], elapsed_time);
\r
3697 moveList[moveNum - 1][0] = NULLCHAR;
\r
3698 } else if (strcmp(move_str, "none") == 0) {
\r
3699 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3700 /* Again, we don't know what the board looked like;
\r
3701 this is really the start of the game. */
\r
3702 parseList[moveNum - 1][0] = NULLCHAR;
\r
3703 moveList[moveNum - 1][0] = NULLCHAR;
\r
3704 backwardMostMove = moveNum;
\r
3705 startedFromSetupPosition = TRUE;
\r
3706 fromX = fromY = toX = toY = -1;
\r
3708 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3709 // So we parse the long-algebraic move string in stead of the SAN move
\r
3710 int valid; char buf[MSG_SIZ], *prom;
\r
3712 // str looks something like "Q/a1-a2"; kill the slash
\r
3713 if(str[1] == '/')
\r
3714 sprintf(buf, "%c%s", str[0], str+2);
\r
3715 else strcpy(buf, str); // might be castling
\r
3716 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3717 strcat(buf, prom); // long move lacks promo specification!
\r
3718 if(!appData.testLegality) {
\r
3719 if(appData.debugMode)
\r
3720 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3721 strcpy(move_str, buf);
\r
3723 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3724 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3725 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3726 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3727 // end of long SAN patch
\r
3729 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3730 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3731 fromY, fromX, toY, toX, promoChar,
\r
3732 parseList[moveNum-1]);
\r
3733 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3734 castlingRights[moveNum]) ) {
\r
3736 case MT_STALEMATE:
\r
3740 if(gameInfo.variant != VariantShogi)
\r
3741 strcat(parseList[moveNum - 1], "+");
\r
3743 case MT_CHECKMATE:
\r
3744 strcat(parseList[moveNum - 1], "#");
\r
3747 strcat(parseList[moveNum - 1], " ");
\r
3748 strcat(parseList[moveNum - 1], elapsed_time);
\r
3749 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3750 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3751 strcat(moveList[moveNum - 1], "\n");
\r
3753 /* Move from ICS was illegal!? Punt. */
\r
3754 if (appData.debugMode) {
\r
3755 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3756 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3759 if (appData.testLegality && appData.debugMode) {
\r
3760 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3761 DisplayError(str, 0);
\r
3764 strcpy(parseList[moveNum - 1], move_str);
\r
3765 strcat(parseList[moveNum - 1], " ");
\r
3766 strcat(parseList[moveNum - 1], elapsed_time);
\r
3767 moveList[moveNum - 1][0] = NULLCHAR;
\r
3768 fromX = fromY = toX = toY = -1;
\r
3771 if (appData.debugMode) {
\r
3772 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3773 setbuf(debugFP, NULL);
\r
3777 /* Send move to chess program (BEFORE animating it). */
\r
3778 if (appData.zippyPlay && !newGame && newMove &&
\r
3779 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3781 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3782 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3783 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3784 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3786 DisplayError(str, 0);
\r
3788 if (first.sendTime) {
\r
3789 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3791 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3792 if (firstMove && !bookHit) {
\r
3793 firstMove = FALSE;
\r
3794 if (first.useColors) {
\r
3795 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3797 "black\ngo\n", &first);
\r
3799 SendToProgram("go\n", &first);
\r
3801 first.maybeThinking = TRUE;
\r
3804 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3805 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3806 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3807 DisplayError(str, 0);
\r
3809 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3810 SendMoveToProgram(moveNum - 1, &first);
\r
3817 if (moveNum > 0 && !gotPremove) {
\r
3818 /* If move comes from a remote source, animate it. If it
\r
3819 isn't remote, it will have already been animated. */
\r
3820 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3821 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3823 if (!pausing && appData.highlightLastMove) {
\r
3824 SetHighlights(fromX, fromY, toX, toY);
\r
3828 /* Start the clocks */
\r
3829 whiteFlag = blackFlag = FALSE;
\r
3830 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3831 if (ticking == 0) {
\r
3832 ics_clock_paused = TRUE;
\r
3834 } else if (ticking == 1) {
\r
3835 ics_clock_paused = FALSE;
\r
3837 if (gameMode == IcsIdle ||
\r
3838 relation == RELATION_OBSERVING_STATIC ||
\r
3839 relation == RELATION_EXAMINING ||
\r
3841 DisplayBothClocks();
\r
3845 /* Display opponents and material strengths */
\r
3846 if (gameInfo.variant != VariantBughouse &&
\r
3847 gameInfo.variant != VariantCrazyhouse) {
\r
3848 if (tinyLayout || smallLayout) {
\r
3849 if(gameInfo.variant == VariantNormal)
\r
3850 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3851 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3852 basetime, increment);
\r
3854 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3855 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3856 basetime, increment, (int) gameInfo.variant);
\r
3858 if(gameInfo.variant == VariantNormal)
\r
3859 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3860 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3861 basetime, increment);
\r
3863 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3864 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3865 basetime, increment, VariantName(gameInfo.variant));
\r
3867 DisplayTitle(str);
\r
3868 if (appData.debugMode) {
\r
3869 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3874 /* Display the board */
\r
3877 if (appData.premove)
\r
3878 if (!gotPremove ||
\r
3879 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3880 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3881 ClearPremoveHighlights();
\r
3883 DrawPosition(FALSE, boards[currentMove]);
\r
3884 DisplayMove(moveNum - 1);
\r
3885 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3889 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3891 if(bookHit) { // [HGM] book: simulate book reply
\r
3892 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3894 programStats.nodes = programStats.depth = programStats.time =
\r
3895 programStats.score = programStats.got_only_move = 0;
\r
3896 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3898 strcpy(bookMove, "move ");
\r
3899 strcat(bookMove, bookHit);
\r
3900 HandleMachineMove(bookMove, &first);
\r
3906 GetMoveListEvent()
\r
3908 char buf[MSG_SIZ];
\r
3909 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3910 ics_getting_history = H_REQUESTED;
\r
3911 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3917 AnalysisPeriodicEvent(force)
\r
3920 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3921 && !force) || !appData.periodicUpdates)
\r
3924 /* Send . command to Crafty to collect stats */
\r
3925 SendToProgram(".\n", &first);
\r
3927 /* Don't send another until we get a response (this makes
\r
3928 us stop sending to old Crafty's which don't understand
\r
3929 the "." command (sending illegal cmds resets node count & time,
\r
3930 which looks bad)) */
\r
3931 programStats.ok_to_send = 0;
\r
3935 SendMoveToProgram(moveNum, cps)
\r
3937 ChessProgramState *cps;
\r
3939 char buf[MSG_SIZ];
\r
3941 if (cps->useUsermove) {
\r
3942 SendToProgram("usermove ", cps);
\r
3944 if (cps->useSAN) {
\r
3946 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3947 int len = space - parseList[moveNum];
\r
3948 memcpy(buf, parseList[moveNum], len);
\r
3949 buf[len++] = '\n';
\r
3950 buf[len] = NULLCHAR;
\r
3952 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3954 SendToProgram(buf, cps);
\r
3956 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3957 AlphaRank(moveList[moveNum], 4);
\r
3958 SendToProgram(moveList[moveNum], cps);
\r
3959 AlphaRank(moveList[moveNum], 4); // and back
\r
3961 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3962 * the engine. It would be nice to have a better way to identify castle
\r
3964 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3965 && cps->useOOCastle) {
\r
3966 int fromX = moveList[moveNum][0] - AAA;
\r
3967 int fromY = moveList[moveNum][1] - ONE;
\r
3968 int toX = moveList[moveNum][2] - AAA;
\r
3969 int toY = moveList[moveNum][3] - ONE;
\r
3970 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3971 && boards[moveNum][toY][toX] == WhiteRook)
\r
3972 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3973 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3974 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3975 else SendToProgram("O-O-O\n", cps);
\r
3977 else SendToProgram(moveList[moveNum], cps);
\r
3979 else SendToProgram(moveList[moveNum], cps);
\r
3980 /* End of additions by Tord */
\r
3983 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3984 /* Send 'go' if we are in a mode where machine should play. */
\r
3985 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3986 (gameMode == TwoMachinesPlay ||
\r
3988 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3990 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3991 SendToProgram("go\n", cps);
\r
3992 if (appData.debugMode) {
\r
3993 fprintf(debugFP, "(extra)\n");
\r
3996 setboardSpoiledMachineBlack = 0;
\r
4000 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4001 ChessMove moveType;
\r
4002 int fromX, fromY, toX, toY;
\r
4004 char user_move[MSG_SIZ];
\r
4006 switch (moveType) {
\r
4008 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4009 (int)moveType, fromX, fromY, toX, toY);
\r
4010 DisplayError(user_move + strlen("say "), 0);
\r
4012 case WhiteKingSideCastle:
\r
4013 case BlackKingSideCastle:
\r
4014 case WhiteQueenSideCastleWild:
\r
4015 case BlackQueenSideCastleWild:
\r
4017 case WhiteHSideCastleFR:
\r
4018 case BlackHSideCastleFR:
\r
4020 sprintf(user_move, "o-o\n");
\r
4022 case WhiteQueenSideCastle:
\r
4023 case BlackQueenSideCastle:
\r
4024 case WhiteKingSideCastleWild:
\r
4025 case BlackKingSideCastleWild:
\r
4027 case WhiteASideCastleFR:
\r
4028 case BlackASideCastleFR:
\r
4030 sprintf(user_move, "o-o-o\n");
\r
4032 case WhitePromotionQueen:
\r
4033 case BlackPromotionQueen:
\r
4034 case WhitePromotionRook:
\r
4035 case BlackPromotionRook:
\r
4036 case WhitePromotionBishop:
\r
4037 case BlackPromotionBishop:
\r
4038 case WhitePromotionKnight:
\r
4039 case BlackPromotionKnight:
\r
4040 case WhitePromotionKing:
\r
4041 case BlackPromotionKing:
\r
4042 case WhitePromotionChancellor:
\r
4043 case BlackPromotionChancellor:
\r
4044 case WhitePromotionArchbishop:
\r
4045 case BlackPromotionArchbishop:
\r
4046 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4047 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4048 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4049 PieceToChar(WhiteFerz));
\r
4050 else if(gameInfo.variant == VariantGreat)
\r
4051 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4052 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4053 PieceToChar(WhiteMan));
\r
4055 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4056 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4057 PieceToChar(PromoPiece(moveType)));
\r
4061 sprintf(user_move, "%c@%c%c\n",
\r
4062 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4063 AAA + toX, ONE + toY);
\r
4066 case WhiteCapturesEnPassant:
\r
4067 case BlackCapturesEnPassant:
\r
4068 case IllegalMove: /* could be a variant we don't quite understand */
\r
4069 sprintf(user_move, "%c%c%c%c\n",
\r
4070 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4073 SendToICS(user_move);
\r
4077 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4078 int rf, ff, rt, ft;
\r
4082 if (rf == DROP_RANK) {
\r
4083 sprintf(move, "%c@%c%c\n",
\r
4084 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4086 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4087 sprintf(move, "%c%c%c%c\n",
\r
4088 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4090 sprintf(move, "%c%c%c%c%c\n",
\r
4091 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4097 ProcessICSInitScript(f)
\r
4100 char buf[MSG_SIZ];
\r
4102 while (fgets(buf, MSG_SIZ, f)) {
\r
4103 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4110 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4112 AlphaRank(char *move, int n)
\r
4114 // char *p = move, c; int x, y;
\r
4116 if (appData.debugMode) {
\r
4117 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4120 if(move[1]=='*' &&
\r
4121 move[2]>='0' && move[2]<='9' &&
\r
4122 move[3]>='a' && move[3]<='x' ) {
\r
4124 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4125 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4127 if(move[0]>='0' && move[0]<='9' &&
\r
4128 move[1]>='a' && move[1]<='x' &&
\r
4129 move[2]>='0' && move[2]<='9' &&
\r
4130 move[3]>='a' && move[3]<='x' ) {
\r
4131 /* input move, Shogi -> normal */
\r
4132 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4133 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4134 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4135 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4137 if(move[1]=='@' &&
\r
4138 move[3]>='0' && move[3]<='9' &&
\r
4139 move[2]>='a' && move[2]<='x' ) {
\r
4141 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4142 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4145 move[0]>='a' && move[0]<='x' &&
\r
4146 move[3]>='0' && move[3]<='9' &&
\r
4147 move[2]>='a' && move[2]<='x' ) {
\r
4148 /* output move, normal -> Shogi */
\r
4149 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4150 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4151 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4152 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4153 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4155 if (appData.debugMode) {
\r
4156 fprintf(debugFP, " out = '%s'\n", move);
\r
4160 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4162 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4165 ChessMove *moveType;
\r
4166 int *fromX, *fromY, *toX, *toY;
\r
4169 if (appData.debugMode) {
\r
4170 fprintf(debugFP, "move to parse: %s\n", move);
\r
4172 *moveType = yylexstr(moveNum, move);
\r
4174 switch (*moveType) {
\r
4175 case WhitePromotionChancellor:
\r
4176 case BlackPromotionChancellor:
\r
4177 case WhitePromotionArchbishop:
\r
4178 case BlackPromotionArchbishop:
\r
4179 case WhitePromotionQueen:
\r
4180 case BlackPromotionQueen:
\r
4181 case WhitePromotionRook:
\r
4182 case BlackPromotionRook:
\r
4183 case WhitePromotionBishop:
\r
4184 case BlackPromotionBishop:
\r
4185 case WhitePromotionKnight:
\r
4186 case BlackPromotionKnight:
\r
4187 case WhitePromotionKing:
\r
4188 case BlackPromotionKing:
\r
4190 case WhiteCapturesEnPassant:
\r
4191 case BlackCapturesEnPassant:
\r
4192 case WhiteKingSideCastle:
\r
4193 case WhiteQueenSideCastle:
\r
4194 case BlackKingSideCastle:
\r
4195 case BlackQueenSideCastle:
\r
4196 case WhiteKingSideCastleWild:
\r
4197 case WhiteQueenSideCastleWild:
\r
4198 case BlackKingSideCastleWild:
\r
4199 case BlackQueenSideCastleWild:
\r
4200 /* Code added by Tord: */
\r
4201 case WhiteHSideCastleFR:
\r
4202 case WhiteASideCastleFR:
\r
4203 case BlackHSideCastleFR:
\r
4204 case BlackASideCastleFR:
\r
4205 /* End of code added by Tord */
\r
4206 case IllegalMove: /* bug or odd chess variant */
\r
4207 *fromX = currentMoveString[0] - AAA;
\r
4208 *fromY = currentMoveString[1] - ONE;
\r
4209 *toX = currentMoveString[2] - AAA;
\r
4210 *toY = currentMoveString[3] - ONE;
\r
4211 *promoChar = currentMoveString[4];
\r
4212 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4213 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4214 if (appData.debugMode) {
\r
4215 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4217 *fromX = *fromY = *toX = *toY = 0;
\r
4220 if (appData.testLegality) {
\r
4221 return (*moveType != IllegalMove);
\r
4223 return !(fromX == fromY && toX == toY);
\r
4228 *fromX = *moveType == WhiteDrop ?
\r
4229 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4230 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4231 *fromY = DROP_RANK;
\r
4232 *toX = currentMoveString[2] - AAA;
\r
4233 *toY = currentMoveString[3] - ONE;
\r
4234 *promoChar = NULLCHAR;
\r
4237 case AmbiguousMove:
\r
4238 case ImpossibleMove:
\r
4239 case (ChessMove) 0: /* end of file */
\r
4248 if (appData.debugMode) {
\r
4249 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4252 *fromX = *fromY = *toX = *toY = 0;
\r
4253 *promoChar = NULLCHAR;
\r
4259 /* [AS] FRC game initialization */
\r
4260 static int FindEmptySquare( Board board, int n )
\r
4265 while( board[0][i] != EmptySquare ) i++;
\r
4275 static void ShuffleFRC( Board board )
\r
4281 for( i=0; i<8; i++ ) {
\r
4282 board[0][i] = EmptySquare;
\r
4285 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4286 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4287 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4288 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4289 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4290 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4291 initialRights[1] = initialRights[4] =
\r
4292 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4293 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4294 initialRights[2] = initialRights[5] =
\r
4295 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4296 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4297 initialRights[0] = initialRights[3] =
\r
4298 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4300 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4301 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4305 static unsigned char FRC_KnightTable[10] = {
\r
4306 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4309 static void SetupFRC( Board board, int pos_index )
\r
4312 unsigned char knights;
\r
4314 /* Bring the position index into a safe range (just in case...) */
\r
4315 if( pos_index < 0 ) pos_index = 0;
\r
4319 /* Clear the board */
\r
4320 for( i=0; i<8; i++ ) {
\r
4321 board[0][i] = EmptySquare;
\r
4324 /* Place bishops and queen */
\r
4325 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4328 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4331 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4334 /* Place knigths */
\r
4335 knights = FRC_KnightTable[ pos_index ];
\r
4337 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4338 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4340 /* Place rooks and king */
\r
4341 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4342 initialRights[1] = initialRights[4] =
\r
4343 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4344 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4345 initialRights[2] = initialRights[5] =
\r
4346 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4347 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4348 initialRights[0] = initialRights[3] =
\r
4349 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4351 /* Mirror piece placement for black */
\r
4352 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4353 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4357 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4358 // All positions will have equal probability, but the current method will not provide a unique
\r
4359 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4364 int squaresLeft[4];
\r
4365 int piecesLeft[(int)BlackPawn];
\r
4366 u64 seed, nrOfShuffles;
\r
4368 void GetPositionNumber()
\r
4369 { // sets global variable seed
\r
4372 seed = appData.defaultFrcPosition;
\r
4373 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4374 srandom(time(0));
\r
4375 for(i=0; i<50; i++) seed += random();
\r
4376 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4377 if(seed<0) seed = -seed;
\r
4381 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4382 // put the piece on the (n-1)-th empty squares of the given shade
\r
4386 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4387 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4388 board[rank][i] = (ChessSquare) pieceType;
\r
4389 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4390 squaresLeft[ANY]--;
\r
4391 piecesLeft[pieceType]--;
\r
4399 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4400 // calculate where the next piece goes, (any empty square), and put it there
\r
4404 i = seed % squaresLeft[shade];
\r
4405 nrOfShuffles *= squaresLeft[shade];
\r
4406 seed /= squaresLeft[shade];
\r
4407 put(board, pieceType, rank, i, shade);
\r
4410 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4411 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4413 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4415 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4416 i = seed % k; // pick one
\r
4417 nrOfShuffles *= k;
\r
4419 while(i >= j) i -= j--;
\r
4420 j = n - 1 - j; i += j;
\r
4421 put(board, pieceType, rank, j, ANY);
\r
4422 put(board, pieceType, rank, i, ANY);
\r
4425 void SetUpShuffle(Board board, int number)
\r
4427 int i, p, first=1;
\r
4429 GetPositionNumber(); nrOfShuffles = 1;
\r
4431 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4432 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4433 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4435 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4437 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4438 p = (int) board[0][i];
\r
4439 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4440 board[0][i] = EmptySquare;
\r
4443 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4444 // shuffles restricted to allow normal castling put KRR first
\r
4445 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4446 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4447 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4448 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4449 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4450 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4451 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4452 put(board, WhiteRook, 0, 0, ANY);
\r
4453 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4456 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4457 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4458 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4459 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4460 while(piecesLeft[p] >= 2) {
\r
4461 AddOnePiece(board, p, 0, LITE);
\r
4462 AddOnePiece(board, p, 0, DARK);
\r
4464 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4467 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4468 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4469 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4470 if(p == (int)WhiteRook) continue;
\r
4471 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4472 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4475 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4477 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4478 // Last King gets castling rights
\r
4479 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4480 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4481 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4484 while(piecesLeft[(int)WhiteKing]) {
\r
4485 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4486 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4491 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4492 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4495 // Only Rooks can be left; simply place them all
\r
4496 while(piecesLeft[(int)WhiteRook]) {
\r
4497 i = put(board, WhiteRook, 0, 0, ANY);
\r
4498 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4501 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4503 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4506 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4507 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4510 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4515 int SetCharTable( char *table, const char * map )
\r
4516 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4517 /* Basically a safe strcpy that uses the last character as King */
\r
4519 int result = FALSE; int NrPieces;
\r
4521 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4522 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4523 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4525 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4526 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4527 table[i] = map[i];
\r
4528 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4530 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4531 table[(int) BlackKing] = map[NrPieces-1];
\r
4539 void Prelude(Board board)
\r
4540 { // [HGM] superchess: random selection of exo-pieces
\r
4541 int i, j, k; ChessSquare p;
\r
4542 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4544 GetPositionNumber(); // use FRC position number
\r
4546 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4547 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4548 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4549 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4552 j = seed%4; seed /= 4;
\r
4553 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4554 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4555 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4556 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4557 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4558 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4559 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4560 j = seed%3; seed /= 3;
\r
4561 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4562 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4563 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4564 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4565 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4566 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4567 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4568 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4569 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4570 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4571 put(board, exoPieces[0], 0, 0, ANY);
\r
4572 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4576 InitPosition(redraw)
\r
4579 ChessSquare (* pieces)[BOARD_SIZE];
\r
4580 int i, j, pawnRow, overrule,
\r
4581 oldx = gameInfo.boardWidth,
\r
4582 oldy = gameInfo.boardHeight,
\r
4583 oldh = gameInfo.holdingsWidth,
\r
4584 oldv = gameInfo.variant;
\r
4586 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4587 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4589 /* [AS] Initialize pv info list [HGM] and game status */
\r
4591 for( i=0; i<MAX_MOVES; i++ ) {
\r
4592 pvInfoList[i].depth = 0;
\r
4593 epStatus[i]=EP_NONE;
\r
4594 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4597 initialRulePlies = 0; /* 50-move counter start */
\r
4599 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4600 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4604 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4605 /* the initialized data only consist of the two backranks. The switch */
\r
4606 /* selects which one we will use, which is than copied to the Board */
\r
4607 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4608 /* empty squares. This initial position is then copied to boards[0], */
\r
4609 /* possibly after shuffling, so that it remains available. */
\r
4611 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4612 gameInfo.boardWidth = 8;
\r
4613 gameInfo.boardHeight = 8;
\r
4614 gameInfo.holdingsSize = 0;
\r
4615 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4616 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4617 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4619 switch (gameInfo.variant) {
\r
4620 case VariantFischeRandom:
\r
4621 shuffleOpenings = TRUE;
\r
4623 pieces = FIDEArray;
\r
4625 case VariantShatranj:
\r
4626 pieces = ShatranjArray;
\r
4627 nrCastlingRights = 0;
\r
4628 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4630 case VariantTwoKings:
\r
4631 pieces = twoKingsArray;
\r
4633 case VariantCapaRandom:
\r
4634 shuffleOpenings = TRUE;
\r
4635 case VariantCapablanca:
\r
4636 pieces = CapablancaArray;
\r
4637 gameInfo.boardWidth = 10;
\r
4638 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4640 case VariantGothic:
\r
4641 pieces = GothicArray;
\r
4642 gameInfo.boardWidth = 10;
\r
4643 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4645 case VariantJanus:
\r
4646 pieces = JanusArray;
\r
4647 gameInfo.boardWidth = 10;
\r
4648 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4649 nrCastlingRights = 6;
\r
4650 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4651 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4652 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4653 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4654 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4655 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4657 case VariantFalcon:
\r
4658 pieces = FalconArray;
\r
4659 gameInfo.boardWidth = 10;
\r
4660 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4662 case VariantXiangqi:
\r
4663 pieces = XiangqiArray;
\r
4664 gameInfo.boardWidth = 9;
\r
4665 gameInfo.boardHeight = 10;
\r
4666 nrCastlingRights = 0;
\r
4667 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4669 case VariantShogi:
\r
4670 pieces = ShogiArray;
\r
4671 gameInfo.boardWidth = 9;
\r
4672 gameInfo.boardHeight = 9;
\r
4673 gameInfo.holdingsSize = 7;
\r
4674 nrCastlingRights = 0;
\r
4675 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4677 case VariantCourier:
\r
4678 pieces = CourierArray;
\r
4679 gameInfo.boardWidth = 12;
\r
4680 nrCastlingRights = 0;
\r
4681 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4682 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4684 case VariantKnightmate:
\r
4685 pieces = KnightmateArray;
\r
4686 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4688 case VariantFairy:
\r
4689 pieces = fairyArray;
\r
4690 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4692 case VariantGreat:
\r
4693 pieces = GreatArray;
\r
4694 gameInfo.boardWidth = 10;
\r
4695 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4696 gameInfo.holdingsSize = 8;
\r
4698 case VariantSuper:
\r
4699 pieces = FIDEArray;
\r
4700 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4701 gameInfo.holdingsSize = 8;
\r
4702 startedFromSetupPosition = TRUE;
\r
4704 case VariantCrazyhouse:
\r
4705 case VariantBughouse:
\r
4706 pieces = FIDEArray;
\r
4707 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4708 gameInfo.holdingsSize = 5;
\r
4710 case VariantWildCastle:
\r
4711 pieces = FIDEArray;
\r
4712 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4713 shuffleOpenings = 1;
\r
4715 case VariantNoCastle:
\r
4716 pieces = FIDEArray;
\r
4717 nrCastlingRights = 0;
\r
4718 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4719 /* !!?unconstrained back-rank shuffle */
\r
4720 shuffleOpenings = 1;
\r
4725 if(appData.NrFiles >= 0) {
\r
4726 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4727 gameInfo.boardWidth = appData.NrFiles;
\r
4729 if(appData.NrRanks >= 0) {
\r
4730 gameInfo.boardHeight = appData.NrRanks;
\r
4732 if(appData.holdingsSize >= 0) {
\r
4733 i = appData.holdingsSize;
\r
4734 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4735 gameInfo.holdingsSize = i;
\r
4737 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4738 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4739 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4741 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4742 if(pawnRow < 1) pawnRow = 1;
\r
4744 /* User pieceToChar list overrules defaults */
\r
4745 if(appData.pieceToCharTable != NULL)
\r
4746 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4748 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4750 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4751 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4752 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4753 initialPosition[i][j] = s;
\r
4755 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4756 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4757 initialPosition[pawnRow][j] = WhitePawn;
\r
4758 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4759 if(gameInfo.variant == VariantXiangqi) {
\r
4761 initialPosition[pawnRow][j] =
\r
4762 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4763 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4764 initialPosition[2][j] = WhiteCannon;
\r
4765 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4769 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4771 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4774 initialPosition[1][j] = WhiteBishop;
\r
4775 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4777 initialPosition[1][j] = WhiteRook;
\r
4778 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4781 if( nrCastlingRights == -1) {
\r
4782 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4783 /* This sets default castling rights from none to normal corners */
\r
4784 /* Variants with other castling rights must set them themselves above */
\r
4785 nrCastlingRights = 6;
\r
4787 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4788 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4789 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4790 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4791 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4792 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4795 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4796 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4797 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4798 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4799 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4800 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4803 if(gameInfo.variant == VariantFischeRandom) {
\r
4804 if( appData.defaultFrcPosition < 0 ) {
\r
4805 ShuffleFRC( initialPosition );
\r
4808 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4810 startedFromSetupPosition = TRUE;
\r
4813 if (appData.debugMode) {
\r
4814 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4816 if(shuffleOpenings) {
\r
4817 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4818 startedFromSetupPosition = TRUE;
\r
4821 if(startedFromPositionFile) {
\r
4822 /* [HGM] loadPos: use PositionFile for every new game */
\r
4823 CopyBoard(initialPosition, filePosition);
\r
4824 for(i=0; i<nrCastlingRights; i++)
\r
4825 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4826 startedFromSetupPosition = TRUE;
\r
4829 CopyBoard(boards[0], initialPosition);
\r
4831 if(oldx != gameInfo.boardWidth ||
\r
4832 oldy != gameInfo.boardHeight ||
\r
4833 oldh != gameInfo.holdingsWidth
\r
4835 || oldv == VariantGothic || // For licensing popups
\r
4836 gameInfo.variant == VariantGothic
\r
4839 || oldv == VariantFalcon ||
\r
4840 gameInfo.variant == VariantFalcon
\r
4843 InitDrawingSizes(-2 ,0);
\r
4846 DrawPosition(TRUE, boards[currentMove]);
\r
4850 SendBoard(cps, moveNum)
\r
4851 ChessProgramState *cps;
\r
4854 char message[MSG_SIZ];
\r
4856 if (cps->useSetboard) {
\r
4857 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4858 sprintf(message, "setboard %s\n", fen);
\r
4859 SendToProgram(message, cps);
\r
4865 /* Kludge to set black to move, avoiding the troublesome and now
\r
4866 * deprecated "black" command.
\r
4868 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4870 SendToProgram("edit\n", cps);
\r
4871 SendToProgram("#\n", cps);
\r
4872 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4873 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4874 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4875 if ((int) *bp < (int) BlackPawn) {
\r
4876 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4877 AAA + j, ONE + i);
\r
4878 if(message[0] == '+' || message[0] == '~') {
\r
4879 sprintf(message, "%c%c%c+\n",
\r
4880 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4881 AAA + j, ONE + i);
\r
4883 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4884 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4885 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4887 SendToProgram(message, cps);
\r
4892 SendToProgram("c\n", cps);
\r
4893 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4894 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4895 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4896 if (((int) *bp != (int) EmptySquare)
\r
4897 && ((int) *bp >= (int) BlackPawn)) {
\r
4898 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4899 AAA + j, ONE + i);
\r
4900 if(message[0] == '+' || message[0] == '~') {
\r
4901 sprintf(message, "%c%c%c+\n",
\r
4902 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4903 AAA + j, ONE + i);
\r
4905 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4906 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4907 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4909 SendToProgram(message, cps);
\r
4914 SendToProgram(".\n", cps);
\r
4916 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4920 IsPromotion(fromX, fromY, toX, toY)
\r
4921 int fromX, fromY, toX, toY;
\r
4923 /* [HGM] add Shogi promotions */
\r
4924 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4925 ChessSquare piece;
\r
4927 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4928 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4929 /* [HGM] Note to self: line above also weeds out drops */
\r
4930 piece = boards[currentMove][fromY][fromX];
\r
4931 if(gameInfo.variant == VariantShogi) {
\r
4932 promotionZoneSize = 3;
\r
4933 highestPromotingPiece = (int)WhiteKing;
\r
4934 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4935 and if in normal chess we then allow promotion to King, why not
\r
4936 allow promotion of other piece in Shogi? */
\r
4938 if((int)piece >= BlackPawn) {
\r
4939 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4941 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4943 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4944 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4946 return ( (int)piece <= highestPromotingPiece );
\r
4950 InPalace(row, column)
\r
4952 { /* [HGM] for Xiangqi */
\r
4953 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4954 column < (BOARD_WIDTH + 4)/2 &&
\r
4955 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4960 PieceForSquare (x, y)
\r
4964 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4967 return boards[currentMove][y][x];
\r
4971 OKToStartUserMove(x, y)
\r
4974 ChessSquare from_piece;
\r
4977 if (matchMode) return FALSE;
\r
4978 if (gameMode == EditPosition) return TRUE;
\r
4980 if (x >= 0 && y >= 0)
\r
4981 from_piece = boards[currentMove][y][x];
\r
4983 from_piece = EmptySquare;
\r
4985 if (from_piece == EmptySquare) return FALSE;
\r
4987 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4988 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4990 switch (gameMode) {
\r
4991 case PlayFromGameFile:
\r
4993 case TwoMachinesPlay:
\r
4997 case IcsObserving:
\r
5001 case MachinePlaysWhite:
\r
5002 case IcsPlayingBlack:
\r
5003 if (appData.zippyPlay) return FALSE;
\r
5004 if (white_piece) {
\r
5005 DisplayMoveError(_("You are playing Black"));
\r
5010 case MachinePlaysBlack:
\r
5011 case IcsPlayingWhite:
\r
5012 if (appData.zippyPlay) return FALSE;
\r
5013 if (!white_piece) {
\r
5014 DisplayMoveError(_("You are playing White"));
\r
5020 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5021 DisplayMoveError(_("It is White's turn"));
\r
5024 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5025 DisplayMoveError(_("It is Black's turn"));
\r
5028 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
5029 /* Editing correspondence game history */
\r
5030 /* Could disallow this or prompt for confirmation */
\r
5031 cmailOldMove = -1;
\r
5033 if (currentMove < forwardMostMove) {
\r
5034 /* Discarding moves */
\r
5035 /* Could prompt for confirmation here,
\r
5036 but I don't think that's such a good idea */
\r
5037 forwardMostMove = currentMove;
\r
5041 case BeginningOfGame:
\r
5042 if (appData.icsActive) return FALSE;
\r
5043 if (!appData.noChessProgram) {
\r
5044 if (!white_piece) {
\r
5045 DisplayMoveError(_("You are playing White"));
\r
5052 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5053 DisplayMoveError(_("It is White's turn"));
\r
5056 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5057 DisplayMoveError(_("It is Black's turn"));
\r
5063 case IcsExamining:
\r
5066 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
5067 && gameMode != AnalyzeFile && gameMode != Training) {
\r
5068 DisplayMoveError(_("Displayed position is not current"));
\r
5074 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
5075 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
5076 int lastLoadGameUseList = FALSE;
\r
5077 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
5078 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
5082 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
5083 int fromX, fromY, toX, toY;
\r
5086 ChessMove moveType;
\r
5087 ChessSquare pdown, pup;
\r
5089 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
5090 if ((fromX == toX) && (fromY == toY)) {
\r
5091 return ImpossibleMove;
\r
5094 /* [HGM] suppress all moves into holdings area and guard band */
\r
5095 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5096 return ImpossibleMove;
\r
5098 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5099 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5100 pdown = boards[currentMove][fromY][fromX];
\r
5101 pup = boards[currentMove][toY][toX];
\r
5102 if ( gameMode != EditPosition &&
\r
5103 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5104 WhitePawn <= pup && pup < BlackPawn ||
\r
5105 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5106 BlackPawn <= pup && pup < EmptySquare
\r
5107 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5108 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5109 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5111 return ImpossibleMove;
\r
5113 /* Check if the user is playing in turn. This is complicated because we
\r
5114 let the user "pick up" a piece before it is his turn. So the piece he
\r
5115 tried to pick up may have been captured by the time he puts it down!
\r
5116 Therefore we use the color the user is supposed to be playing in this
\r
5117 test, not the color of the piece that is currently on the starting
\r
5118 square---except in EditGame mode, where the user is playing both
\r
5119 sides; fortunately there the capture race can't happen. (It can
\r
5120 now happen in IcsExamining mode, but that's just too bad. The user
\r
5121 will get a somewhat confusing message in that case.)
\r
5124 switch (gameMode) {
\r
5125 case PlayFromGameFile:
\r
5127 case TwoMachinesPlay:
\r
5129 case IcsObserving:
\r
5131 /* We switched into a game mode where moves are not accepted,
\r
5132 perhaps while the mouse button was down. */
\r
5133 return ImpossibleMove;
\r
5135 case MachinePlaysWhite:
\r
5136 /* User is moving for Black */
\r
5137 if (WhiteOnMove(currentMove)) {
\r
5138 DisplayMoveError(_("It is White's turn"));
\r
5139 return ImpossibleMove;
\r
5143 case MachinePlaysBlack:
\r
5144 /* User is moving for White */
\r
5145 if (!WhiteOnMove(currentMove)) {
\r
5146 DisplayMoveError(_("It is Black's turn"));
\r
5147 return ImpossibleMove;
\r
5152 case IcsExamining:
\r
5153 case BeginningOfGame:
\r
5156 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5157 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5158 /* User is moving for Black */
\r
5159 if (WhiteOnMove(currentMove)) {
\r
5160 DisplayMoveError(_("It is White's turn"));
\r
5161 return ImpossibleMove;
\r
5164 /* User is moving for White */
\r
5165 if (!WhiteOnMove(currentMove)) {
\r
5166 DisplayMoveError(_("It is Black's turn"));
\r
5167 return ImpossibleMove;
\r
5172 case IcsPlayingBlack:
\r
5173 /* User is moving for Black */
\r
5174 if (WhiteOnMove(currentMove)) {
\r
5175 if (!appData.premove) {
\r
5176 DisplayMoveError(_("It is White's turn"));
\r
5177 } else if (toX >= 0 && toY >= 0) {
\r
5180 premoveFromX = fromX;
\r
5181 premoveFromY = fromY;
\r
5182 premovePromoChar = promoChar;
\r
5184 if (appData.debugMode)
\r
5185 fprintf(debugFP, "Got premove: fromX %d,"
\r
5186 "fromY %d, toX %d, toY %d\n",
\r
5187 fromX, fromY, toX, toY);
\r
5189 return ImpossibleMove;
\r
5193 case IcsPlayingWhite:
\r
5194 /* User is moving for White */
\r
5195 if (!WhiteOnMove(currentMove)) {
\r
5196 if (!appData.premove) {
\r
5197 DisplayMoveError(_("It is Black's turn"));
\r
5198 } else if (toX >= 0 && toY >= 0) {
\r
5201 premoveFromX = fromX;
\r
5202 premoveFromY = fromY;
\r
5203 premovePromoChar = promoChar;
\r
5205 if (appData.debugMode)
\r
5206 fprintf(debugFP, "Got premove: fromX %d,"
\r
5207 "fromY %d, toX %d, toY %d\n",
\r
5208 fromX, fromY, toX, toY);
\r
5210 return ImpossibleMove;
\r
5217 case EditPosition:
\r
5218 /* EditPosition, empty square, or different color piece;
\r
5219 click-click move is possible */
\r
5220 if (toX == -2 || toY == -2) {
\r
5221 boards[0][fromY][fromX] = EmptySquare;
\r
5222 return AmbiguousMove;
\r
5223 } else if (toX >= 0 && toY >= 0) {
\r
5224 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5225 boards[0][fromY][fromX] = EmptySquare;
\r
5226 return AmbiguousMove;
\r
5228 return ImpossibleMove;
\r
5231 /* [HGM] If move started in holdings, it means a drop */
\r
5232 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5233 if( pup != EmptySquare ) return ImpossibleMove;
\r
5234 if(appData.testLegality) {
\r
5235 /* it would be more logical if LegalityTest() also figured out
\r
5236 * which drops are legal. For now we forbid pawns on back rank.
\r
5237 * Shogi is on its own here...
\r
5239 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5240 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5241 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5243 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5246 userOfferedDraw = FALSE;
\r
5248 /* [HGM] always test for legality, to get promotion info */
\r
5249 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5250 epStatus[currentMove], castlingRights[currentMove],
\r
5251 fromY, fromX, toY, toX, promoChar);
\r
5253 /* [HGM] but possibly ignore an IllegalMove result */
\r
5254 if (appData.testLegality) {
\r
5255 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5256 DisplayMoveError(_("Illegal move"));
\r
5257 return ImpossibleMove;
\r
5260 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5262 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5263 function is made into one that returns an OK move type if FinishMove
\r
5264 should be called. This to give the calling driver routine the
\r
5265 opportunity to finish the userMove input with a promotion popup,
\r
5266 without bothering the user with this for invalid or illegal moves */
\r
5268 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5271 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5273 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5274 ChessMove moveType;
\r
5275 int fromX, fromY, toX, toY;
\r
5276 /*char*/int promoChar;
\r
5278 char *bookHit = 0;
\r
5279 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5280 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5281 // [HGM] superchess: suppress promotions to non-available piece
\r
5282 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5283 if(WhiteOnMove(currentMove)) {
\r
5284 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5286 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5290 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5291 move type in caller when we know the move is a legal promotion */
\r
5292 if(moveType == NormalMove && promoChar)
\r
5293 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5294 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5295 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5296 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5297 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5298 fromX = boards[currentMove][fromY][fromX];
\r
5299 fromY = DROP_RANK;
\r
5302 /* [HGM] <popupFix> The following if has been moved here from
\r
5303 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5304 piece drops in training games?), and because it can only be
\r
5305 performed after it is known to what we promote. */
\r
5306 if (gameMode == Training) {
\r
5307 /* compare the move played on the board to the next move in the
\r
5308 * game. If they match, display the move and the opponent's response.
\r
5309 * If they don't match, display an error message.
\r
5313 CopyBoard(testBoard, boards[currentMove]);
\r
5314 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5316 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5317 ForwardInner(currentMove+1);
\r
5319 /* Autoplay the opponent's response.
\r
5320 * if appData.animate was TRUE when Training mode was entered,
\r
5321 * the response will be animated.
\r
5323 saveAnimate = appData.animate;
\r
5324 appData.animate = animateTraining;
\r
5325 ForwardInner(currentMove+1);
\r
5326 appData.animate = saveAnimate;
\r
5328 /* check for the end of the game */
\r
5329 if (currentMove >= forwardMostMove) {
\r
5330 gameMode = PlayFromGameFile;
\r
5332 SetTrainingModeOff();
\r
5333 DisplayInformation(_("End of game"));
\r
5336 DisplayError(_("Incorrect move"), 0);
\r
5341 /* Ok, now we know that the move is good, so we can kill
\r
5342 the previous line in Analysis Mode */
\r
5343 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5344 forwardMostMove = currentMove;
\r
5347 /* If we need the chess program but it's dead, restart it */
\r
5348 ResurrectChessProgram();
\r
5350 /* A user move restarts a paused game*/
\r
5354 thinkOutput[0] = NULLCHAR;
\r
5356 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5358 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5359 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5360 // [HGM] superchess: take promotion piece out of holdings
\r
5361 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5362 if(WhiteOnMove(forwardMostMove-1)) {
\r
5363 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5364 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5366 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5367 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5371 if (gameMode == BeginningOfGame) {
\r
5372 if (appData.noChessProgram) {
\r
5373 gameMode = EditGame;
\r
5376 char buf[MSG_SIZ];
\r
5377 gameMode = MachinePlaysBlack;
\r
5380 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5381 DisplayTitle(buf);
\r
5382 if (first.sendName) {
\r
5383 sprintf(buf, "name %s\n", gameInfo.white);
\r
5384 SendToProgram(buf, &first);
\r
5390 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5391 /* Relay move to ICS or chess engine */
\r
5392 if (appData.icsActive) {
\r
5393 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5394 gameMode == IcsExamining) {
\r
5395 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5396 ics_user_moved = 1;
\r
5399 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5400 gameMode == MachinePlaysWhite ||
\r
5401 gameMode == MachinePlaysBlack)) {
\r
5402 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5404 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5405 // [HGM] book: if program might be playing, let it use book
\r
5406 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5407 first.maybeThinking = TRUE;
\r
5408 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5409 if (currentMove == cmailOldMove + 1) {
\r
5410 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5414 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5416 switch (gameMode) {
\r
5418 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5419 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5423 case MT_CHECKMATE:
\r
5424 if (WhiteOnMove(currentMove)) {
\r
5425 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5427 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5430 case MT_STALEMATE:
\r
5431 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5436 case MachinePlaysBlack:
\r
5437 case MachinePlaysWhite:
\r
5438 /* disable certain menu options while machine is thinking */
\r
5439 SetMachineThinkingEnables();
\r
5446 if(bookHit) { // [HGM] book: simulate book reply
\r
5447 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5449 programStats.nodes = programStats.depth = programStats.time =
\r
5450 programStats.score = programStats.got_only_move = 0;
\r
5451 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5453 strcpy(bookMove, "move ");
\r
5454 strcat(bookMove, bookHit);
\r
5455 HandleMachineMove(bookMove, &first);
\r
5461 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5462 int fromX, fromY, toX, toY;
\r
5465 /* [HGM] This routine was added to allow calling of its two logical
\r
5466 parts from other modules in the old way. Before, UserMoveEvent()
\r
5467 automatically called FinishMove() if the move was OK, and returned
\r
5468 otherwise. I separated the two, in order to make it possible to
\r
5469 slip a promotion popup in between. But that it always needs two
\r
5470 calls, to the first part, (now called UserMoveTest() ), and to
\r
5471 FinishMove if the first part succeeded. Calls that do not need
\r
5472 to do anything in between, can call this routine the old way.
\r
5474 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5475 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5476 if(moveType != ImpossibleMove)
\r
5477 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5480 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5482 // char * hint = lastHint;
\r
5483 FrontEndProgramStats stats;
\r
5485 stats.which = cps == &first ? 0 : 1;
\r
5486 stats.depth = cpstats->depth;
\r
5487 stats.nodes = cpstats->nodes;
\r
5488 stats.score = cpstats->score;
\r
5489 stats.time = cpstats->time;
\r
5490 stats.pv = cpstats->movelist;
\r
5491 stats.hint = lastHint;
\r
5492 stats.an_move_index = 0;
\r
5493 stats.an_move_count = 0;
\r
5495 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5496 stats.hint = cpstats->move_name;
\r
5497 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5498 stats.an_move_count = cpstats->nr_moves;
\r
5501 SetProgramStats( &stats );
\r
5504 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5505 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5506 char *bookHit = NULL;
\r
5508 //first determine if the incoming move brings opponent into his book
\r
5509 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5510 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5511 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5512 if(bookHit != NULL && !cps->bookSuspend) {
\r
5513 // make sure opponent is not going to reply after receiving move to book position
\r
5514 SendToProgram("force\n", cps);
\r
5515 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5517 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5518 // now arrange restart after book miss
\r
5520 // after a book hit we never send 'go', and the code after the call to this routine
\r
5521 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5522 char buf[MSG_SIZ];
\r
5523 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5524 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5525 SendToProgram(buf, cps);
\r
5526 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5527 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5528 SendToProgram("go\n", cps);
\r
5529 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5530 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5531 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5532 SendToProgram("go\n", cps);
\r
5533 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5535 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5538 char *savedMessage;
\r
5539 ChessProgramState *savedState;
\r
5540 void DeferredBookMove(void)
\r
5542 if(savedState->lastPing != savedState->lastPong)
\r
5543 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5545 HandleMachineMove(savedMessage, savedState);
\r
5549 HandleMachineMove(message, cps)
\r
5551 ChessProgramState *cps;
\r
5553 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5554 char realname[MSG_SIZ];
\r
5555 int fromX, fromY, toX, toY;
\r
5556 ChessMove moveType;
\r
5562 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5564 * Kludge to ignore BEL characters
\r
5566 while (*message == '\007') message++;
\r
5569 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5571 if(cps->debug && *message == '#') return;
\r
5574 * Look for book output
\r
5576 if (cps == &first && bookRequested) {
\r
5577 if (message[0] == '\t' || message[0] == ' ') {
\r
5578 /* Part of the book output is here; append it */
\r
5579 strcat(bookOutput, message);
\r
5580 strcat(bookOutput, " \n");
\r
5582 } else if (bookOutput[0] != NULLCHAR) {
\r
5583 /* All of book output has arrived; display it */
\r
5584 char *p = bookOutput;
\r
5585 while (*p != NULLCHAR) {
\r
5586 if (*p == '\t') *p = ' ';
\r
5589 DisplayInformation(bookOutput);
\r
5590 bookRequested = FALSE;
\r
5591 /* Fall through to parse the current output */
\r
5596 * Look for machine move.
\r
5598 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5599 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5601 /* This method is only useful on engines that support ping */
\r
5602 if (cps->lastPing != cps->lastPong) {
\r
5603 if (gameMode == BeginningOfGame) {
\r
5604 /* Extra move from before last new; ignore */
\r
5605 if (appData.debugMode) {
\r
5606 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5609 if (appData.debugMode) {
\r
5610 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5611 cps->which, gameMode);
\r
5614 SendToProgram("undo\n", cps);
\r
5619 switch (gameMode) {
\r
5620 case BeginningOfGame:
\r
5621 /* Extra move from before last reset; ignore */
\r
5622 if (appData.debugMode) {
\r
5623 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5630 /* Extra move after we tried to stop. The mode test is
\r
5631 not a reliable way of detecting this problem, but it's
\r
5632 the best we can do on engines that don't support ping.
\r
5634 if (appData.debugMode) {
\r
5635 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5636 cps->which, gameMode);
\r
5638 SendToProgram("undo\n", cps);
\r
5641 case MachinePlaysWhite:
\r
5642 case IcsPlayingWhite:
\r
5643 machineWhite = TRUE;
\r
5646 case MachinePlaysBlack:
\r
5647 case IcsPlayingBlack:
\r
5648 machineWhite = FALSE;
\r
5651 case TwoMachinesPlay:
\r
5652 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5655 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5656 if (appData.debugMode) {
\r
5658 "Ignoring move out of turn by %s, gameMode %d"
\r
5659 ", forwardMost %d\n",
\r
5660 cps->which, gameMode, forwardMostMove);
\r
5665 if (appData.debugMode) { int f = forwardMostMove;
\r
5666 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5667 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5669 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5670 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5671 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5672 /* Machine move could not be parsed; ignore it. */
\r
5673 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5674 machineMove, cps->which);
\r
5675 DisplayError(buf1, 0);
\r
5676 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5677 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5678 if (gameMode == TwoMachinesPlay) {
\r
5679 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5685 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5686 /* So we have to redo legality test with true e.p. status here, */
\r
5687 /* to make sure an illegal e.p. capture does not slip through, */
\r
5688 /* to cause a forfeit on a justified illegal-move complaint */
\r
5689 /* of the opponent. */
\r
5690 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5691 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5693 ChessMove moveType;
\r
5694 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5695 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5696 fromY, fromX, toY, toX, promoChar);
\r
5697 if (appData.debugMode) {
\r
5699 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5700 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5701 fprintf(debugFP, "castling rights\n");
\r
5703 if(moveType == IllegalMove) {
\r
5704 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5705 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5706 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5708 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5709 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5710 when they shouldn't (like TSCP-Gothic) */
\r
5711 switch(moveType) {
\r
5712 case WhiteASideCastleFR:
\r
5713 case BlackASideCastleFR:
\r
5715 currentMoveString[2]++;
\r
5717 case WhiteHSideCastleFR:
\r
5718 case BlackHSideCastleFR:
\r
5720 currentMoveString[2]--;
\r
5722 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5725 hintRequested = FALSE;
\r
5726 lastHint[0] = NULLCHAR;
\r
5727 bookRequested = FALSE;
\r
5728 /* Program may be pondering now */
\r
5729 cps->maybeThinking = TRUE;
\r
5730 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5731 if (cps->offeredDraw) cps->offeredDraw--;
\r
5734 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5736 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5737 ics_user_moved = 1;
\r
5738 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5739 char buf[3*MSG_SIZ];
\r
5741 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5742 programStats.depth,
\r
5743 programStats.score / 100.,
\r
5744 programStats.time / 100.,
\r
5745 u64ToDouble(programStats.nodes),
\r
5746 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5747 programStats.movelist);
\r
5752 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5753 strcpy(machineMove, currentMoveString);
\r
5754 strcat(machineMove, "\n");
\r
5755 strcpy(moveList[forwardMostMove], machineMove);
\r
5757 /* [AS] Save move info and clear stats for next move */
\r
5758 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5759 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5760 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5761 ClearProgramStats();
\r
5762 thinkOutput[0] = NULLCHAR;
\r
5763 hiddenThinkOutputState = 0;
\r
5765 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5767 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5768 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5771 while( count < adjudicateLossPlies ) {
\r
5772 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5775 score = -score; /* Flip score for winning side */
\r
5778 if( score > adjudicateLossThreshold ) {
\r
5785 if( count >= adjudicateLossPlies ) {
\r
5786 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5788 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5789 "Xboard adjudication",
\r
5796 if( gameMode == TwoMachinesPlay ) {
\r
5797 // [HGM] some adjudications useful with buggy engines
\r
5798 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5799 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5801 if(appData.testLegality)
\r
5802 // don't wait for engine to announce game end if we can judge ourselves
\r
5803 switch (MateTest(boards[forwardMostMove],
\r
5804 PosFlags(forwardMostMove), epFile,
\r
5805 castlingRights[forwardMostMove]) ) {
\r
5810 case MT_STALEMATE:
\r
5811 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5812 if(appData.checkMates) {
\r
5813 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5814 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5815 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide
\r
5816 || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5817 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!
\r
5818 "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5820 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5824 case MT_CHECKMATE:
\r
5825 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5826 if(appData.checkMates) {
\r
5827 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5828 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5829 GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:
\r
5830 ? BlackWins : WhiteWins, // reverse the result ( A!=1 is !A for a boolean)
\r
5831 "Xboard adjudication: Checkmate", GE_XBOARD );
\r
5837 if( appData.testLegality )
\r
5838 { /* [HGM] Some more adjudications for obstinate engines */
\r
5839 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5840 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5841 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5842 static int moveCount = 6;
\r
5844 /* First absolutely insufficient mating material. Count what is on board. */
\r
5845 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5846 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5850 { /* count B,N,R and other of each side */
\r
5853 NrK++; break; // [HGM] atomic: count Kings
\r
5857 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5858 bishopsColor |= 1 << ((i^j)&1);
\r
5863 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5864 bishopsColor |= 1 << ((i^j)&1);
\r
5874 case EmptySquare:
\r
5879 PawnAdvance += m; NrPawns++;
\r
5881 NrPieces += (p != EmptySquare);
\r
5882 NrW += ((int)p < (int)BlackPawn);
\r
5883 if(gameInfo.variant == VariantXiangqi &&
\r
5884 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5885 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5886 NrW -= ((int)p < (int)BlackPawn);
\r
5890 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5891 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5892 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5893 if(appData.checkMates) {
\r
5894 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5895 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5896 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5897 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5902 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5903 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5904 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5905 epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win
\r
5906 if(appData.checkMates) {
\r
5907 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5908 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5909 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5910 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5914 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5916 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm
\r
5917 if(appData.checkMates) {
\r
5918 /* but only adjudicate if adjudication enabled */
\r
5919 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5920 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5921 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5922 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5929 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5930 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5931 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5932 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5933 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5935 /* always flag draws, for judging claims */
\r
5936 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5938 if(appData.materialDraws) {
\r
5939 /* but only adjudicate them if adjudication enabled */
\r
5940 SendToProgram("force\n", cps->other); // suppress reply
\r
5941 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5942 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5943 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5948 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5949 if(NrPieces == 4 &&
\r
5950 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5951 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5952 || NrWN==2 || NrBN==2 /* KNNK */
\r
5953 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5955 if(--moveCount < 0 && appData.trivialDraws)
\r
5956 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5957 SendToProgram("force\n", cps->other); // suppress reply
\r
5958 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5959 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5960 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5963 } else moveCount = 6;
\r
5967 if (appData.debugMode) { int i;
\r
5968 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5969 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5970 appData.drawRepeats);
\r
5971 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5972 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5976 /* Check for rep-draws */
\r
5978 for(k = forwardMostMove-2;
\r
5979 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5980 epStatus[k] < EP_UNKNOWN &&
\r
5981 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5985 if (appData.debugMode) {
\r
5986 fprintf(debugFP, " loop\n");
\r
5989 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5991 if (appData.debugMode) {
\r
5992 fprintf(debugFP, "match\n");
\r
5995 /* compare castling rights */
\r
5996 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5997 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5998 rights++; /* King lost rights, while rook still had them */
\r
5999 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
6000 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
6001 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
6002 rights++; /* but at least one rook lost them */
\r
6004 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
6005 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
6007 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
6008 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
6009 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
6013 if (appData.debugMode) {
\r
6014 for(i=0; i<nrCastlingRights; i++)
\r
6015 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
6018 if (appData.debugMode) {
\r
6019 fprintf(debugFP, " %d %d\n", rights, k);
\r
6022 if( rights == 0 && ++count > appData.drawRepeats-2
\r
6023 && appData.drawRepeats > 1) {
\r
6024 /* adjudicate after user-specified nr of repeats */
\r
6025 SendToProgram("force\n", cps->other); // suppress reply
\r
6026 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6027 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6028 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
6029 // [HGM] xiangqi: check for forbidden perpetuals
\r
6030 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
6031 for(m=forwardMostMove; m>k; m-=2) {
\r
6032 if(MateTest(boards[m], PosFlags(m),
\r
6033 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
6034 ourPerpetual = 0; // the current mover did not always check
\r
6035 if(MateTest(boards[m-1], PosFlags(m-1),
\r
6036 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
6037 hisPerpetual = 0; // the opponent did not always check
\r
6039 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
6040 ourPerpetual, hisPerpetual);
\r
6041 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6042 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6043 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6046 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6047 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6048 // Now check for perpetual chases
\r
6049 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
6050 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
6051 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
6052 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
6053 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
6054 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
6057 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
6058 break; // Abort repetition-checking loop.
\r
6060 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
6062 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6065 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6066 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6070 /* Now we test for 50-move draws. Determine ply count */
\r
6071 count = forwardMostMove;
\r
6072 /* look for last irreversble move */
\r
6073 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6075 /* if we hit starting position, add initial plies */
\r
6076 if( count == backwardMostMove )
\r
6077 count -= initialRulePlies;
\r
6078 count = forwardMostMove - count;
\r
6080 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6081 /* this is used to judge if draw claims are legal */
\r
6082 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6083 SendToProgram("force\n", cps->other); // suppress reply
\r
6084 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6085 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6086 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6090 /* if draw offer is pending, treat it as a draw claim
\r
6091 * when draw condition present, to allow engines a way to
\r
6092 * claim draws before making their move to avoid a race
\r
6093 * condition occurring after their move
\r
6095 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6097 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6098 p = "Draw claim: 50-move rule";
\r
6099 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6100 p = "Draw claim: 3-fold repetition";
\r
6101 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6102 p = "Draw claim: insufficient mating material";
\r
6104 SendToProgram("force\n", cps->other); // suppress reply
\r
6105 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6106 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6107 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6113 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6114 SendToProgram("force\n", cps->other); // suppress reply
\r
6115 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6116 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6118 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6125 if (gameMode == TwoMachinesPlay) {
\r
6126 /* [HGM] relaying draw offers moved to after reception of move */
\r
6127 /* and interpreting offer as claim if it brings draw condition */
\r
6128 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6129 SendToProgram("draw\n", cps->other);
\r
6131 if (cps->other->sendTime) {
\r
6132 SendTimeRemaining(cps->other,
\r
6133 cps->other->twoMachinesColor[0] == 'w');
\r
6135 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6136 if (firstMove && !bookHit) {
\r
6137 firstMove = FALSE;
\r
6138 if (cps->other->useColors) {
\r
6139 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6141 SendToProgram("go\n", cps->other);
\r
6143 cps->other->maybeThinking = TRUE;
\r
6146 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6148 if (!pausing && appData.ringBellAfterMoves) {
\r
6153 * Reenable menu items that were disabled while
\r
6154 * machine was thinking
\r
6156 if (gameMode != TwoMachinesPlay)
\r
6157 SetUserThinkingEnables();
\r
6159 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6160 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6161 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6163 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6165 strcpy(bookMove, "move ");
\r
6166 strcat(bookMove, bookHit);
\r
6167 message = bookMove;
\r
6169 programStats.nodes = programStats.depth = programStats.time =
\r
6170 programStats.score = programStats.got_only_move = 0;
\r
6171 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6173 if(cps->lastPing != cps->lastPong) {
\r
6174 savedMessage = message; // args for deferred call
\r
6176 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6179 goto FakeBookMove;
\r
6185 /* Set special modes for chess engines. Later something general
\r
6186 * could be added here; for now there is just one kludge feature,
\r
6187 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6188 * when "xboard" is given as an interactive command.
\r
6190 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6191 cps->useSigint = FALSE;
\r
6192 cps->useSigterm = FALSE;
\r
6195 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6196 * want this, I was asked to put it in, and obliged.
\r
6198 if (!strncmp(message, "setboard ", 9)) {
\r
6199 Board initial_position; int i;
\r
6201 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6203 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6204 DisplayError(_("Bad FEN received from engine"), 0);
\r
6207 Reset(FALSE, FALSE);
\r
6208 CopyBoard(boards[0], initial_position);
\r
6209 initialRulePlies = FENrulePlies;
\r
6210 epStatus[0] = FENepStatus;
\r
6211 for( i=0; i<nrCastlingRights; i++ )
\r
6212 castlingRights[0][i] = FENcastlingRights[i];
\r
6213 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6214 else gameMode = MachinePlaysBlack;
\r
6215 DrawPosition(FALSE, boards[currentMove]);
\r
6221 * Look for communication commands
\r
6223 if (!strncmp(message, "telluser ", 9)) {
\r
6224 DisplayNote(message + 9);
\r
6227 if (!strncmp(message, "tellusererror ", 14)) {
\r
6228 DisplayError(message + 14, 0);
\r
6231 if (!strncmp(message, "tellopponent ", 13)) {
\r
6232 if (appData.icsActive) {
\r
6234 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6238 DisplayNote(message + 13);
\r
6242 if (!strncmp(message, "tellothers ", 11)) {
\r
6243 if (appData.icsActive) {
\r
6245 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6251 if (!strncmp(message, "tellall ", 8)) {
\r
6252 if (appData.icsActive) {
\r
6254 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6258 DisplayNote(message + 8);
\r
6262 if (strncmp(message, "warning", 7) == 0) {
\r
6263 /* Undocumented feature, use tellusererror in new code */
\r
6264 DisplayError(message, 0);
\r
6267 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6268 strcpy(realname, cps->tidy);
\r
6269 strcat(realname, " query");
\r
6270 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6273 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6274 * sent until we are logged on. Crafty kibitzes have been known to
\r
6275 * interfere with the login process.
\r
6278 if (!strncmp(message, "tellics ", 8)) {
\r
6279 SendToICS(message + 8);
\r
6283 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6284 SendToICS(ics_prefix);
\r
6285 SendToICS(message + 15);
\r
6289 /* The following are for backward compatibility only */
\r
6290 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6291 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6292 SendToICS(ics_prefix);
\r
6293 SendToICS(message);
\r
6298 if (strncmp(message, "feature ", 8) == 0) {
\r
6299 ParseFeatures(message+8, cps);
\r
6301 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6305 * If the move is illegal, cancel it and redraw the board.
\r
6306 * Also deal with other error cases. Matching is rather loose
\r
6307 * here to accommodate engines written before the spec.
\r
6309 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6310 strncmp(message, "Error", 5) == 0) {
\r
6311 if (StrStr(message, "name") ||
\r
6312 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6313 StrStr(message, "result") || StrStr(message, "board") ||
\r
6314 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6315 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6316 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6317 StrStr(message, "accepted")) {
\r
6320 if (StrStr(message, "protover")) {
\r
6321 /* Program is responding to input, so it's apparently done
\r
6322 initializing, and this error message indicates it is
\r
6323 protocol version 1. So we don't need to wait any longer
\r
6324 for it to initialize and send feature commands. */
\r
6325 FeatureDone(cps, 1);
\r
6326 cps->protocolVersion = 1;
\r
6329 cps->maybeThinking = FALSE;
\r
6331 if (StrStr(message, "draw")) {
\r
6332 /* Program doesn't have "draw" command */
\r
6333 cps->sendDrawOffers = 0;
\r
6336 if (cps->sendTime != 1 &&
\r
6337 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6338 /* Program apparently doesn't have "time" or "otim" command */
\r
6339 cps->sendTime = 0;
\r
6342 if (StrStr(message, "analyze")) {
\r
6343 cps->analysisSupport = FALSE;
\r
6344 cps->analyzing = FALSE;
\r
6345 Reset(FALSE, TRUE);
\r
6346 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6347 DisplayError(buf2, 0);
\r
6350 if (StrStr(message, "(no matching move)st")) {
\r
6351 /* Special kludge for GNU Chess 4 only */
\r
6352 cps->stKludge = TRUE;
\r
6353 SendTimeControl(cps, movesPerSession, timeControl,
\r
6354 timeIncrement, appData.searchDepth,
\r
6358 if (StrStr(message, "(no matching move)sd")) {
\r
6359 /* Special kludge for GNU Chess 4 only */
\r
6360 cps->sdKludge = TRUE;
\r
6361 SendTimeControl(cps, movesPerSession, timeControl,
\r
6362 timeIncrement, appData.searchDepth,
\r
6366 if (!StrStr(message, "llegal")) {
\r
6369 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6370 gameMode == IcsIdle) return;
\r
6371 if (forwardMostMove <= backwardMostMove) return;
\r
6373 /* Following removed: it caused a bug where a real illegal move
\r
6374 message in analyze mored would be ignored. */
\r
6375 if (cps == &first && programStats.ok_to_send == 0) {
\r
6376 /* Bogus message from Crafty responding to "." This filtering
\r
6377 can miss some of the bad messages, but fortunately the bug
\r
6378 is fixed in current Crafty versions, so it doesn't matter. */
\r
6382 if (pausing) PauseEvent();
\r
6383 if (gameMode == PlayFromGameFile) {
\r
6384 /* Stop reading this game file */
\r
6385 gameMode = EditGame;
\r
6388 currentMove = --forwardMostMove;
\r
6389 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6391 DisplayBothClocks();
\r
6392 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6393 parseList[currentMove], cps->which);
\r
6394 DisplayMoveError(buf1);
\r
6395 DrawPosition(FALSE, boards[currentMove]);
\r
6397 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6398 /* only passes fully legal moves */
\r
6399 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6400 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6401 "False illegal-move claim", GE_XBOARD );
\r
6405 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6406 /* Program has a broken "time" command that
\r
6407 outputs a string not ending in newline.
\r
6409 cps->sendTime = 0;
\r
6413 * If chess program startup fails, exit with an error message.
\r
6414 * Attempts to recover here are futile.
\r
6416 if ((StrStr(message, "unknown host") != NULL)
\r
6417 || (StrStr(message, "No remote directory") != NULL)
\r
6418 || (StrStr(message, "not found") != NULL)
\r
6419 || (StrStr(message, "No such file") != NULL)
\r
6420 || (StrStr(message, "can't alloc") != NULL)
\r
6421 || (StrStr(message, "Permission denied") != NULL)) {
\r
6423 cps->maybeThinking = FALSE;
\r
6424 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6425 cps->which, cps->program, cps->host, message);
\r
6426 RemoveInputSource(cps->isr);
\r
6427 DisplayFatalError(buf1, 0, 1);
\r
6432 * Look for hint output
\r
6434 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6435 if (cps == &first && hintRequested) {
\r
6436 hintRequested = FALSE;
\r
6437 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6438 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6439 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6440 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6441 fromY, fromX, toY, toX, promoChar, buf1);
\r
6442 sprintf(buf2, _("Hint: %s"), buf1);
\r
6443 DisplayInformation(buf2);
\r
6445 /* Hint move could not be parsed!? */
\r
6447 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6448 buf1, cps->which);
\r
6449 DisplayError(buf2, 0);
\r
6452 strcpy(lastHint, buf1);
\r
6458 * Ignore other messages if game is not in progress
\r
6460 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6461 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6464 * look for win, lose, draw, or draw offer
\r
6466 if (strncmp(message, "1-0", 3) == 0) {
\r
6467 char *p, *q, *r = "";
\r
6468 p = strchr(message, '{');
\r
6470 q = strchr(p, '}');
\r
6476 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6478 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6479 char *p, *q, *r = "";
\r
6480 p = strchr(message, '{');
\r
6482 q = strchr(p, '}');
\r
6488 /* Kludge for Arasan 4.1 bug */
\r
6489 if (strcmp(r, "Black resigns") == 0) {
\r
6490 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6493 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6495 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6496 char *p, *q, *r = "";
\r
6497 p = strchr(message, '{');
\r
6499 q = strchr(p, '}');
\r
6506 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6509 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6510 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6512 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6513 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6515 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6516 strncmp(message, "Black matches", 13) == 0 ) {
\r
6517 /* [HGM] ignore GNUShogi noises */
\r
6519 } else if (strncmp(message, "White", 5) == 0 &&
\r
6520 message[5] != '(' &&
\r
6521 StrStr(message, "Black") == NULL) {
\r
6522 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6524 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6525 message[5] != '(') {
\r
6526 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6528 } else if (strcmp(message, "resign") == 0 ||
\r
6529 strcmp(message, "computer resigns") == 0) {
\r
6530 switch (gameMode) {
\r
6531 case MachinePlaysBlack:
\r
6532 case IcsPlayingBlack:
\r
6533 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6535 case MachinePlaysWhite:
\r
6536 case IcsPlayingWhite:
\r
6537 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6539 case TwoMachinesPlay:
\r
6540 if (cps->twoMachinesColor[0] == 'w')
\r
6541 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6543 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6546 /* can't happen */
\r
6550 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6551 switch (gameMode) {
\r
6552 case MachinePlaysBlack:
\r
6553 case IcsPlayingBlack:
\r
6554 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6556 case MachinePlaysWhite:
\r
6557 case IcsPlayingWhite:
\r
6558 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6560 case TwoMachinesPlay:
\r
6561 if (cps->twoMachinesColor[0] == 'w')
\r
6562 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6564 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6567 /* can't happen */
\r
6571 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6572 switch (gameMode) {
\r
6573 case MachinePlaysBlack:
\r
6574 case IcsPlayingBlack:
\r
6575 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6577 case MachinePlaysWhite:
\r
6578 case IcsPlayingWhite:
\r
6579 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6581 case TwoMachinesPlay:
\r
6582 if (cps->twoMachinesColor[0] == 'w')
\r
6583 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6585 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6588 /* can't happen */
\r
6592 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6593 if (WhiteOnMove(forwardMostMove)) {
\r
6594 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6596 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6599 } else if (strstr(message, "Draw") != NULL ||
\r
6600 strstr(message, "game is a draw") != NULL) {
\r
6601 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6603 } else if (strstr(message, "offer") != NULL &&
\r
6604 strstr(message, "draw") != NULL) {
\r
6606 if (appData.zippyPlay && first.initDone) {
\r
6607 /* Relay offer to ICS */
\r
6608 SendToICS(ics_prefix);
\r
6609 SendToICS("draw\n");
\r
6612 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6613 if (gameMode == TwoMachinesPlay) {
\r
6614 if (cps->other->offeredDraw) {
\r
6615 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6616 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6617 /* until after we also have move, to see if it is really claim */
\r
6621 if (cps->other->sendDrawOffers) {
\r
6622 SendToProgram("draw\n", cps->other);
\r
6626 } else if (gameMode == MachinePlaysWhite ||
\r
6627 gameMode == MachinePlaysBlack) {
\r
6628 if (userOfferedDraw) {
\r
6629 DisplayInformation(_("Machine accepts your draw offer"));
\r
6630 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6632 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6639 * Look for thinking output
\r
6641 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6642 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6644 int plylev, mvleft, mvtot, curscore, time;
\r
6645 char mvname[MOVE_LEN];
\r
6646 u64 nodes; // [DM]
\r
6648 int ignore = FALSE;
\r
6649 int prefixHint = FALSE;
\r
6650 mvname[0] = NULLCHAR;
\r
6652 switch (gameMode) {
\r
6653 case MachinePlaysBlack:
\r
6654 case IcsPlayingBlack:
\r
6655 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6657 case MachinePlaysWhite:
\r
6658 case IcsPlayingWhite:
\r
6659 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6664 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6665 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6667 case TwoMachinesPlay:
\r
6668 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6678 buf1[0] = NULLCHAR;
\r
6679 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6680 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6682 if (plyext != ' ' && plyext != '\t') {
\r
6686 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6687 if( cps->scoreIsAbsolute &&
\r
6688 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6690 curscore = -curscore;
\r
6694 programStats.depth = plylev;
\r
6695 programStats.nodes = nodes;
\r
6696 programStats.time = time;
\r
6697 programStats.score = curscore;
\r
6698 programStats.got_only_move = 0;
\r
6700 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6703 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6704 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6705 if(WhiteOnMove(forwardMostMove))
\r
6706 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6707 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6710 /* Buffer overflow protection */
\r
6711 if (buf1[0] != NULLCHAR) {
\r
6712 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6713 && appData.debugMode) {
\r
6715 "PV is too long; using the first %d bytes.\n",
\r
6716 sizeof(programStats.movelist) - 1);
\r
6719 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6721 sprintf(programStats.movelist, " no PV\n");
\r
6724 if (programStats.seen_stat) {
\r
6725 programStats.ok_to_send = 1;
\r
6728 if (strchr(programStats.movelist, '(') != NULL) {
\r
6729 programStats.line_is_book = 1;
\r
6730 programStats.nr_moves = 0;
\r
6731 programStats.moves_left = 0;
\r
6733 programStats.line_is_book = 0;
\r
6736 SendProgramStatsToFrontend( cps, &programStats );
\r
6739 [AS] Protect the thinkOutput buffer from overflow... this
\r
6740 is only useful if buf1 hasn't overflowed first!
\r
6742 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6744 (gameMode == TwoMachinesPlay ?
\r
6745 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6746 ((double) curscore) / 100.0,
\r
6747 prefixHint ? lastHint : "",
\r
6748 prefixHint ? " " : "" );
\r
6750 if( buf1[0] != NULLCHAR ) {
\r
6751 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6753 if( strlen(buf1) > max_len ) {
\r
6754 if( appData.debugMode) {
\r
6755 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6757 buf1[max_len+1] = '\0';
\r
6760 strcat( thinkOutput, buf1 );
\r
6763 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6764 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6765 DisplayMove(currentMove - 1);
\r
6766 DisplayAnalysis();
\r
6770 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6771 /* crafty (9.25+) says "(only move) <move>"
\r
6772 * if there is only 1 legal move
\r
6774 sscanf(p, "(only move) %s", buf1);
\r
6775 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6776 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6777 programStats.depth = 1;
\r
6778 programStats.nr_moves = 1;
\r
6779 programStats.moves_left = 1;
\r
6780 programStats.nodes = 1;
\r
6781 programStats.time = 1;
\r
6782 programStats.got_only_move = 1;
\r
6784 /* Not really, but we also use this member to
\r
6785 mean "line isn't going to change" (Crafty
\r
6786 isn't searching, so stats won't change) */
\r
6787 programStats.line_is_book = 1;
\r
6789 SendProgramStatsToFrontend( cps, &programStats );
\r
6791 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6792 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6793 DisplayMove(currentMove - 1);
\r
6794 DisplayAnalysis();
\r
6797 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6798 &time, &nodes, &plylev, &mvleft,
\r
6799 &mvtot, mvname) >= 5) {
\r
6800 /* The stat01: line is from Crafty (9.29+) in response
\r
6801 to the "." command */
\r
6802 programStats.seen_stat = 1;
\r
6803 cps->maybeThinking = TRUE;
\r
6805 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6808 programStats.depth = plylev;
\r
6809 programStats.time = time;
\r
6810 programStats.nodes = nodes;
\r
6811 programStats.moves_left = mvleft;
\r
6812 programStats.nr_moves = mvtot;
\r
6813 strcpy(programStats.move_name, mvname);
\r
6814 programStats.ok_to_send = 1;
\r
6815 programStats.movelist[0] = '\0';
\r
6817 SendProgramStatsToFrontend( cps, &programStats );
\r
6819 DisplayAnalysis();
\r
6822 } else if (strncmp(message,"++",2) == 0) {
\r
6823 /* Crafty 9.29+ outputs this */
\r
6824 programStats.got_fail = 2;
\r
6827 } else if (strncmp(message,"--",2) == 0) {
\r
6828 /* Crafty 9.29+ outputs this */
\r
6829 programStats.got_fail = 1;
\r
6832 } else if (thinkOutput[0] != NULLCHAR &&
\r
6833 strncmp(message, " ", 4) == 0) {
\r
6834 unsigned message_len;
\r
6837 while (*p && *p == ' ') p++;
\r
6839 message_len = strlen( p );
\r
6841 /* [AS] Avoid buffer overflow */
\r
6842 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6843 strcat(thinkOutput, " ");
\r
6844 strcat(thinkOutput, p);
\r
6847 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6848 strcat(programStats.movelist, " ");
\r
6849 strcat(programStats.movelist, p);
\r
6852 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6853 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6854 DisplayMove(currentMove - 1);
\r
6855 DisplayAnalysis();
\r
6861 buf1[0] = NULLCHAR;
\r
6863 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6864 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6866 ChessProgramStats cpstats;
\r
6868 if (plyext != ' ' && plyext != '\t') {
\r
6872 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6873 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6874 curscore = -curscore;
\r
6877 cpstats.depth = plylev;
\r
6878 cpstats.nodes = nodes;
\r
6879 cpstats.time = time;
\r
6880 cpstats.score = curscore;
\r
6881 cpstats.got_only_move = 0;
\r
6882 cpstats.movelist[0] = '\0';
\r
6884 if (buf1[0] != NULLCHAR) {
\r
6885 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6888 cpstats.ok_to_send = 0;
\r
6889 cpstats.line_is_book = 0;
\r
6890 cpstats.nr_moves = 0;
\r
6891 cpstats.moves_left = 0;
\r
6893 SendProgramStatsToFrontend( cps, &cpstats );
\r
6900 /* Parse a game score from the character string "game", and
\r
6901 record it as the history of the current game. The game
\r
6902 score is NOT assumed to start from the standard position.
\r
6903 The display is not updated in any way.
\r
6906 ParseGameHistory(game)
\r
6909 ChessMove moveType;
\r
6910 int fromX, fromY, toX, toY, boardIndex;
\r
6913 char buf[MSG_SIZ];
\r
6915 if (appData.debugMode)
\r
6916 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6918 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6919 gameInfo.site = StrSave(appData.icsHost);
\r
6920 gameInfo.date = PGNDate();
\r
6921 gameInfo.round = StrSave("-");
\r
6923 /* Parse out names of players */
\r
6924 while (*game == ' ') game++;
\r
6926 while (*game != ' ') *p++ = *game++;
\r
6928 gameInfo.white = StrSave(buf);
\r
6929 while (*game == ' ') game++;
\r
6931 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6933 gameInfo.black = StrSave(buf);
\r
6936 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6939 yyboardindex = boardIndex;
\r
6940 moveType = (ChessMove) yylex();
\r
6941 switch (moveType) {
\r
6942 case IllegalMove: /* maybe suicide chess, etc. */
\r
6943 if (appData.debugMode) {
\r
6944 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6945 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6946 setbuf(debugFP, NULL);
\r
6948 case WhitePromotionChancellor:
\r
6949 case BlackPromotionChancellor:
\r
6950 case WhitePromotionArchbishop:
\r
6951 case BlackPromotionArchbishop:
\r
6952 case WhitePromotionQueen:
\r
6953 case BlackPromotionQueen:
\r
6954 case WhitePromotionRook:
\r
6955 case BlackPromotionRook:
\r
6956 case WhitePromotionBishop:
\r
6957 case BlackPromotionBishop:
\r
6958 case WhitePromotionKnight:
\r
6959 case BlackPromotionKnight:
\r
6960 case WhitePromotionKing:
\r
6961 case BlackPromotionKing:
\r
6963 case WhiteCapturesEnPassant:
\r
6964 case BlackCapturesEnPassant:
\r
6965 case WhiteKingSideCastle:
\r
6966 case WhiteQueenSideCastle:
\r
6967 case BlackKingSideCastle:
\r
6968 case BlackQueenSideCastle:
\r
6969 case WhiteKingSideCastleWild:
\r
6970 case WhiteQueenSideCastleWild:
\r
6971 case BlackKingSideCastleWild:
\r
6972 case BlackQueenSideCastleWild:
\r
6974 case WhiteHSideCastleFR:
\r
6975 case WhiteASideCastleFR:
\r
6976 case BlackHSideCastleFR:
\r
6977 case BlackASideCastleFR:
\r
6979 fromX = currentMoveString[0] - AAA;
\r
6980 fromY = currentMoveString[1] - ONE;
\r
6981 toX = currentMoveString[2] - AAA;
\r
6982 toY = currentMoveString[3] - ONE;
\r
6983 promoChar = currentMoveString[4];
\r
6987 fromX = moveType == WhiteDrop ?
\r
6988 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6989 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6990 fromY = DROP_RANK;
\r
6991 toX = currentMoveString[2] - AAA;
\r
6992 toY = currentMoveString[3] - ONE;
\r
6993 promoChar = NULLCHAR;
\r
6995 case AmbiguousMove:
\r
6997 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6998 if (appData.debugMode) {
\r
6999 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
7000 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
7001 setbuf(debugFP, NULL);
\r
7003 DisplayError(buf, 0);
\r
7005 case ImpossibleMove:
\r
7007 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
7008 if (appData.debugMode) {
\r
7009 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
7010 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
7011 setbuf(debugFP, NULL);
\r
7013 DisplayError(buf, 0);
\r
7015 case (ChessMove) 0: /* end of file */
\r
7016 if (boardIndex < backwardMostMove) {
\r
7017 /* Oops, gap. How did that happen? */
\r
7018 DisplayError(_("Gap in move list"), 0);
\r
7021 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7022 if (boardIndex > forwardMostMove) {
\r
7023 forwardMostMove = boardIndex;
\r
7027 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
7028 strcat(parseList[boardIndex-1], " ");
\r
7029 strcat(parseList[boardIndex-1], yy_text);
\r
7041 case GameUnfinished:
\r
7042 if (gameMode == IcsExamining) {
\r
7043 if (boardIndex < backwardMostMove) {
\r
7044 /* Oops, gap. How did that happen? */
\r
7047 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7050 gameInfo.result = moveType;
\r
7051 p = strchr(yy_text, '{');
\r
7052 if (p == NULL) p = strchr(yy_text, '(');
\r
7055 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7057 q = strchr(p, *p == '{' ? '}' : ')');
\r
7058 if (q != NULL) *q = NULLCHAR;
\r
7061 gameInfo.resultDetails = StrSave(p);
\r
7064 if (boardIndex >= forwardMostMove &&
\r
7065 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7066 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7069 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7070 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7071 parseList[boardIndex]);
\r
7072 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7073 /* currentMoveString is set as a side-effect of yylex */
\r
7074 strcpy(moveList[boardIndex], currentMoveString);
\r
7075 strcat(moveList[boardIndex], "\n");
\r
7077 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7078 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7079 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7081 case MT_STALEMATE:
\r
7085 if(gameInfo.variant != VariantShogi)
\r
7086 strcat(parseList[boardIndex - 1], "+");
\r
7088 case MT_CHECKMATE:
\r
7089 strcat(parseList[boardIndex - 1], "#");
\r
7096 /* Apply a move to the given board */
\r
7098 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7099 int fromX, fromY, toX, toY;
\r
7103 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7105 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7106 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7107 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7110 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7111 oldEP = epStatus[p-1];
\r
7112 epStatus[p] = EP_NONE;
\r
7114 if( board[toY][toX] != EmptySquare )
\r
7115 epStatus[p] = EP_CAPTURE;
\r
7117 if( board[fromY][fromX] == WhitePawn ) {
\r
7118 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7119 epStatus[p] = EP_PAWN_MOVE;
\r
7120 if( toY-fromY==2) {
\r
7121 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7122 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7123 epStatus[p] = toX | berolina;
\r
7124 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7125 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7126 epStatus[p] = toX;
\r
7129 if( board[fromY][fromX] == BlackPawn ) {
\r
7130 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7131 epStatus[p] = EP_PAWN_MOVE;
\r
7132 if( toY-fromY== -2) {
\r
7133 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7134 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7135 epStatus[p] = toX | berolina;
\r
7136 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7137 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7138 epStatus[p] = toX;
\r
7142 for(i=0; i<nrCastlingRights; i++) {
\r
7143 castlingRights[p][i] = castlingRights[p-1][i];
\r
7144 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7145 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7146 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7151 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7152 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7153 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7155 if (fromX == toX && fromY == toY) return;
\r
7157 if (fromY == DROP_RANK) {
\r
7158 /* must be first */
\r
7159 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7161 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7162 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7163 if(gameInfo.variant == VariantKnightmate)
\r
7164 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7166 /* Code added by Tord: */
\r
7167 /* FRC castling assumed when king captures friendly rook. */
\r
7168 if (board[fromY][fromX] == WhiteKing &&
\r
7169 board[toY][toX] == WhiteRook) {
\r
7170 board[fromY][fromX] = EmptySquare;
\r
7171 board[toY][toX] = EmptySquare;
\r
7173 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7175 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7177 } else if (board[fromY][fromX] == BlackKing &&
\r
7178 board[toY][toX] == BlackRook) {
\r
7179 board[fromY][fromX] = EmptySquare;
\r
7180 board[toY][toX] = EmptySquare;
\r
7182 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7184 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7186 /* End of code added by Tord */
\r
7188 } else if (board[fromY][fromX] == king
\r
7189 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7190 && toY == fromY && toX > fromX+1) {
\r
7191 board[fromY][fromX] = EmptySquare;
\r
7192 board[toY][toX] = king;
\r
7193 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7194 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7195 } else if (board[fromY][fromX] == king
\r
7196 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7197 && toY == fromY && toX < fromX-1) {
\r
7198 board[fromY][fromX] = EmptySquare;
\r
7199 board[toY][toX] = king;
\r
7200 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7201 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7202 } else if (board[fromY][fromX] == WhitePawn
\r
7203 && toY == BOARD_HEIGHT-1
\r
7204 && gameInfo.variant != VariantXiangqi
\r
7206 /* white pawn promotion */
\r
7207 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7208 if (board[toY][toX] == EmptySquare) {
\r
7209 board[toY][toX] = WhiteQueen;
\r
7211 if(gameInfo.variant==VariantBughouse ||
\r
7212 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7213 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7214 board[fromY][fromX] = EmptySquare;
\r
7215 } else if ((fromY == BOARD_HEIGHT-4)
\r
7217 && gameInfo.variant != VariantXiangqi
\r
7218 && gameInfo.variant != VariantBerolina
\r
7219 && (board[fromY][fromX] == WhitePawn)
\r
7220 && (board[toY][toX] == EmptySquare)) {
\r
7221 board[fromY][fromX] = EmptySquare;
\r
7222 board[toY][toX] = WhitePawn;
\r
7223 captured = board[toY - 1][toX];
\r
7224 board[toY - 1][toX] = EmptySquare;
\r
7225 } else if ((fromY == BOARD_HEIGHT-4)
\r
7227 && gameInfo.variant == VariantBerolina
\r
7228 && (board[fromY][fromX] == WhitePawn)
\r
7229 && (board[toY][toX] == EmptySquare)) {
\r
7230 board[fromY][fromX] = EmptySquare;
\r
7231 board[toY][toX] = WhitePawn;
\r
7232 if(oldEP & EP_BEROLIN_A) {
\r
7233 captured = board[fromY][fromX-1];
\r
7234 board[fromY][fromX-1] = EmptySquare;
\r
7235 }else{ captured = board[fromY][fromX+1];
\r
7236 board[fromY][fromX+1] = EmptySquare;
\r
7238 } else if (board[fromY][fromX] == king
\r
7239 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7240 && toY == fromY && toX > fromX+1) {
\r
7241 board[fromY][fromX] = EmptySquare;
\r
7242 board[toY][toX] = king;
\r
7243 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7244 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7245 } else if (board[fromY][fromX] == king
\r
7246 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7247 && toY == fromY && toX < fromX-1) {
\r
7248 board[fromY][fromX] = EmptySquare;
\r
7249 board[toY][toX] = king;
\r
7250 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7251 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7252 } else if (fromY == 7 && fromX == 3
\r
7253 && board[fromY][fromX] == BlackKing
\r
7254 && toY == 7 && toX == 5) {
\r
7255 board[fromY][fromX] = EmptySquare;
\r
7256 board[toY][toX] = BlackKing;
\r
7257 board[fromY][7] = EmptySquare;
\r
7258 board[toY][4] = BlackRook;
\r
7259 } else if (fromY == 7 && fromX == 3
\r
7260 && board[fromY][fromX] == BlackKing
\r
7261 && toY == 7 && toX == 1) {
\r
7262 board[fromY][fromX] = EmptySquare;
\r
7263 board[toY][toX] = BlackKing;
\r
7264 board[fromY][0] = EmptySquare;
\r
7265 board[toY][2] = BlackRook;
\r
7266 } else if (board[fromY][fromX] == BlackPawn
\r
7268 && gameInfo.variant != VariantXiangqi
\r
7270 /* black pawn promotion */
\r
7271 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7272 if (board[0][toX] == EmptySquare) {
\r
7273 board[0][toX] = BlackQueen;
\r
7275 if(gameInfo.variant==VariantBughouse ||
\r
7276 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7277 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7278 board[fromY][fromX] = EmptySquare;
\r
7279 } else if ((fromY == 3)
\r
7281 && gameInfo.variant != VariantXiangqi
\r
7282 && gameInfo.variant != VariantBerolina
\r
7283 && (board[fromY][fromX] == BlackPawn)
\r
7284 && (board[toY][toX] == EmptySquare)) {
\r
7285 board[fromY][fromX] = EmptySquare;
\r
7286 board[toY][toX] = BlackPawn;
\r
7287 captured = board[toY + 1][toX];
\r
7288 board[toY + 1][toX] = EmptySquare;
\r
7289 } else if ((fromY == 3)
\r
7291 && gameInfo.variant == VariantBerolina
\r
7292 && (board[fromY][fromX] == BlackPawn)
\r
7293 && (board[toY][toX] == EmptySquare)) {
\r
7294 board[fromY][fromX] = EmptySquare;
\r
7295 board[toY][toX] = BlackPawn;
\r
7296 if(oldEP & EP_BEROLIN_A) {
\r
7297 captured = board[fromY][fromX-1];
\r
7298 board[fromY][fromX-1] = EmptySquare;
\r
7299 }else{ captured = board[fromY][fromX+1];
\r
7300 board[fromY][fromX+1] = EmptySquare;
\r
7303 board[toY][toX] = board[fromY][fromX];
\r
7304 board[fromY][fromX] = EmptySquare;
\r
7307 /* [HGM] now we promote for Shogi, if needed */
\r
7308 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7309 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7312 if (gameInfo.holdingsWidth != 0) {
\r
7314 /* !!A lot more code needs to be written to support holdings */
\r
7315 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7316 /* penultimate board files, so they are automaticlly stored */
\r
7317 /* in the game history. */
\r
7318 if (fromY == DROP_RANK) {
\r
7319 /* Delete from holdings, by decreasing count */
\r
7320 /* and erasing image if necessary */
\r
7322 if(p < (int) BlackPawn) { /* white drop */
\r
7323 p -= (int)WhitePawn;
\r
7324 if(p >= gameInfo.holdingsSize) p = 0;
\r
7325 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7326 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7327 } else { /* black drop */
\r
7328 p -= (int)BlackPawn;
\r
7329 if(p >= gameInfo.holdingsSize) p = 0;
\r
7330 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7331 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7334 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7335 && gameInfo.variant != VariantBughouse ) {
\r
7336 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7337 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7338 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7339 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7341 p = (int) captured;
\r
7342 if (p >= (int) BlackPawn) {
\r
7343 p -= (int)BlackPawn;
\r
7344 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7345 /* in Shogi restore piece to its original first */
\r
7346 captured = (ChessSquare) (DEMOTED captured);
\r
7349 p = PieceToNumber((ChessSquare)p);
\r
7350 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7351 board[p][BOARD_WIDTH-2]++;
\r
7352 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7354 p -= (int)WhitePawn;
\r
7355 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7356 captured = (ChessSquare) (DEMOTED captured);
\r
7359 p = PieceToNumber((ChessSquare)p);
\r
7360 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7361 board[BOARD_HEIGHT-1-p][1]++;
\r
7362 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7366 } else if (gameInfo.variant == VariantAtomic) {
\r
7367 if (captured != EmptySquare) {
\r
7369 for (y = toY-1; y <= toY+1; y++) {
\r
7370 for (x = toX-1; x <= toX+1; x++) {
\r
7371 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7372 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7373 board[y][x] = EmptySquare;
\r
7377 board[toY][toX] = EmptySquare;
\r
7380 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7381 /* [HGM] Shogi promotions */
\r
7382 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7387 /* Updates forwardMostMove */
\r
7389 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7390 int fromX, fromY, toX, toY;
\r
7393 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7395 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7396 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7397 piece = boards[forwardMostMove][fromY][fromX];
\r
7398 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7399 if(gameInfo.variant == VariantKnightmate)
\r
7400 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7401 if(forwardMostMove == 0) {
\r
7402 if(blackPlaysFirst)
\r
7403 fprintf(serverMoves, "%s;", second.tidy);
\r
7404 fprintf(serverMoves, "%s;", first.tidy);
\r
7405 if(!blackPlaysFirst)
\r
7406 fprintf(serverMoves, "%s;", second.tidy);
\r
7407 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7408 lastLoadFlag = loadFlag;
\r
7409 // print base move
\r
7410 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7411 // print castling suffix
\r
7412 if( toY == fromY && piece == king ) {
\r
7414 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7416 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7419 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7420 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7421 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7423 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7424 // promotion suffix
\r
7425 if(promoChar != NULLCHAR)
\r
7426 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7428 fprintf(serverMoves, "/%d/%d",
\r
7429 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7430 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7431 else timeLeft = blackTimeRemaining/1000;
\r
7432 fprintf(serverMoves, "/%d", timeLeft);
\r
7434 fflush(serverMoves);
\r
7437 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7438 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7443 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7444 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7445 if (commentList[forwardMostMove+1] != NULL) {
\r
7446 free(commentList[forwardMostMove+1]);
\r
7447 commentList[forwardMostMove+1] = NULL;
\r
7449 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7450 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7451 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7452 gameInfo.result = GameUnfinished;
\r
7453 if (gameInfo.resultDetails != NULL) {
\r
7454 free(gameInfo.resultDetails);
\r
7455 gameInfo.resultDetails = NULL;
\r
7457 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7458 moveList[forwardMostMove - 1]);
\r
7459 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7460 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7461 fromY, fromX, toY, toX, promoChar,
\r
7462 parseList[forwardMostMove - 1]);
\r
7463 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7464 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7465 castlingRights[forwardMostMove]) ) {
\r
7467 case MT_STALEMATE:
\r
7471 if(gameInfo.variant != VariantShogi)
\r
7472 strcat(parseList[forwardMostMove - 1], "+");
\r
7474 case MT_CHECKMATE:
\r
7475 strcat(parseList[forwardMostMove - 1], "#");
\r
7478 if (appData.debugMode) {
\r
7479 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7484 /* Updates currentMove if not pausing */
\r
7486 ShowMove(fromX, fromY, toX, toY)
\r
7488 int instant = (gameMode == PlayFromGameFile) ?
\r
7489 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7490 if(appData.noGUI) return;
\r
7491 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7493 if (forwardMostMove == currentMove + 1) {
\r
7494 AnimateMove(boards[forwardMostMove - 1],
\r
7495 fromX, fromY, toX, toY);
\r
7497 if (appData.highlightLastMove) {
\r
7498 SetHighlights(fromX, fromY, toX, toY);
\r
7501 currentMove = forwardMostMove;
\r
7504 if (instant) return;
\r
7506 DisplayMove(currentMove - 1);
\r
7507 DrawPosition(FALSE, boards[currentMove]);
\r
7508 DisplayBothClocks();
\r
7509 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7512 void SendEgtPath(ChessProgramState *cps)
\r
7513 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7514 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7516 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7519 char c, *q = name+1, *r, *s;
\r
7521 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7522 while(*p && *p != ',') *q++ = *p++;
\r
7523 *q++ = ':'; *q = 0;
\r
7524 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7525 strcmp(name, ",nalimov:") == 0 ) {
\r
7526 // take nalimov path from the menu-changeable option first, if it is defined
\r
7527 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7528 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7530 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7531 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7532 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7533 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7534 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7535 c = *r; *r = 0; // temporarily null-terminate path info
\r
7536 *--q = 0; // strip of trailig ':' from name
\r
7537 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7539 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7541 if(*p == ',') p++; // read away comma to position for next format name
\r
7546 InitChessProgram(cps, setup)
\r
7547 ChessProgramState *cps;
\r
7548 int setup; /* [HGM] needed to setup FRC opening position */
\r
7550 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7551 if (appData.noChessProgram) return;
\r
7552 hintRequested = FALSE;
\r
7553 bookRequested = FALSE;
\r
7555 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7556 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7557 if(cps->memSize) { /* [HGM] memory */
\r
7558 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7559 SendToProgram(buf, cps);
\r
7561 SendEgtPath(cps); /* [HGM] EGT */
\r
7562 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7563 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7564 SendToProgram(buf, cps);
\r
7567 SendToProgram(cps->initString, cps);
\r
7568 if (gameInfo.variant != VariantNormal &&
\r
7569 gameInfo.variant != VariantLoadable
\r
7570 /* [HGM] also send variant if board size non-standard */
\r
7571 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7573 char *v = VariantName(gameInfo.variant);
\r
7574 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7575 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7576 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7577 DisplayFatalError(buf, 0, 1);
\r
7581 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7582 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7583 if( gameInfo.variant == VariantXiangqi )
\r
7584 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7585 if( gameInfo.variant == VariantShogi )
\r
7586 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7587 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7588 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7589 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7590 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7591 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7592 if( gameInfo.variant == VariantCourier )
\r
7593 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7594 if( gameInfo.variant == VariantSuper )
\r
7595 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7596 if( gameInfo.variant == VariantGreat )
\r
7597 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7600 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7601 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7602 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7603 if(StrStr(cps->variants, b) == NULL) {
\r
7604 // specific sized variant not known, check if general sizing allowed
\r
7605 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7606 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7607 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7608 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7609 DisplayFatalError(buf, 0, 1);
\r
7612 /* [HGM] here we really should compare with the maximum supported board size */
\r
7615 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7616 sprintf(buf, "variant %s\n", b);
\r
7617 SendToProgram(buf, cps);
\r
7619 currentlyInitializedVariant = gameInfo.variant;
\r
7621 /* [HGM] send opening position in FRC to first engine */
\r
7623 SendToProgram("force\n", cps);
\r
7624 SendBoard(cps, 0);
\r
7625 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7626 setboardSpoiledMachineBlack = 1;
\r
7629 if (cps->sendICS) {
\r
7630 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7631 SendToProgram(buf, cps);
\r
7633 cps->maybeThinking = FALSE;
\r
7634 cps->offeredDraw = 0;
\r
7635 if (!appData.icsActive) {
\r
7636 SendTimeControl(cps, movesPerSession, timeControl,
\r
7637 timeIncrement, appData.searchDepth,
\r
7640 if (appData.showThinking
\r
7641 // [HGM] thinking: four options require thinking output to be sent
\r
7642 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7644 SendToProgram("post\n", cps);
\r
7646 SendToProgram("hard\n", cps);
\r
7647 if (!appData.ponderNextMove) {
\r
7648 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7649 it without being sure what state we are in first. "hard"
\r
7650 is not a toggle, so that one is OK.
\r
7652 SendToProgram("easy\n", cps);
\r
7654 if (cps->usePing) {
\r
7655 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7656 SendToProgram(buf, cps);
\r
7658 cps->initDone = TRUE;
\r
7663 StartChessProgram(cps)
\r
7664 ChessProgramState *cps;
\r
7666 char buf[MSG_SIZ];
\r
7669 if (appData.noChessProgram) return;
\r
7670 cps->initDone = FALSE;
\r
7672 if (strcmp(cps->host, "localhost") == 0) {
\r
7673 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7674 } else if (*appData.remoteShell == NULLCHAR) {
\r
7675 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7677 if (*appData.remoteUser == NULLCHAR) {
\r
7678 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7681 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7682 cps->host, appData.remoteUser, cps->program);
\r
7684 err = StartChildProcess(buf, "", &cps->pr);
\r
7688 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7689 DisplayFatalError(buf, err, 1);
\r
7695 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7696 if (cps->protocolVersion > 1) {
\r
7697 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7698 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7699 cps->comboCnt = 0; // and values of combo boxes
\r
7700 SendToProgram(buf, cps);
\r
7702 SendToProgram("xboard\n", cps);
\r
7708 TwoMachinesEventIfReady P((void))
\r
7710 if (first.lastPing != first.lastPong) {
\r
7711 DisplayMessage("", _("Waiting for first chess program"));
\r
7712 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7715 if (second.lastPing != second.lastPong) {
\r
7716 DisplayMessage("", _("Waiting for second chess program"));
\r
7717 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7721 TwoMachinesEvent();
\r
7725 NextMatchGame P((void))
\r
7727 int index; /* [HGM] autoinc: step lod index during match */
\r
7728 Reset(FALSE, TRUE);
\r
7729 if (*appData.loadGameFile != NULLCHAR) {
\r
7730 index = appData.loadGameIndex;
\r
7731 if(index < 0) { // [HGM] autoinc
\r
7732 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7733 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7735 LoadGameFromFile(appData.loadGameFile,
\r
7737 appData.loadGameFile, FALSE);
\r
7738 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7739 index = appData.loadPositionIndex;
\r
7740 if(index < 0) { // [HGM] autoinc
\r
7741 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7742 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7744 LoadPositionFromFile(appData.loadPositionFile,
\r
7746 appData.loadPositionFile);
\r
7748 TwoMachinesEventIfReady();
\r
7751 void UserAdjudicationEvent( int result )
\r
7753 ChessMove gameResult = GameIsDrawn;
\r
7755 if( result > 0 ) {
\r
7756 gameResult = WhiteWins;
\r
7758 else if( result < 0 ) {
\r
7759 gameResult = BlackWins;
\r
7762 if( gameMode == TwoMachinesPlay ) {
\r
7763 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7769 GameEnds(result, resultDetails, whosays)
\r
7771 char *resultDetails;
\r
7774 GameMode nextGameMode;
\r
7776 char buf[MSG_SIZ];
\r
7778 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7781 if (appData.debugMode) {
\r
7782 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7783 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7786 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7787 /* If we are playing on ICS, the server decides when the
\r
7788 game is over, but the engine can offer to draw, claim
\r
7789 a draw, or resign.
\r
7792 if (appData.zippyPlay && first.initDone) {
\r
7793 if (result == GameIsDrawn) {
\r
7794 /* In case draw still needs to be claimed */
\r
7795 SendToICS(ics_prefix);
\r
7796 SendToICS("draw\n");
\r
7797 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7798 SendToICS(ics_prefix);
\r
7799 SendToICS("resign\n");
\r
7803 endingGame = 0; /* [HGM] crash */
\r
7807 /* If we're loading the game from a file, stop */
\r
7808 if (whosays == GE_FILE) {
\r
7809 (void) StopLoadGameTimer();
\r
7810 gameFileFP = NULL;
\r
7813 /* Cancel draw offers */
\r
7814 first.offeredDraw = second.offeredDraw = 0;
\r
7816 /* If this is an ICS game, only ICS can really say it's done;
\r
7817 if not, anyone can. */
\r
7818 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7819 gameMode == IcsPlayingBlack ||
\r
7820 gameMode == IcsObserving ||
\r
7821 gameMode == IcsExamining);
\r
7823 if (!isIcsGame || whosays == GE_ICS) {
\r
7824 /* OK -- not an ICS game, or ICS said it was done */
\r
7826 if (!isIcsGame && !appData.noChessProgram)
\r
7827 SetUserThinkingEnables();
\r
7829 /* [HGM] if a machine claims the game end we verify this claim */
\r
7830 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7831 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7833 ChessMove trueResult = (ChessMove) -1;
\r
7835 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7836 first.twoMachinesColor[0] :
\r
7837 second.twoMachinesColor[0] ;
\r
7839 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7840 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7841 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7842 trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)
\r
7843 ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!
\r
7845 if(epStatus[forwardMostMove] == EP_STALEMATE) {
\r
7846 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7847 if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide ||
\r
7848 gameInfo.variant == VariantLosers) // [HGM] losers: in giveaway variants stalemate wins
\r
7849 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7852 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7853 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7854 || gameInfo.variant == VariantGreat) &&
\r
7855 (result == WhiteWins && claimer == 'w' ||
\r
7856 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7857 if (appData.debugMode) {
\r
7858 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7859 result, epStatus[forwardMostMove], forwardMostMove);
\r
7861 if(result != trueResult) {
\r
7862 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7863 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7864 resultDetails = buf;
\r
7867 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7868 && (forwardMostMove <= backwardMostMove ||
\r
7869 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7870 (claimer=='b')==(forwardMostMove&1))
\r
7872 /* [HGM] verify: draws that were not flagged are false claims */
\r
7873 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7874 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7875 resultDetails = buf;
\r
7877 /* (Claiming a loss is accepted no questions asked!) */
\r
7879 /* [HGM] bare: don't allow bare King to win */
\r
7880 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7881 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7882 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7883 && result != GameIsDrawn)
\r
7884 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7885 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7886 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7887 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7889 if (appData.debugMode) {
\r
7890 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7891 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7894 result = GameIsDrawn;
\r
7895 sprintf(buf, "%s but bare king", resultDetails);
\r
7896 resultDetails = buf;
\r
7902 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7903 if(result==WhiteWins) c = '+';
\r
7904 if(result==BlackWins) c = '-';
\r
7905 if(resultDetails != NULL)
\r
7906 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7908 if (resultDetails != NULL) {
\r
7909 gameInfo.result = result;
\r
7910 gameInfo.resultDetails = StrSave(resultDetails);
\r
7912 /* display last move only if game was not loaded from file */
\r
7913 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7914 DisplayMove(currentMove - 1);
\r
7916 if (forwardMostMove != 0) {
\r
7917 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7918 if (*appData.saveGameFile != NULLCHAR) {
\r
7919 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7920 } else if (appData.autoSaveGames) {
\r
7923 if (*appData.savePositionFile != NULLCHAR) {
\r
7924 SavePositionToFile(appData.savePositionFile);
\r
7929 /* Tell program how game ended in case it is learning */
\r
7930 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7931 /* engine died and we got here through time loss. In that */
\r
7932 /* case we will get a fatal error writing the pipe, which */
\r
7933 /* would otherwise lose us the PGN. */
\r
7934 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7935 /* output during GameEnds should never be fatal anymore */
\r
7936 if (gameMode == MachinePlaysWhite ||
\r
7937 gameMode == MachinePlaysBlack ||
\r
7938 gameMode == TwoMachinesPlay ||
\r
7939 gameMode == IcsPlayingWhite ||
\r
7940 gameMode == IcsPlayingBlack ||
\r
7941 gameMode == BeginningOfGame) {
\r
7942 char buf[MSG_SIZ];
\r
7943 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7945 if (first.pr != NoProc) {
\r
7946 SendToProgram(buf, &first);
\r
7948 if (second.pr != NoProc &&
\r
7949 gameMode == TwoMachinesPlay) {
\r
7950 SendToProgram(buf, &second);
\r
7955 if (appData.icsActive) {
\r
7956 if (appData.quietPlay &&
\r
7957 (gameMode == IcsPlayingWhite ||
\r
7958 gameMode == IcsPlayingBlack)) {
\r
7959 SendToICS(ics_prefix);
\r
7960 SendToICS("set shout 1\n");
\r
7962 nextGameMode = IcsIdle;
\r
7963 ics_user_moved = FALSE;
\r
7964 /* clean up premove. It's ugly when the game has ended and the
\r
7965 * premove highlights are still on the board.
\r
7968 gotPremove = FALSE;
\r
7969 ClearPremoveHighlights();
\r
7970 DrawPosition(FALSE, boards[currentMove]);
\r
7972 if (whosays == GE_ICS) {
\r
7975 if (gameMode == IcsPlayingWhite)
\r
7976 PlayIcsWinSound();
\r
7977 else if(gameMode == IcsPlayingBlack)
\r
7978 PlayIcsLossSound();
\r
7981 if (gameMode == IcsPlayingBlack)
\r
7982 PlayIcsWinSound();
\r
7983 else if(gameMode == IcsPlayingWhite)
\r
7984 PlayIcsLossSound();
\r
7987 PlayIcsDrawSound();
\r
7990 PlayIcsUnfinishedSound();
\r
7993 } else if (gameMode == EditGame ||
\r
7994 gameMode == PlayFromGameFile ||
\r
7995 gameMode == AnalyzeMode ||
\r
7996 gameMode == AnalyzeFile) {
\r
7997 nextGameMode = gameMode;
\r
7999 nextGameMode = EndOfGame;
\r
8004 nextGameMode = gameMode;
\r
8007 if (appData.noChessProgram) {
\r
8008 gameMode = nextGameMode;
\r
8010 endingGame = 0; /* [HGM] crash */
\r
8014 if (first.reuse) {
\r
8015 /* Put first chess program into idle state */
\r
8016 if (first.pr != NoProc &&
\r
8017 (gameMode == MachinePlaysWhite ||
\r
8018 gameMode == MachinePlaysBlack ||
\r
8019 gameMode == TwoMachinesPlay ||
\r
8020 gameMode == IcsPlayingWhite ||
\r
8021 gameMode == IcsPlayingBlack ||
\r
8022 gameMode == BeginningOfGame)) {
\r
8023 SendToProgram("force\n", &first);
\r
8024 if (first.usePing) {
\r
8025 char buf[MSG_SIZ];
\r
8026 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
8027 SendToProgram(buf, &first);
\r
8030 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8031 /* Kill off first chess program */
\r
8032 if (first.isr != NULL)
\r
8033 RemoveInputSource(first.isr);
\r
8036 if (first.pr != NoProc) {
\r
8037 ExitAnalyzeMode();
\r
8038 DoSleep( appData.delayBeforeQuit );
\r
8039 SendToProgram("quit\n", &first);
\r
8040 DoSleep( appData.delayAfterQuit );
\r
8041 DestroyChildProcess(first.pr, first.useSigterm);
\r
8043 first.pr = NoProc;
\r
8045 if (second.reuse) {
\r
8046 /* Put second chess program into idle state */
\r
8047 if (second.pr != NoProc &&
\r
8048 gameMode == TwoMachinesPlay) {
\r
8049 SendToProgram("force\n", &second);
\r
8050 if (second.usePing) {
\r
8051 char buf[MSG_SIZ];
\r
8052 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
8053 SendToProgram(buf, &second);
\r
8056 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
8057 /* Kill off second chess program */
\r
8058 if (second.isr != NULL)
\r
8059 RemoveInputSource(second.isr);
\r
8060 second.isr = NULL;
\r
8062 if (second.pr != NoProc) {
\r
8063 DoSleep( appData.delayBeforeQuit );
\r
8064 SendToProgram("quit\n", &second);
\r
8065 DoSleep( appData.delayAfterQuit );
\r
8066 DestroyChildProcess(second.pr, second.useSigterm);
\r
8068 second.pr = NoProc;
\r
8071 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8074 if (first.twoMachinesColor[0] == 'w') {
\r
8075 first.matchWins++;
\r
8077 second.matchWins++;
\r
8081 if (first.twoMachinesColor[0] == 'b') {
\r
8082 first.matchWins++;
\r
8084 second.matchWins++;
\r
8090 if (matchGame < appData.matchGames) {
\r
8092 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8093 tmp = first.twoMachinesColor;
\r
8094 first.twoMachinesColor = second.twoMachinesColor;
\r
8095 second.twoMachinesColor = tmp;
\r
8097 gameMode = nextGameMode;
\r
8099 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8100 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8101 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8102 endingGame = 0; /* [HGM] crash */
\r
8105 char buf[MSG_SIZ];
\r
8106 gameMode = nextGameMode;
\r
8107 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8108 first.tidy, second.tidy,
\r
8109 first.matchWins, second.matchWins,
\r
8110 appData.matchGames - (first.matchWins + second.matchWins));
\r
8111 DisplayFatalError(buf, 0, 0);
\r
8114 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8115 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8116 ExitAnalyzeMode();
\r
8117 gameMode = nextGameMode;
\r
8119 endingGame = 0; /* [HGM] crash */
\r
8122 /* Assumes program was just initialized (initString sent).
\r
8123 Leaves program in force mode. */
\r
8125 FeedMovesToProgram(cps, upto)
\r
8126 ChessProgramState *cps;
\r
8131 if (appData.debugMode)
\r
8132 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8133 startedFromSetupPosition ? "position and " : "",
\r
8134 backwardMostMove, upto, cps->which);
\r
8135 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8136 // [HGM] variantswitch: make engine aware of new variant
\r
8137 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8138 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8139 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8140 SendToProgram(buf, cps);
\r
8141 currentlyInitializedVariant = gameInfo.variant;
\r
8143 SendToProgram("force\n", cps);
\r
8144 if (startedFromSetupPosition) {
\r
8145 SendBoard(cps, backwardMostMove);
\r
8146 if (appData.debugMode) {
\r
8147 fprintf(debugFP, "feedMoves\n");
\r
8150 for (i = backwardMostMove; i < upto; i++) {
\r
8151 SendMoveToProgram(i, cps);
\r
8157 ResurrectChessProgram()
\r
8159 /* The chess program may have exited.
\r
8160 If so, restart it and feed it all the moves made so far. */
\r
8162 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8164 StartChessProgram(&first);
\r
8165 InitChessProgram(&first, FALSE);
\r
8166 FeedMovesToProgram(&first, currentMove);
\r
8168 if (!first.sendTime) {
\r
8169 /* can't tell gnuchess what its clock should read,
\r
8170 so we bow to its notion. */
\r
8172 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8173 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8176 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8177 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8178 SendToProgram("analyze\n", &first);
\r
8179 first.analyzing = TRUE;
\r
8184 * Button procedures
\r
8187 Reset(redraw, init)
\r
8192 if (appData.debugMode) {
\r
8193 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8194 redraw, init, gameMode);
\r
8196 pausing = pauseExamInvalid = FALSE;
\r
8197 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8199 whiteFlag = blackFlag = FALSE;
\r
8200 userOfferedDraw = FALSE;
\r
8201 hintRequested = bookRequested = FALSE;
\r
8202 first.maybeThinking = FALSE;
\r
8203 second.maybeThinking = FALSE;
\r
8204 first.bookSuspend = FALSE; // [HGM] book
\r
8205 second.bookSuspend = FALSE;
\r
8206 thinkOutput[0] = NULLCHAR;
\r
8207 lastHint[0] = NULLCHAR;
\r
8208 ClearGameInfo(&gameInfo);
\r
8209 gameInfo.variant = StringToVariant(appData.variant);
\r
8210 ics_user_moved = ics_clock_paused = FALSE;
\r
8211 ics_getting_history = H_FALSE;
\r
8213 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8214 ClearProgramStats();
\r
8215 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8218 ClearHighlights();
\r
8219 flipView = appData.flipView;
\r
8220 ClearPremoveHighlights();
\r
8221 gotPremove = FALSE;
\r
8222 alarmSounded = FALSE;
\r
8224 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8225 if(appData.serverMovesName != NULL) {
\r
8226 /* [HGM] prepare to make moves file for broadcasting */
\r
8227 clock_t t = clock();
\r
8228 if(serverMoves != NULL) fclose(serverMoves);
\r
8229 serverMoves = fopen(appData.serverMovesName, "r");
\r
8230 if(serverMoves != NULL) {
\r
8231 fclose(serverMoves);
\r
8232 /* delay 15 sec before overwriting, so all clients can see end */
\r
8233 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8235 serverMoves = fopen(appData.serverMovesName, "w");
\r
8238 ExitAnalyzeMode();
\r
8239 gameMode = BeginningOfGame;
\r
8241 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8242 InitPosition(redraw);
\r
8243 for (i = 0; i < MAX_MOVES; i++) {
\r
8244 if (commentList[i] != NULL) {
\r
8245 free(commentList[i]);
\r
8246 commentList[i] = NULL;
\r
8250 timeRemaining[0][0] = whiteTimeRemaining;
\r
8251 timeRemaining[1][0] = blackTimeRemaining;
\r
8252 if (first.pr == NULL) {
\r
8253 StartChessProgram(&first);
\r
8256 InitChessProgram(&first, startedFromSetupPosition);
\r
8259 DisplayMessage("", "");
\r
8260 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8264 AutoPlayGameLoop()
\r
8267 if (!AutoPlayOneMove())
\r
8269 if (matchMode || appData.timeDelay == 0)
\r
8271 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8273 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8282 int fromX, fromY, toX, toY;
\r
8284 if (appData.debugMode) {
\r
8285 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8288 if (gameMode != PlayFromGameFile)
\r
8291 if (currentMove >= forwardMostMove) {
\r
8292 gameMode = EditGame;
\r
8295 /* [AS] Clear current move marker at the end of a game */
\r
8296 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8301 toX = moveList[currentMove][2] - AAA;
\r
8302 toY = moveList[currentMove][3] - ONE;
\r
8304 if (moveList[currentMove][1] == '@') {
\r
8305 if (appData.highlightLastMove) {
\r
8306 SetHighlights(-1, -1, toX, toY);
\r
8309 fromX = moveList[currentMove][0] - AAA;
\r
8310 fromY = moveList[currentMove][1] - ONE;
\r
8312 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8314 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8316 if (appData.highlightLastMove) {
\r
8317 SetHighlights(fromX, fromY, toX, toY);
\r
8320 DisplayMove(currentMove);
\r
8321 SendMoveToProgram(currentMove++, &first);
\r
8322 DisplayBothClocks();
\r
8323 DrawPosition(FALSE, boards[currentMove]);
\r
8324 // [HGM] PV info: always display, routine tests if empty
\r
8325 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8331 LoadGameOneMove(readAhead)
\r
8332 ChessMove readAhead;
\r
8334 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8335 char promoChar = NULLCHAR;
\r
8336 ChessMove moveType;
\r
8337 char move[MSG_SIZ];
\r
8340 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8341 gameMode != AnalyzeMode && gameMode != Training) {
\r
8342 gameFileFP = NULL;
\r
8346 yyboardindex = forwardMostMove;
\r
8347 if (readAhead != (ChessMove)0) {
\r
8348 moveType = readAhead;
\r
8350 if (gameFileFP == NULL)
\r
8352 moveType = (ChessMove) yylex();
\r
8356 switch (moveType) {
\r
8358 if (appData.debugMode)
\r
8359 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8361 if (*p == '{' || *p == '[' || *p == '(') {
\r
8362 p[strlen(p) - 1] = NULLCHAR;
\r
8366 /* append the comment but don't display it */
\r
8367 while (*p == '\n') p++;
\r
8368 AppendComment(currentMove, p);
\r
8371 case WhiteCapturesEnPassant:
\r
8372 case BlackCapturesEnPassant:
\r
8373 case WhitePromotionChancellor:
\r
8374 case BlackPromotionChancellor:
\r
8375 case WhitePromotionArchbishop:
\r
8376 case BlackPromotionArchbishop:
\r
8377 case WhitePromotionCentaur:
\r
8378 case BlackPromotionCentaur:
\r
8379 case WhitePromotionQueen:
\r
8380 case BlackPromotionQueen:
\r
8381 case WhitePromotionRook:
\r
8382 case BlackPromotionRook:
\r
8383 case WhitePromotionBishop:
\r
8384 case BlackPromotionBishop:
\r
8385 case WhitePromotionKnight:
\r
8386 case BlackPromotionKnight:
\r
8387 case WhitePromotionKing:
\r
8388 case BlackPromotionKing:
\r
8390 case WhiteKingSideCastle:
\r
8391 case WhiteQueenSideCastle:
\r
8392 case BlackKingSideCastle:
\r
8393 case BlackQueenSideCastle:
\r
8394 case WhiteKingSideCastleWild:
\r
8395 case WhiteQueenSideCastleWild:
\r
8396 case BlackKingSideCastleWild:
\r
8397 case BlackQueenSideCastleWild:
\r
8399 case WhiteHSideCastleFR:
\r
8400 case WhiteASideCastleFR:
\r
8401 case BlackHSideCastleFR:
\r
8402 case BlackASideCastleFR:
\r
8404 if (appData.debugMode)
\r
8405 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8406 fromX = currentMoveString[0] - AAA;
\r
8407 fromY = currentMoveString[1] - ONE;
\r
8408 toX = currentMoveString[2] - AAA;
\r
8409 toY = currentMoveString[3] - ONE;
\r
8410 promoChar = currentMoveString[4];
\r
8415 if (appData.debugMode)
\r
8416 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8417 fromX = moveType == WhiteDrop ?
\r
8418 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8419 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8420 fromY = DROP_RANK;
\r
8421 toX = currentMoveString[2] - AAA;
\r
8422 toY = currentMoveString[3] - ONE;
\r
8428 case GameUnfinished:
\r
8429 if (appData.debugMode)
\r
8430 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8431 p = strchr(yy_text, '{');
\r
8432 if (p == NULL) p = strchr(yy_text, '(');
\r
8435 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8437 q = strchr(p, *p == '{' ? '}' : ')');
\r
8438 if (q != NULL) *q = NULLCHAR;
\r
8441 GameEnds(moveType, p, GE_FILE);
\r
8443 if (cmailMsgLoaded) {
\r
8444 ClearHighlights();
\r
8445 flipView = WhiteOnMove(currentMove);
\r
8446 if (moveType == GameUnfinished) flipView = !flipView;
\r
8447 if (appData.debugMode)
\r
8448 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8452 case (ChessMove) 0: /* end of file */
\r
8453 if (appData.debugMode)
\r
8454 fprintf(debugFP, "Parser hit end of file\n");
\r
8455 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8456 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8460 case MT_CHECKMATE:
\r
8461 if (WhiteOnMove(currentMove)) {
\r
8462 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8464 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8467 case MT_STALEMATE:
\r
8468 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8474 case MoveNumberOne:
\r
8475 if (lastLoadGameStart == GNUChessGame) {
\r
8476 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8477 if (appData.debugMode)
\r
8478 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8479 yy_text, (int) moveType);
\r
8480 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8482 /* else fall thru */
\r
8485 case GNUChessGame:
\r
8487 /* Reached start of next game in file */
\r
8488 if (appData.debugMode)
\r
8489 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8490 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8491 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8495 case MT_CHECKMATE:
\r
8496 if (WhiteOnMove(currentMove)) {
\r
8497 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8499 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8502 case MT_STALEMATE:
\r
8503 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8509 case PositionDiagram: /* should not happen; ignore */
\r
8510 case ElapsedTime: /* ignore */
\r
8511 case NAG: /* ignore */
\r
8512 if (appData.debugMode)
\r
8513 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8514 yy_text, (int) moveType);
\r
8515 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8518 if (appData.testLegality) {
\r
8519 if (appData.debugMode)
\r
8520 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8521 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8522 (forwardMostMove / 2) + 1,
\r
8523 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8524 DisplayError(move, 0);
\r
8527 if (appData.debugMode)
\r
8528 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8529 yy_text, currentMoveString);
\r
8530 fromX = currentMoveString[0] - AAA;
\r
8531 fromY = currentMoveString[1] - ONE;
\r
8532 toX = currentMoveString[2] - AAA;
\r
8533 toY = currentMoveString[3] - ONE;
\r
8534 promoChar = currentMoveString[4];
\r
8538 case AmbiguousMove:
\r
8539 if (appData.debugMode)
\r
8540 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8541 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8542 (forwardMostMove / 2) + 1,
\r
8543 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8544 DisplayError(move, 0);
\r
8549 case ImpossibleMove:
\r
8550 if (appData.debugMode)
\r
8551 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8552 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8553 (forwardMostMove / 2) + 1,
\r
8554 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8555 DisplayError(move, 0);
\r
8561 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8562 DrawPosition(FALSE, boards[currentMove]);
\r
8563 DisplayBothClocks();
\r
8564 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8565 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8567 (void) StopLoadGameTimer();
\r
8568 gameFileFP = NULL;
\r
8569 cmailOldMove = forwardMostMove;
\r
8572 /* currentMoveString is set as a side-effect of yylex */
\r
8573 strcat(currentMoveString, "\n");
\r
8574 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8576 thinkOutput[0] = NULLCHAR;
\r
8577 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8578 currentMove = forwardMostMove;
\r
8583 /* Load the nth game from the given file */
\r
8585 LoadGameFromFile(filename, n, title, useList)
\r
8589 /*Boolean*/ int useList;
\r
8592 char buf[MSG_SIZ];
\r
8594 if (strcmp(filename, "-") == 0) {
\r
8598 f = fopen(filename, "rb");
\r
8600 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8601 DisplayError(buf, errno);
\r
8605 if (fseek(f, 0, 0) == -1) {
\r
8606 /* f is not seekable; probably a pipe */
\r
8609 if (useList && n == 0) {
\r
8610 int error = GameListBuild(f);
\r
8612 DisplayError(_("Cannot build game list"), error);
\r
8613 } else if (!ListEmpty(&gameList) &&
\r
8614 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8615 GameListPopUp(f, title);
\r
8618 GameListDestroy();
\r
8621 if (n == 0) n = 1;
\r
8622 return LoadGame(f, n, title, FALSE);
\r
8627 MakeRegisteredMove()
\r
8629 int fromX, fromY, toX, toY;
\r
8631 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8632 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8635 if (appData.debugMode)
\r
8636 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8637 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8639 thinkOutput[0] = NULLCHAR;
\r
8640 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8641 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8642 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8643 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8644 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8645 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8646 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8647 ShowMove(fromX, fromY, toX, toY);
\r
8649 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8650 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8655 case MT_CHECKMATE:
\r
8656 if (WhiteOnMove(currentMove)) {
\r
8657 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8659 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8663 case MT_STALEMATE:
\r
8664 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8670 case CMAIL_RESIGN:
\r
8671 if (WhiteOnMove(currentMove)) {
\r
8672 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8674 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8678 case CMAIL_ACCEPT:
\r
8679 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8690 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8692 CmailLoadGame(f, gameNumber, title, useList)
\r
8700 if (gameNumber > nCmailGames) {
\r
8701 DisplayError(_("No more games in this message"), 0);
\r
8704 if (f == lastLoadGameFP) {
\r
8705 int offset = gameNumber - lastLoadGameNumber;
\r
8706 if (offset == 0) {
\r
8707 cmailMsg[0] = NULLCHAR;
\r
8708 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8709 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8710 nCmailMovesRegistered--;
\r
8712 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8713 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8714 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8717 if (! RegisterMove()) return FALSE;
\r
8721 retVal = LoadGame(f, gameNumber, title, useList);
\r
8723 /* Make move registered during previous look at this game, if any */
\r
8724 MakeRegisteredMove();
\r
8726 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8727 commentList[currentMove]
\r
8728 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8729 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8735 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8737 ReloadGame(offset)
\r
8740 int gameNumber = lastLoadGameNumber + offset;
\r
8741 if (lastLoadGameFP == NULL) {
\r
8742 DisplayError(_("No game has been loaded yet"), 0);
\r
8745 if (gameNumber <= 0) {
\r
8746 DisplayError(_("Can't back up any further"), 0);
\r
8749 if (cmailMsgLoaded) {
\r
8750 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8751 lastLoadGameTitle, lastLoadGameUseList);
\r
8753 return LoadGame(lastLoadGameFP, gameNumber,
\r
8754 lastLoadGameTitle, lastLoadGameUseList);
\r
8760 /* Load the nth game from open file f */
\r
8762 LoadGame(f, gameNumber, title, useList)
\r
8769 char buf[MSG_SIZ];
\r
8770 int gn = gameNumber;
\r
8771 ListGame *lg = NULL;
\r
8772 int numPGNTags = 0;
\r
8774 GameMode oldGameMode;
\r
8775 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8777 if (appData.debugMode)
\r
8778 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8780 if (gameMode == Training )
\r
8781 SetTrainingModeOff();
\r
8783 oldGameMode = gameMode;
\r
8784 if (gameMode != BeginningOfGame) {
\r
8785 Reset(FALSE, TRUE);
\r
8789 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8790 fclose(lastLoadGameFP);
\r
8794 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8797 fseek(f, lg->offset, 0);
\r
8798 GameListHighlight(gameNumber);
\r
8802 DisplayError(_("Game number out of range"), 0);
\r
8806 GameListDestroy();
\r
8807 if (fseek(f, 0, 0) == -1) {
\r
8808 if (f == lastLoadGameFP ?
\r
8809 gameNumber == lastLoadGameNumber + 1 :
\r
8810 gameNumber == 1) {
\r
8813 DisplayError(_("Can't seek on game file"), 0);
\r
8818 lastLoadGameFP = f;
\r
8819 lastLoadGameNumber = gameNumber;
\r
8820 strcpy(lastLoadGameTitle, title);
\r
8821 lastLoadGameUseList = useList;
\r
8825 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8826 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8827 lg->gameInfo.black);
\r
8828 DisplayTitle(buf);
\r
8829 } else if (*title != NULLCHAR) {
\r
8830 if (gameNumber > 1) {
\r
8831 sprintf(buf, "%s %d", title, gameNumber);
\r
8832 DisplayTitle(buf);
\r
8834 DisplayTitle(title);
\r
8838 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8839 gameMode = PlayFromGameFile;
\r
8843 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8844 CopyBoard(boards[0], initialPosition);
\r
8848 * Skip the first gn-1 games in the file.
\r
8849 * Also skip over anything that precedes an identifiable
\r
8850 * start of game marker, to avoid being confused by
\r
8851 * garbage at the start of the file. Currently
\r
8852 * recognized start of game markers are the move number "1",
\r
8853 * the pattern "gnuchess .* game", the pattern
\r
8854 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8855 * A game that starts with one of the latter two patterns
\r
8856 * will also have a move number 1, possibly
\r
8857 * following a position diagram.
\r
8858 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8859 * start with an unnumbered move. Does that break anything?
\r
8861 cm = lastLoadGameStart = (ChessMove) 0;
\r
8863 yyboardindex = forwardMostMove;
\r
8864 cm = (ChessMove) yylex();
\r
8866 case (ChessMove) 0:
\r
8867 if (cmailMsgLoaded) {
\r
8868 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8870 Reset(TRUE, TRUE);
\r
8871 DisplayError(_("Game not found in file"), 0);
\r
8875 case GNUChessGame:
\r
8878 lastLoadGameStart = cm;
\r
8881 case MoveNumberOne:
\r
8882 switch (lastLoadGameStart) {
\r
8883 case GNUChessGame:
\r
8887 case MoveNumberOne:
\r
8888 case (ChessMove) 0:
\r
8889 gn--; /* count this game */
\r
8890 lastLoadGameStart = cm;
\r
8899 switch (lastLoadGameStart) {
\r
8900 case GNUChessGame:
\r
8902 case MoveNumberOne:
\r
8903 case (ChessMove) 0:
\r
8904 gn--; /* count this game */
\r
8905 lastLoadGameStart = cm;
\r
8908 lastLoadGameStart = cm; /* game counted already */
\r
8916 yyboardindex = forwardMostMove;
\r
8917 cm = (ChessMove) yylex();
\r
8918 } while (cm == PGNTag || cm == Comment);
\r
8925 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8926 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8927 != CMAIL_OLD_RESULT) {
\r
8928 nCmailResults ++ ;
\r
8929 cmailResult[ CMAIL_MAX_GAMES
\r
8930 - gn - 1] = CMAIL_OLD_RESULT;
\r
8936 /* Only a NormalMove can be at the start of a game
\r
8937 * without a position diagram. */
\r
8938 if (lastLoadGameStart == (ChessMove) 0) {
\r
8940 lastLoadGameStart = MoveNumberOne;
\r
8949 if (appData.debugMode)
\r
8950 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8952 if (cm == XBoardGame) {
\r
8953 /* Skip any header junk before position diagram and/or move 1 */
\r
8955 yyboardindex = forwardMostMove;
\r
8956 cm = (ChessMove) yylex();
\r
8958 if (cm == (ChessMove) 0 ||
\r
8959 cm == GNUChessGame || cm == XBoardGame) {
\r
8960 /* Empty game; pretend end-of-file and handle later */
\r
8961 cm = (ChessMove) 0;
\r
8965 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8966 cm == PGNTag || cm == Comment)
\r
8969 } else if (cm == GNUChessGame) {
\r
8970 if (gameInfo.event != NULL) {
\r
8971 free(gameInfo.event);
\r
8973 gameInfo.event = StrSave(yy_text);
\r
8976 startedFromSetupPosition = FALSE;
\r
8977 while (cm == PGNTag) {
\r
8978 if (appData.debugMode)
\r
8979 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8980 err = ParsePGNTag(yy_text, &gameInfo);
\r
8981 if (!err) numPGNTags++;
\r
8983 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8984 if(gameInfo.variant != oldVariant) {
\r
8985 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8986 InitPosition(TRUE);
\r
8987 oldVariant = gameInfo.variant;
\r
8988 if (appData.debugMode)
\r
8989 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8993 if (gameInfo.fen != NULL) {
\r
8994 Board initial_position;
\r
8995 startedFromSetupPosition = TRUE;
\r
8996 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8997 Reset(TRUE, TRUE);
\r
8998 DisplayError(_("Bad FEN position in file"), 0);
\r
9001 CopyBoard(boards[0], initial_position);
\r
9002 if (blackPlaysFirst) {
\r
9003 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9004 CopyBoard(boards[1], initial_position);
\r
9005 strcpy(moveList[0], "");
\r
9006 strcpy(parseList[0], "");
\r
9007 timeRemaining[0][1] = whiteTimeRemaining;
\r
9008 timeRemaining[1][1] = blackTimeRemaining;
\r
9009 if (commentList[0] != NULL) {
\r
9010 commentList[1] = commentList[0];
\r
9011 commentList[0] = NULL;
\r
9014 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9016 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
9018 initialRulePlies = FENrulePlies;
\r
9019 epStatus[forwardMostMove] = FENepStatus;
\r
9020 for( i=0; i< nrCastlingRights; i++ )
\r
9021 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9023 yyboardindex = forwardMostMove;
\r
9024 free(gameInfo.fen);
\r
9025 gameInfo.fen = NULL;
\r
9028 yyboardindex = forwardMostMove;
\r
9029 cm = (ChessMove) yylex();
\r
9031 /* Handle comments interspersed among the tags */
\r
9032 while (cm == Comment) {
\r
9034 if (appData.debugMode)
\r
9035 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9037 if (*p == '{' || *p == '[' || *p == '(') {
\r
9038 p[strlen(p) - 1] = NULLCHAR;
\r
9041 while (*p == '\n') p++;
\r
9042 AppendComment(currentMove, p);
\r
9043 yyboardindex = forwardMostMove;
\r
9044 cm = (ChessMove) yylex();
\r
9048 /* don't rely on existence of Event tag since if game was
\r
9049 * pasted from clipboard the Event tag may not exist
\r
9051 if (numPGNTags > 0){
\r
9053 if (gameInfo.variant == VariantNormal) {
\r
9054 gameInfo.variant = StringToVariant(gameInfo.event);
\r
9057 if( appData.autoDisplayTags ) {
\r
9058 tags = PGNTags(&gameInfo);
\r
9059 TagsPopUp(tags, CmailMsg());
\r
9064 /* Make something up, but don't display it now */
\r
9069 if (cm == PositionDiagram) {
\r
9072 Board initial_position;
\r
9074 if (appData.debugMode)
\r
9075 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9077 if (!startedFromSetupPosition) {
\r
9079 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9080 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9090 initial_position[i][j++] = CharToPiece(*p);
\r
9093 while (*p == ' ' || *p == '\t' ||
\r
9094 *p == '\n' || *p == '\r') p++;
\r
9096 if (strncmp(p, "black", strlen("black"))==0)
\r
9097 blackPlaysFirst = TRUE;
\r
9099 blackPlaysFirst = FALSE;
\r
9100 startedFromSetupPosition = TRUE;
\r
9102 CopyBoard(boards[0], initial_position);
\r
9103 if (blackPlaysFirst) {
\r
9104 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9105 CopyBoard(boards[1], initial_position);
\r
9106 strcpy(moveList[0], "");
\r
9107 strcpy(parseList[0], "");
\r
9108 timeRemaining[0][1] = whiteTimeRemaining;
\r
9109 timeRemaining[1][1] = blackTimeRemaining;
\r
9110 if (commentList[0] != NULL) {
\r
9111 commentList[1] = commentList[0];
\r
9112 commentList[0] = NULL;
\r
9115 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9118 yyboardindex = forwardMostMove;
\r
9119 cm = (ChessMove) yylex();
\r
9122 if (first.pr == NoProc) {
\r
9123 StartChessProgram(&first);
\r
9125 InitChessProgram(&first, FALSE);
\r
9126 SendToProgram("force\n", &first);
\r
9127 if (startedFromSetupPosition) {
\r
9128 SendBoard(&first, forwardMostMove);
\r
9129 if (appData.debugMode) {
\r
9130 fprintf(debugFP, "Load Game\n");
\r
9132 DisplayBothClocks();
\r
9135 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9136 loadFlag = appData.suppressLoadMoves;
\r
9138 while (cm == Comment) {
\r
9140 if (appData.debugMode)
\r
9141 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9143 if (*p == '{' || *p == '[' || *p == '(') {
\r
9144 p[strlen(p) - 1] = NULLCHAR;
\r
9147 while (*p == '\n') p++;
\r
9148 AppendComment(currentMove, p);
\r
9149 yyboardindex = forwardMostMove;
\r
9150 cm = (ChessMove) yylex();
\r
9153 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9154 cm == WhiteWins || cm == BlackWins ||
\r
9155 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9156 DisplayMessage("", _("No moves in game"));
\r
9157 if (cmailMsgLoaded) {
\r
9158 if (appData.debugMode)
\r
9159 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9160 ClearHighlights();
\r
9163 DrawPosition(FALSE, boards[currentMove]);
\r
9164 DisplayBothClocks();
\r
9165 gameMode = EditGame;
\r
9167 gameFileFP = NULL;
\r
9172 // [HGM] PV info: routine tests if comment empty
\r
9173 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9174 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9176 if (!matchMode && appData.timeDelay != 0)
\r
9177 DrawPosition(FALSE, boards[currentMove]);
\r
9179 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9180 programStats.ok_to_send = 1;
\r
9183 /* if the first token after the PGN tags is a move
\r
9184 * and not move number 1, retrieve it from the parser
\r
9186 if (cm != MoveNumberOne)
\r
9187 LoadGameOneMove(cm);
\r
9189 /* load the remaining moves from the file */
\r
9190 while (LoadGameOneMove((ChessMove)0)) {
\r
9191 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9192 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9195 /* rewind to the start of the game */
\r
9196 currentMove = backwardMostMove;
\r
9198 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9200 if (oldGameMode == AnalyzeFile ||
\r
9201 oldGameMode == AnalyzeMode) {
\r
9202 AnalyzeFileEvent();
\r
9205 if (matchMode || appData.timeDelay == 0) {
\r
9207 gameMode = EditGame;
\r
9209 } else if (appData.timeDelay > 0) {
\r
9210 AutoPlayGameLoop();
\r
9213 if (appData.debugMode)
\r
9214 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9216 loadFlag = 0; /* [HGM] true game starts */
\r
9220 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9222 ReloadPosition(offset)
\r
9225 int positionNumber = lastLoadPositionNumber + offset;
\r
9226 if (lastLoadPositionFP == NULL) {
\r
9227 DisplayError(_("No position has been loaded yet"), 0);
\r
9230 if (positionNumber <= 0) {
\r
9231 DisplayError(_("Can't back up any further"), 0);
\r
9234 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9235 lastLoadPositionTitle);
\r
9238 /* Load the nth position from the given file */
\r
9240 LoadPositionFromFile(filename, n, title)
\r
9246 char buf[MSG_SIZ];
\r
9248 if (strcmp(filename, "-") == 0) {
\r
9249 return LoadPosition(stdin, n, "stdin");
\r
9251 f = fopen(filename, "rb");
\r
9253 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9254 DisplayError(buf, errno);
\r
9257 return LoadPosition(f, n, title);
\r
9262 /* Load the nth position from the given open file, and close it */
\r
9264 LoadPosition(f, positionNumber, title)
\r
9266 int positionNumber;
\r
9269 char *p, line[MSG_SIZ];
\r
9270 Board initial_position;
\r
9271 int i, j, fenMode, pn;
\r
9273 if (gameMode == Training )
\r
9274 SetTrainingModeOff();
\r
9276 if (gameMode != BeginningOfGame) {
\r
9277 Reset(FALSE, TRUE);
\r
9279 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9280 fclose(lastLoadPositionFP);
\r
9282 if (positionNumber == 0) positionNumber = 1;
\r
9283 lastLoadPositionFP = f;
\r
9284 lastLoadPositionNumber = positionNumber;
\r
9285 strcpy(lastLoadPositionTitle, title);
\r
9286 if (first.pr == NoProc) {
\r
9287 StartChessProgram(&first);
\r
9288 InitChessProgram(&first, FALSE);
\r
9290 pn = positionNumber;
\r
9291 if (positionNumber < 0) {
\r
9292 /* Negative position number means to seek to that byte offset */
\r
9293 if (fseek(f, -positionNumber, 0) == -1) {
\r
9294 DisplayError(_("Can't seek on position file"), 0);
\r
9299 if (fseek(f, 0, 0) == -1) {
\r
9300 if (f == lastLoadPositionFP ?
\r
9301 positionNumber == lastLoadPositionNumber + 1 :
\r
9302 positionNumber == 1) {
\r
9305 DisplayError(_("Can't seek on position file"), 0);
\r
9310 /* See if this file is FEN or old-style xboard */
\r
9311 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9312 DisplayError(_("Position not found in file"), 0);
\r
9316 switch (line[0]) {
\r
9317 case '#': case 'x':
\r
9321 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9322 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9323 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9324 case '7': case '8': case '9':
\r
9325 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9326 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9327 case 'C': case 'W': case 'c': case 'w':
\r
9332 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9333 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9337 if (fenMode || line[0] == '#') pn--;
\r
9339 /* skip positions before number pn */
\r
9340 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9341 Reset(TRUE, TRUE);
\r
9342 DisplayError(_("Position not found in file"), 0);
\r
9345 if (fenMode || line[0] == '#') pn--;
\r
9350 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9351 DisplayError(_("Bad FEN position in file"), 0);
\r
9355 (void) fgets(line, MSG_SIZ, f);
\r
9356 (void) fgets(line, MSG_SIZ, f);
\r
9358 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9359 (void) fgets(line, MSG_SIZ, f);
\r
9360 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9363 initial_position[i][j++] = CharToPiece(*p);
\r
9367 blackPlaysFirst = FALSE;
\r
9369 (void) fgets(line, MSG_SIZ, f);
\r
9370 if (strncmp(line, "black", strlen("black"))==0)
\r
9371 blackPlaysFirst = TRUE;
\r
9374 startedFromSetupPosition = TRUE;
\r
9376 SendToProgram("force\n", &first);
\r
9377 CopyBoard(boards[0], initial_position);
\r
9378 if (blackPlaysFirst) {
\r
9379 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9380 strcpy(moveList[0], "");
\r
9381 strcpy(parseList[0], "");
\r
9382 CopyBoard(boards[1], initial_position);
\r
9383 DisplayMessage("", _("Black to play"));
\r
9385 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9386 DisplayMessage("", _("White to play"));
\r
9388 /* [HGM] copy FEN attributes as well */
\r
9390 initialRulePlies = FENrulePlies;
\r
9391 epStatus[forwardMostMove] = FENepStatus;
\r
9392 for( i=0; i< nrCastlingRights; i++ )
\r
9393 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9395 SendBoard(&first, forwardMostMove);
\r
9396 if (appData.debugMode) {
\r
9398 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9399 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9400 fprintf(debugFP, "Load Position\n");
\r
9403 if (positionNumber > 1) {
\r
9404 sprintf(line, "%s %d", title, positionNumber);
\r
9405 DisplayTitle(line);
\r
9407 DisplayTitle(title);
\r
9409 gameMode = EditGame;
\r
9412 timeRemaining[0][1] = whiteTimeRemaining;
\r
9413 timeRemaining[1][1] = blackTimeRemaining;
\r
9414 DrawPosition(FALSE, boards[currentMove]);
\r
9421 CopyPlayerNameIntoFileName(dest, src)
\r
9422 char **dest, *src;
\r
9424 while (*src != NULLCHAR && *src != ',') {
\r
9425 if (*src == ' ') {
\r
9429 *(*dest)++ = *src++;
\r
9434 char *DefaultFileName(ext)
\r
9437 static char def[MSG_SIZ];
\r
9440 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9442 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9444 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9448 def[0] = NULLCHAR;
\r
9453 /* Save the current game to the given file */
\r
9455 SaveGameToFile(filename, append)
\r
9460 char buf[MSG_SIZ];
\r
9462 if (strcmp(filename, "-") == 0) {
\r
9463 return SaveGame(stdout, 0, NULL);
\r
9465 f = fopen(filename, append ? "a" : "w");
\r
9467 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9468 DisplayError(buf, errno);
\r
9471 return SaveGame(f, 0, NULL);
\r
9480 static char buf[MSG_SIZ];
\r
9483 p = strchr(str, ' ');
\r
9484 if (p == NULL) return str;
\r
9485 strncpy(buf, str, p - str);
\r
9486 buf[p - str] = NULLCHAR;
\r
9490 #define PGN_MAX_LINE 75
\r
9492 #define PGN_SIDE_WHITE 0
\r
9493 #define PGN_SIDE_BLACK 1
\r
9496 static int FindFirstMoveOutOfBook( int side )
\r
9500 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9501 int index = backwardMostMove;
\r
9502 int has_book_hit = 0;
\r
9504 if( (index % 2) != side ) {
\r
9508 while( index < forwardMostMove ) {
\r
9509 /* Check to see if engine is in book */
\r
9510 int depth = pvInfoList[index].depth;
\r
9511 int score = pvInfoList[index].score;
\r
9514 if( depth <= 2 ) {
\r
9517 else if( score == 0 && depth == 63 ) {
\r
9518 in_book = 1; /* Zappa */
\r
9520 else if( score == 2 && depth == 99 ) {
\r
9521 in_book = 1; /* Abrok */
\r
9524 has_book_hit += in_book;
\r
9540 void GetOutOfBookInfo( char * buf )
\r
9544 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9546 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9547 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9551 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9552 for( i=0; i<2; i++ ) {
\r
9556 if( i > 0 && oob[0] >= 0 ) {
\r
9557 strcat( buf, " " );
\r
9560 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9561 sprintf( buf+strlen(buf), "%s%.2f",
\r
9562 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9563 pvInfoList[idx].score / 100.0 );
\r
9569 /* Save game in PGN style and close the file */
\r
9574 int i, offset, linelen, newblock;
\r
9576 // char *movetext;
\r
9578 int movelen, numlen, blank;
\r
9579 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9581 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9583 tm = time((time_t *) NULL);
\r
9585 PrintPGNTags(f, &gameInfo);
\r
9587 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9588 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9589 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9590 fprintf(f, "\n{--------------\n");
\r
9591 PrintPosition(f, backwardMostMove);
\r
9592 fprintf(f, "--------------}\n");
\r
9596 /* [AS] Out of book annotation */
\r
9597 if( appData.saveOutOfBookInfo ) {
\r
9600 GetOutOfBookInfo( buf );
\r
9602 if( buf[0] != '\0' ) {
\r
9603 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9610 i = backwardMostMove;
\r
9614 while (i < forwardMostMove) {
\r
9615 /* Print comments preceding this move */
\r
9616 if (commentList[i] != NULL) {
\r
9617 if (linelen > 0) fprintf(f, "\n");
\r
9618 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9623 /* Format move number */
\r
9624 if ((i % 2) == 0) {
\r
9625 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9628 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9630 numtext[0] = NULLCHAR;
\r
9633 numlen = strlen(numtext);
\r
9636 /* Print move number */
\r
9637 blank = linelen > 0 && numlen > 0;
\r
9638 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9647 fprintf(f, numtext);
\r
9648 linelen += numlen;
\r
9651 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9654 blank = linelen > 0 && movelen > 0;
\r
9655 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9664 fprintf(f, parseList[i]);
\r
9665 linelen += movelen;
\r
9667 /* [AS] Add PV info if present */
\r
9668 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9669 /* [HGM] add time */
\r
9670 char buf[MSG_SIZ]; int seconds = 0;
\r
9673 if(i >= backwardMostMove) {
\r
9674 if(WhiteOnMove(i))
\r
9675 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9676 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9678 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9679 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9681 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9683 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9686 if( seconds <= 0) buf[0] = 0; else
\r
9687 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9688 seconds = (seconds + 4)/10; // round to full seconds
\r
9689 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9690 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9693 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9694 pvInfoList[i].score >= 0 ? "+" : "",
\r
9695 pvInfoList[i].score / 100.0,
\r
9696 pvInfoList[i].depth,
\r
9699 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9701 /* Print score/depth */
\r
9702 blank = linelen > 0 && movelen > 0;
\r
9703 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9712 fprintf(f, move_buffer);
\r
9713 linelen += movelen;
\r
9719 /* Start a new line */
\r
9720 if (linelen > 0) fprintf(f, "\n");
\r
9722 /* Print comments after last move */
\r
9723 if (commentList[i] != NULL) {
\r
9724 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9727 /* Print result */
\r
9728 if (gameInfo.resultDetails != NULL &&
\r
9729 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9730 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9731 PGNResult(gameInfo.result));
\r
9733 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9740 /* Save game in old style and close the file */
\r
9742 SaveGameOldStyle(f)
\r
9748 tm = time((time_t *) NULL);
\r
9750 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9751 PrintOpponents(f);
\r
9753 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9754 fprintf(f, "\n[--------------\n");
\r
9755 PrintPosition(f, backwardMostMove);
\r
9756 fprintf(f, "--------------]\n");
\r
9761 i = backwardMostMove;
\r
9762 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9764 while (i < forwardMostMove) {
\r
9765 if (commentList[i] != NULL) {
\r
9766 fprintf(f, "[%s]\n", commentList[i]);
\r
9769 if ((i % 2) == 1) {
\r
9770 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9773 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9775 if (commentList[i] != NULL) {
\r
9779 if (i >= forwardMostMove) {
\r
9783 fprintf(f, "%s\n", parseList[i]);
\r
9788 if (commentList[i] != NULL) {
\r
9789 fprintf(f, "[%s]\n", commentList[i]);
\r
9792 /* This isn't really the old style, but it's close enough */
\r
9793 if (gameInfo.resultDetails != NULL &&
\r
9794 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9795 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9796 gameInfo.resultDetails);
\r
9798 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9805 /* Save the current game to open file f and close the file */
\r
9807 SaveGame(f, dummy, dummy2)
\r
9812 if (gameMode == EditPosition) EditPositionDone();
\r
9813 if (appData.oldSaveStyle)
\r
9814 return SaveGameOldStyle(f);
\r
9816 return SaveGamePGN(f);
\r
9819 /* Save the current position to the given file */
\r
9821 SavePositionToFile(filename)
\r
9825 char buf[MSG_SIZ];
\r
9827 if (strcmp(filename, "-") == 0) {
\r
9828 return SavePosition(stdout, 0, NULL);
\r
9830 f = fopen(filename, "a");
\r
9832 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9833 DisplayError(buf, errno);
\r
9836 SavePosition(f, 0, NULL);
\r
9842 /* Save the current position to the given open file and close the file */
\r
9844 SavePosition(f, dummy, dummy2)
\r
9852 if (appData.oldSaveStyle) {
\r
9853 tm = time((time_t *) NULL);
\r
9855 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9856 PrintOpponents(f);
\r
9857 fprintf(f, "[--------------\n");
\r
9858 PrintPosition(f, currentMove);
\r
9859 fprintf(f, "--------------]\n");
\r
9861 fen = PositionToFEN(currentMove, 1);
\r
9862 fprintf(f, "%s\n", fen);
\r
9870 ReloadCmailMsgEvent(unregister)
\r
9874 static char *inFilename = NULL;
\r
9875 static char *outFilename;
\r
9877 struct stat inbuf, outbuf;
\r
9880 /* Any registered moves are unregistered if unregister is set, */
\r
9881 /* i.e. invoked by the signal handler */
\r
9883 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9884 cmailMoveRegistered[i] = FALSE;
\r
9885 if (cmailCommentList[i] != NULL) {
\r
9886 free(cmailCommentList[i]);
\r
9887 cmailCommentList[i] = NULL;
\r
9890 nCmailMovesRegistered = 0;
\r
9893 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9894 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9896 nCmailResults = 0;
\r
9898 if (inFilename == NULL) {
\r
9899 /* Because the filenames are static they only get malloced once */
\r
9900 /* and they never get freed */
\r
9901 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9902 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9904 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9905 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9908 status = stat(outFilename, &outbuf);
\r
9910 cmailMailedMove = FALSE;
\r
9912 status = stat(inFilename, &inbuf);
\r
9913 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9916 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9917 counts the games, notes how each one terminated, etc.
\r
9919 It would be nice to remove this kludge and instead gather all
\r
9920 the information while building the game list. (And to keep it
\r
9921 in the game list nodes instead of having a bunch of fixed-size
\r
9922 parallel arrays.) Note this will require getting each game's
\r
9923 termination from the PGN tags, as the game list builder does
\r
9924 not process the game moves. --mann
\r
9926 cmailMsgLoaded = TRUE;
\r
9927 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9929 /* Load first game in the file or popup game menu */
\r
9930 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9932 #endif /* !WIN32 */
\r
9940 char string[MSG_SIZ];
\r
9942 if ( cmailMailedMove
\r
9943 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9944 return TRUE; /* Allow free viewing */
\r
9947 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9948 /* with the move registered when the conditions for registering no */
\r
9950 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9951 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9952 nCmailMovesRegistered --;
\r
9954 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9956 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9957 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9961 if (cmailOldMove == -1) {
\r
9962 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9966 if (currentMove > cmailOldMove + 1) {
\r
9967 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9971 if (currentMove < cmailOldMove) {
\r
9972 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9976 if (forwardMostMove > currentMove) {
\r
9977 /* Silently truncate extra moves */
\r
9981 if ( (currentMove == cmailOldMove + 1)
\r
9982 || ( (currentMove == cmailOldMove)
\r
9983 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9984 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9985 if (gameInfo.result != GameUnfinished) {
\r
9986 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9989 if (commentList[currentMove] != NULL) {
\r
9990 cmailCommentList[lastLoadGameNumber - 1]
\r
9991 = StrSave(commentList[currentMove]);
\r
9993 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9995 if (appData.debugMode)
\r
9996 fprintf(debugFP, "Saving %s for game %d\n",
\r
9997 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
10000 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
10002 f = fopen(string, "w");
\r
10003 if (appData.oldSaveStyle) {
\r
10004 SaveGameOldStyle(f); /* also closes the file */
\r
10006 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
10007 f = fopen(string, "w");
\r
10008 SavePosition(f, 0, NULL); /* also closes the file */
\r
10010 fprintf(f, "{--------------\n");
\r
10011 PrintPosition(f, currentMove);
\r
10012 fprintf(f, "--------------}\n\n");
\r
10014 SaveGame(f, 0, NULL); /* also closes the file*/
\r
10017 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
10018 nCmailMovesRegistered ++;
\r
10019 } else if (nCmailGames == 1) {
\r
10020 DisplayError(_("You have not made a move yet"), 0);
\r
10031 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
10032 FILE *commandOutput;
\r
10033 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
10034 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
10040 if (! cmailMsgLoaded) {
\r
10041 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
10045 if (nCmailGames == nCmailResults) {
\r
10046 DisplayError(_("No unfinished games"), 0);
\r
10050 #if CMAIL_PROHIBIT_REMAIL
\r
10051 if (cmailMailedMove) {
\r
10052 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
10053 DisplayError(msg, 0);
\r
10058 if (! (cmailMailedMove || RegisterMove())) return;
\r
10060 if ( cmailMailedMove
\r
10061 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
10062 sprintf(string, partCommandString,
\r
10063 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
10064 commandOutput = popen(string, "r");
\r
10066 if (commandOutput == NULL) {
\r
10067 DisplayError(_("Failed to invoke cmail"), 0);
\r
10069 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10070 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10072 if (nBuffers > 1) {
\r
10073 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10074 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10075 nBytes = MSG_SIZ - 1;
\r
10077 (void) memcpy(msg, buffer, nBytes);
\r
10079 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10081 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10082 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10085 for (i = 0; i < nCmailGames; i ++) {
\r
10086 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10087 archived = FALSE;
\r
10091 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10093 sprintf(buffer, "%s/%s.%s.archive",
\r
10095 appData.cmailGameName,
\r
10097 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10098 cmailMsgLoaded = FALSE;
\r
10102 DisplayInformation(msg);
\r
10103 pclose(commandOutput);
\r
10106 if ((*cmailMsg) != '\0') {
\r
10107 DisplayInformation(cmailMsg);
\r
10112 #endif /* !WIN32 */
\r
10121 int prependComma = 0;
\r
10123 char string[MSG_SIZ]; /* Space for game-list */
\r
10126 if (!cmailMsgLoaded) return "";
\r
10128 if (cmailMailedMove) {
\r
10129 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10131 /* Create a list of games left */
\r
10132 sprintf(string, "[");
\r
10133 for (i = 0; i < nCmailGames; i ++) {
\r
10134 if (! ( cmailMoveRegistered[i]
\r
10135 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10136 if (prependComma) {
\r
10137 sprintf(number, ",%d", i + 1);
\r
10139 sprintf(number, "%d", i + 1);
\r
10140 prependComma = 1;
\r
10143 strcat(string, number);
\r
10146 strcat(string, "]");
\r
10148 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10149 switch (nCmailGames) {
\r
10151 sprintf(cmailMsg,
\r
10152 _("Still need to make move for game\n"));
\r
10156 sprintf(cmailMsg,
\r
10157 _("Still need to make moves for both games\n"));
\r
10161 sprintf(cmailMsg,
\r
10162 _("Still need to make moves for all %d games\n"),
\r
10167 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10169 sprintf(cmailMsg,
\r
10170 _("Still need to make a move for game %s\n"),
\r
10175 if (nCmailResults == nCmailGames) {
\r
10176 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10178 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10183 sprintf(cmailMsg,
\r
10184 _("Still need to make moves for games %s\n"),
\r
10190 #endif /* WIN32 */
\r
10196 if (gameMode == Training)
\r
10197 SetTrainingModeOff();
\r
10199 Reset(TRUE, TRUE);
\r
10200 cmailMsgLoaded = FALSE;
\r
10201 if (appData.icsActive) {
\r
10202 SendToICS(ics_prefix);
\r
10203 SendToICS("refresh\n");
\r
10208 ExitEvent(status)
\r
10212 if (exiting > 2) {
\r
10213 /* Give up on clean exit */
\r
10216 if (exiting > 1) {
\r
10217 /* Keep trying for clean exit */
\r
10221 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10223 if (telnetISR != NULL) {
\r
10224 RemoveInputSource(telnetISR);
\r
10226 if (icsPR != NoProc) {
\r
10227 DestroyChildProcess(icsPR, TRUE);
\r
10230 /* Save game if resource set and not already saved by GameEnds() */
\r
10231 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10232 && forwardMostMove > 0) {
\r
10233 if (*appData.saveGameFile != NULLCHAR) {
\r
10234 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10235 } else if (appData.autoSaveGames) {
\r
10238 if (*appData.savePositionFile != NULLCHAR) {
\r
10239 SavePositionToFile(appData.savePositionFile);
\r
10242 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10244 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10245 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10247 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10248 /* make sure this other one finishes before killing it! */
\r
10249 if(endingGame) { int count = 0;
\r
10250 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10251 while(endingGame && count++ < 10) DoSleep(1);
\r
10252 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10255 /* Kill off chess programs */
\r
10256 if (first.pr != NoProc) {
\r
10257 ExitAnalyzeMode();
\r
10259 DoSleep( appData.delayBeforeQuit );
\r
10260 SendToProgram("quit\n", &first);
\r
10261 DoSleep( appData.delayAfterQuit );
\r
10262 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10264 if (second.pr != NoProc) {
\r
10265 DoSleep( appData.delayBeforeQuit );
\r
10266 SendToProgram("quit\n", &second);
\r
10267 DoSleep( appData.delayAfterQuit );
\r
10268 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10270 if (first.isr != NULL) {
\r
10271 RemoveInputSource(first.isr);
\r
10273 if (second.isr != NULL) {
\r
10274 RemoveInputSource(second.isr);
\r
10277 ShutDownFrontEnd();
\r
10284 if (appData.debugMode)
\r
10285 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10289 if (gameMode == MachinePlaysWhite ||
\r
10290 gameMode == MachinePlaysBlack) {
\r
10293 DisplayBothClocks();
\r
10295 if (gameMode == PlayFromGameFile) {
\r
10296 if (appData.timeDelay >= 0)
\r
10297 AutoPlayGameLoop();
\r
10298 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10299 Reset(FALSE, TRUE);
\r
10300 SendToICS(ics_prefix);
\r
10301 SendToICS("refresh\n");
\r
10302 } else if (currentMove < forwardMostMove) {
\r
10303 ForwardInner(forwardMostMove);
\r
10305 pauseExamInvalid = FALSE;
\r
10307 switch (gameMode) {
\r
10310 case IcsExamining:
\r
10311 pauseExamForwardMostMove = forwardMostMove;
\r
10312 pauseExamInvalid = FALSE;
\r
10313 /* fall through */
\r
10314 case IcsObserving:
\r
10315 case IcsPlayingWhite:
\r
10316 case IcsPlayingBlack:
\r
10320 case PlayFromGameFile:
\r
10321 (void) StopLoadGameTimer();
\r
10325 case BeginningOfGame:
\r
10326 if (appData.icsActive) return;
\r
10327 /* else fall through */
\r
10328 case MachinePlaysWhite:
\r
10329 case MachinePlaysBlack:
\r
10330 case TwoMachinesPlay:
\r
10331 if (forwardMostMove == 0)
\r
10332 return; /* don't pause if no one has moved */
\r
10333 if ((gameMode == MachinePlaysWhite &&
\r
10334 !WhiteOnMove(forwardMostMove)) ||
\r
10335 (gameMode == MachinePlaysBlack &&
\r
10336 WhiteOnMove(forwardMostMove))) {
\r
10347 EditCommentEvent()
\r
10349 char title[MSG_SIZ];
\r
10351 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10352 strcpy(title, _("Edit comment"));
\r
10354 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10355 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10356 parseList[currentMove - 1]);
\r
10359 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10366 char *tags = PGNTags(&gameInfo);
\r
10367 EditTagsPopUp(tags);
\r
10372 AnalyzeModeEvent()
\r
10374 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10377 if (gameMode != AnalyzeFile) {
\r
10378 if (!appData.icsEngineAnalyze) {
\r
10380 if (gameMode != EditGame) return;
\r
10382 ResurrectChessProgram();
\r
10383 SendToProgram("analyze\n", &first);
\r
10384 first.analyzing = TRUE;
\r
10385 /*first.maybeThinking = TRUE;*/
\r
10386 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10387 AnalysisPopUp(_("Analysis"),
\r
10388 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10390 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10395 StartAnalysisClock();
\r
10396 GetTimeMark(&lastNodeCountTime);
\r
10397 lastNodeCount = 0;
\r
10401 AnalyzeFileEvent()
\r
10403 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10406 if (gameMode != AnalyzeMode) {
\r
10408 if (gameMode != EditGame) return;
\r
10409 ResurrectChessProgram();
\r
10410 SendToProgram("analyze\n", &first);
\r
10411 first.analyzing = TRUE;
\r
10412 /*first.maybeThinking = TRUE;*/
\r
10413 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10414 AnalysisPopUp(_("Analysis"),
\r
10415 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10417 gameMode = AnalyzeFile;
\r
10422 StartAnalysisClock();
\r
10423 GetTimeMark(&lastNodeCountTime);
\r
10424 lastNodeCount = 0;
\r
10428 MachineWhiteEvent()
\r
10430 char buf[MSG_SIZ];
\r
10431 char *bookHit = NULL;
\r
10433 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10437 if (gameMode == PlayFromGameFile ||
\r
10438 gameMode == TwoMachinesPlay ||
\r
10439 gameMode == Training ||
\r
10440 gameMode == AnalyzeMode ||
\r
10441 gameMode == EndOfGame)
\r
10444 if (gameMode == EditPosition)
\r
10445 EditPositionDone();
\r
10447 if (!WhiteOnMove(currentMove)) {
\r
10448 DisplayError(_("It is not White's turn"), 0);
\r
10452 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10453 ExitAnalyzeMode();
\r
10455 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10456 gameMode == AnalyzeFile)
\r
10459 ResurrectChessProgram(); /* in case it isn't running */
\r
10460 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10461 gameMode = MachinePlaysWhite;
\r
10464 gameMode = MachinePlaysWhite;
\r
10468 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10469 DisplayTitle(buf);
\r
10470 if (first.sendName) {
\r
10471 sprintf(buf, "name %s\n", gameInfo.black);
\r
10472 SendToProgram(buf, &first);
\r
10474 if (first.sendTime) {
\r
10475 if (first.useColors) {
\r
10476 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10478 SendTimeRemaining(&first, TRUE);
\r
10480 if (first.useColors) {
\r
10481 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10483 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10484 SetMachineThinkingEnables();
\r
10485 first.maybeThinking = TRUE;
\r
10488 if (appData.autoFlipView && !flipView) {
\r
10489 flipView = !flipView;
\r
10490 DrawPosition(FALSE, NULL);
\r
10491 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10494 if(bookHit) { // [HGM] book: simulate book reply
\r
10495 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10497 programStats.nodes = programStats.depth = programStats.time =
\r
10498 programStats.score = programStats.got_only_move = 0;
\r
10499 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10501 strcpy(bookMove, "move ");
\r
10502 strcat(bookMove, bookHit);
\r
10503 HandleMachineMove(bookMove, &first);
\r
10508 MachineBlackEvent()
\r
10510 char buf[MSG_SIZ];
\r
10511 char *bookHit = NULL;
\r
10513 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10517 if (gameMode == PlayFromGameFile ||
\r
10518 gameMode == TwoMachinesPlay ||
\r
10519 gameMode == Training ||
\r
10520 gameMode == AnalyzeMode ||
\r
10521 gameMode == EndOfGame)
\r
10524 if (gameMode == EditPosition)
\r
10525 EditPositionDone();
\r
10527 if (WhiteOnMove(currentMove)) {
\r
10528 DisplayError(_("It is not Black's turn"), 0);
\r
10532 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10533 ExitAnalyzeMode();
\r
10535 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10536 gameMode == AnalyzeFile)
\r
10539 ResurrectChessProgram(); /* in case it isn't running */
\r
10540 gameMode = MachinePlaysBlack;
\r
10544 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10545 DisplayTitle(buf);
\r
10546 if (first.sendName) {
\r
10547 sprintf(buf, "name %s\n", gameInfo.white);
\r
10548 SendToProgram(buf, &first);
\r
10550 if (first.sendTime) {
\r
10551 if (first.useColors) {
\r
10552 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10554 SendTimeRemaining(&first, FALSE);
\r
10556 if (first.useColors) {
\r
10557 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10559 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10560 SetMachineThinkingEnables();
\r
10561 first.maybeThinking = TRUE;
\r
10564 if (appData.autoFlipView && flipView) {
\r
10565 flipView = !flipView;
\r
10566 DrawPosition(FALSE, NULL);
\r
10567 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10569 if(bookHit) { // [HGM] book: simulate book reply
\r
10570 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10572 programStats.nodes = programStats.depth = programStats.time =
\r
10573 programStats.score = programStats.got_only_move = 0;
\r
10574 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10576 strcpy(bookMove, "move ");
\r
10577 strcat(bookMove, bookHit);
\r
10578 HandleMachineMove(bookMove, &first);
\r
10584 DisplayTwoMachinesTitle()
\r
10586 char buf[MSG_SIZ];
\r
10587 if (appData.matchGames > 0) {
\r
10588 if (first.twoMachinesColor[0] == 'w') {
\r
10589 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10590 gameInfo.white, gameInfo.black,
\r
10591 first.matchWins, second.matchWins,
\r
10592 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10594 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10595 gameInfo.white, gameInfo.black,
\r
10596 second.matchWins, first.matchWins,
\r
10597 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10600 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10602 DisplayTitle(buf);
\r
10606 TwoMachinesEvent P((void))
\r
10609 char buf[MSG_SIZ];
\r
10610 ChessProgramState *onmove;
\r
10611 char *bookHit = NULL;
\r
10613 if (appData.noChessProgram) return;
\r
10615 switch (gameMode) {
\r
10616 case TwoMachinesPlay:
\r
10618 case MachinePlaysWhite:
\r
10619 case MachinePlaysBlack:
\r
10620 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10621 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10624 /* fall through */
\r
10625 case BeginningOfGame:
\r
10626 case PlayFromGameFile:
\r
10629 if (gameMode != EditGame) return;
\r
10631 case EditPosition:
\r
10632 EditPositionDone();
\r
10634 case AnalyzeMode:
\r
10635 case AnalyzeFile:
\r
10636 ExitAnalyzeMode();
\r
10643 forwardMostMove = currentMove;
\r
10644 ResurrectChessProgram(); /* in case first program isn't running */
\r
10646 if (second.pr == NULL) {
\r
10647 StartChessProgram(&second);
\r
10648 if (second.protocolVersion == 1) {
\r
10649 TwoMachinesEventIfReady();
\r
10651 /* kludge: allow timeout for initial "feature" command */
\r
10653 DisplayMessage("", _("Starting second chess program"));
\r
10654 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10658 DisplayMessage("", "");
\r
10659 InitChessProgram(&second, FALSE);
\r
10660 SendToProgram("force\n", &second);
\r
10661 if (startedFromSetupPosition) {
\r
10662 SendBoard(&second, backwardMostMove);
\r
10663 if (appData.debugMode) {
\r
10664 fprintf(debugFP, "Two Machines\n");
\r
10667 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10668 SendMoveToProgram(i, &second);
\r
10671 gameMode = TwoMachinesPlay;
\r
10675 DisplayTwoMachinesTitle();
\r
10676 firstMove = TRUE;
\r
10677 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10680 onmove = &second;
\r
10683 SendToProgram(first.computerString, &first);
\r
10684 if (first.sendName) {
\r
10685 sprintf(buf, "name %s\n", second.tidy);
\r
10686 SendToProgram(buf, &first);
\r
10688 SendToProgram(second.computerString, &second);
\r
10689 if (second.sendName) {
\r
10690 sprintf(buf, "name %s\n", first.tidy);
\r
10691 SendToProgram(buf, &second);
\r
10695 if (!first.sendTime || !second.sendTime) {
\r
10696 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10697 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10699 if (onmove->sendTime) {
\r
10700 if (onmove->useColors) {
\r
10701 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10703 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10705 if (onmove->useColors) {
\r
10706 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10708 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10709 // SendToProgram("go\n", onmove);
\r
10710 onmove->maybeThinking = TRUE;
\r
10711 SetMachineThinkingEnables();
\r
10715 if(bookHit) { // [HGM] book: simulate book reply
\r
10716 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10718 programStats.nodes = programStats.depth = programStats.time =
\r
10719 programStats.score = programStats.got_only_move = 0;
\r
10720 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10722 strcpy(bookMove, "move ");
\r
10723 strcat(bookMove, bookHit);
\r
10724 HandleMachineMove(bookMove, &first);
\r
10731 if (gameMode == Training) {
\r
10732 SetTrainingModeOff();
\r
10733 gameMode = PlayFromGameFile;
\r
10734 DisplayMessage("", _("Training mode off"));
\r
10736 gameMode = Training;
\r
10737 animateTraining = appData.animate;
\r
10739 /* make sure we are not already at the end of the game */
\r
10740 if (currentMove < forwardMostMove) {
\r
10741 SetTrainingModeOn();
\r
10742 DisplayMessage("", _("Training mode on"));
\r
10744 gameMode = PlayFromGameFile;
\r
10745 DisplayError(_("Already at end of game"), 0);
\r
10754 if (!appData.icsActive) return;
\r
10755 switch (gameMode) {
\r
10756 case IcsPlayingWhite:
\r
10757 case IcsPlayingBlack:
\r
10758 case IcsObserving:
\r
10760 case BeginningOfGame:
\r
10761 case IcsExamining:
\r
10767 case EditPosition:
\r
10768 EditPositionDone();
\r
10771 case AnalyzeMode:
\r
10772 case AnalyzeFile:
\r
10773 ExitAnalyzeMode();
\r
10781 gameMode = IcsIdle;
\r
10792 switch (gameMode) {
\r
10794 SetTrainingModeOff();
\r
10796 case MachinePlaysWhite:
\r
10797 case MachinePlaysBlack:
\r
10798 case BeginningOfGame:
\r
10799 SendToProgram("force\n", &first);
\r
10800 SetUserThinkingEnables();
\r
10802 case PlayFromGameFile:
\r
10803 (void) StopLoadGameTimer();
\r
10804 if (gameFileFP != NULL) {
\r
10805 gameFileFP = NULL;
\r
10808 case EditPosition:
\r
10809 EditPositionDone();
\r
10811 case AnalyzeMode:
\r
10812 case AnalyzeFile:
\r
10813 ExitAnalyzeMode();
\r
10814 SendToProgram("force\n", &first);
\r
10816 case TwoMachinesPlay:
\r
10817 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10818 ResurrectChessProgram();
\r
10819 SetUserThinkingEnables();
\r
10822 ResurrectChessProgram();
\r
10824 case IcsPlayingBlack:
\r
10825 case IcsPlayingWhite:
\r
10826 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10828 case IcsObserving:
\r
10829 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10831 case IcsExamining:
\r
10832 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10843 first.offeredDraw = second.offeredDraw = 0;
\r
10845 if (gameMode == PlayFromGameFile) {
\r
10846 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10847 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10848 DisplayTitle("");
\r
10851 if (gameMode == MachinePlaysWhite ||
\r
10852 gameMode == MachinePlaysBlack ||
\r
10853 gameMode == TwoMachinesPlay ||
\r
10854 gameMode == EndOfGame) {
\r
10855 i = forwardMostMove;
\r
10856 while (i > currentMove) {
\r
10857 SendToProgram("undo\n", &first);
\r
10860 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10861 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10862 DisplayBothClocks();
\r
10863 if (whiteFlag || blackFlag) {
\r
10864 whiteFlag = blackFlag = 0;
\r
10866 DisplayTitle("");
\r
10869 gameMode = EditGame;
\r
10876 EditPositionEvent()
\r
10878 if (gameMode == EditPosition) {
\r
10884 if (gameMode != EditGame) return;
\r
10886 gameMode = EditPosition;
\r
10889 if (currentMove > 0)
\r
10890 CopyBoard(boards[0], boards[currentMove]);
\r
10892 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10894 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10895 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10900 ExitAnalyzeMode()
\r
10902 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10903 if (appData.icsEngineAnalyze) {
\r
10904 appData.icsEngineAnalyze = FALSE;
\r
10906 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10908 if (first.analysisSupport && first.analyzing) {
\r
10909 SendToProgram("exit\n", &first);
\r
10910 first.analyzing = FALSE;
\r
10912 AnalysisPopDown();
\r
10913 thinkOutput[0] = NULLCHAR;
\r
10917 EditPositionDone()
\r
10919 startedFromSetupPosition = TRUE;
\r
10920 InitChessProgram(&first, FALSE);
\r
10921 SendToProgram("force\n", &first);
\r
10922 if (blackPlaysFirst) {
\r
10923 strcpy(moveList[0], "");
\r
10924 strcpy(parseList[0], "");
\r
10925 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10926 CopyBoard(boards[1], boards[0]);
\r
10927 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10929 epStatus[1] = epStatus[0];
\r
10930 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10933 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10935 SendBoard(&first, forwardMostMove);
\r
10936 if (appData.debugMode) {
\r
10937 fprintf(debugFP, "EditPosDone\n");
\r
10939 DisplayTitle("");
\r
10940 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10941 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10942 gameMode = EditGame;
\r
10944 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10945 ClearHighlights(); /* [AS] */
\r
10948 /* Pause for `ms' milliseconds */
\r
10949 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10956 GetTimeMark(&m1);
\r
10958 GetTimeMark(&m2);
\r
10959 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10962 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10964 SendMultiLineToICS(buf)
\r
10967 char temp[MSG_SIZ+1], *p;
\r
10970 len = strlen(buf);
\r
10971 if (len > MSG_SIZ)
\r
10974 strncpy(temp, buf, len);
\r
10979 if (*p == '\n' || *p == '\r')
\r
10984 strcat(temp, "\n");
\r
10986 SendToPlayer(temp, strlen(temp));
\r
10990 SetWhiteToPlayEvent()
\r
10992 if (gameMode == EditPosition) {
\r
10993 blackPlaysFirst = FALSE;
\r
10994 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10995 } else if (gameMode == IcsExamining) {
\r
10996 SendToICS(ics_prefix);
\r
10997 SendToICS("tomove white\n");
\r
11002 SetBlackToPlayEvent()
\r
11004 if (gameMode == EditPosition) {
\r
11005 blackPlaysFirst = TRUE;
\r
11006 currentMove = 1; /* kludge */
\r
11007 DisplayBothClocks();
\r
11009 } else if (gameMode == IcsExamining) {
\r
11010 SendToICS(ics_prefix);
\r
11011 SendToICS("tomove black\n");
\r
11016 EditPositionMenuEvent(selection, x, y)
\r
11017 ChessSquare selection;
\r
11020 char buf[MSG_SIZ];
\r
11021 ChessSquare piece = boards[0][y][x];
\r
11023 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
11025 switch (selection) {
\r
11027 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
11028 SendToICS(ics_prefix);
\r
11029 SendToICS("bsetup clear\n");
\r
11030 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
11031 SendToICS(ics_prefix);
\r
11032 SendToICS("clearboard\n");
\r
11034 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
11035 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
11036 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
11037 if (gameMode == IcsExamining) {
\r
11038 if (boards[currentMove][y][x] != EmptySquare) {
\r
11039 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
11040 AAA + x, ONE + y);
\r
11044 boards[0][y][x] = p;
\r
11049 if (gameMode == EditPosition) {
\r
11050 DrawPosition(FALSE, boards[0]);
\r
11055 SetWhiteToPlayEvent();
\r
11059 SetBlackToPlayEvent();
\r
11062 case EmptySquare:
\r
11063 if (gameMode == IcsExamining) {
\r
11064 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11067 boards[0][y][x] = EmptySquare;
\r
11068 DrawPosition(FALSE, boards[0]);
\r
11072 case PromotePiece:
\r
11073 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11074 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11075 selection = (ChessSquare) (PROMOTED piece);
\r
11076 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11077 else selection = (ChessSquare)((int)piece - 1);
\r
11078 goto defaultlabel;
\r
11080 case DemotePiece:
\r
11081 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11082 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11083 selection = (ChessSquare) (DEMOTED piece);
\r
11084 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11085 else selection = (ChessSquare)((int)piece + 1);
\r
11086 goto defaultlabel;
\r
11090 if(gameInfo.variant == VariantShatranj ||
\r
11091 gameInfo.variant == VariantXiangqi ||
\r
11092 gameInfo.variant == VariantCourier )
\r
11093 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11094 goto defaultlabel;
\r
11098 if(gameInfo.variant == VariantXiangqi)
\r
11099 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11100 if(gameInfo.variant == VariantKnightmate)
\r
11101 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11104 if (gameMode == IcsExamining) {
\r
11105 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11106 PieceToChar(selection), AAA + x, ONE + y);
\r
11109 boards[0][y][x] = selection;
\r
11110 DrawPosition(FALSE, boards[0]);
\r
11118 DropMenuEvent(selection, x, y)
\r
11119 ChessSquare selection;
\r
11122 ChessMove moveType;
\r
11124 switch (gameMode) {
\r
11125 case IcsPlayingWhite:
\r
11126 case MachinePlaysBlack:
\r
11127 if (!WhiteOnMove(currentMove)) {
\r
11128 DisplayMoveError(_("It is Black's turn"));
\r
11131 moveType = WhiteDrop;
\r
11133 case IcsPlayingBlack:
\r
11134 case MachinePlaysWhite:
\r
11135 if (WhiteOnMove(currentMove)) {
\r
11136 DisplayMoveError(_("It is White's turn"));
\r
11139 moveType = BlackDrop;
\r
11142 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11148 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11149 selection = (ChessSquare) ((int) selection
\r
11150 + (int) BlackPawn - (int) WhitePawn);
\r
11152 if (boards[currentMove][y][x] != EmptySquare) {
\r
11153 DisplayMoveError(_("That square is occupied"));
\r
11157 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11163 /* Accept a pending offer of any kind from opponent */
\r
11165 if (appData.icsActive) {
\r
11166 SendToICS(ics_prefix);
\r
11167 SendToICS("accept\n");
\r
11168 } else if (cmailMsgLoaded) {
\r
11169 if (currentMove == cmailOldMove &&
\r
11170 commentList[cmailOldMove] != NULL &&
\r
11171 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11172 "Black offers a draw" : "White offers a draw")) {
\r
11174 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11175 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11177 DisplayError(_("There is no pending offer on this move"), 0);
\r
11178 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11181 /* Not used for offers from chess program */
\r
11188 /* Decline a pending offer of any kind from opponent */
\r
11190 if (appData.icsActive) {
\r
11191 SendToICS(ics_prefix);
\r
11192 SendToICS("decline\n");
\r
11193 } else if (cmailMsgLoaded) {
\r
11194 if (currentMove == cmailOldMove &&
\r
11195 commentList[cmailOldMove] != NULL &&
\r
11196 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11197 "Black offers a draw" : "White offers a draw")) {
\r
11199 AppendComment(cmailOldMove, "Draw declined");
\r
11200 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11201 #endif /*NOTDEF*/
\r
11203 DisplayError(_("There is no pending offer on this move"), 0);
\r
11206 /* Not used for offers from chess program */
\r
11213 /* Issue ICS rematch command */
\r
11214 if (appData.icsActive) {
\r
11215 SendToICS(ics_prefix);
\r
11216 SendToICS("rematch\n");
\r
11223 /* Call your opponent's flag (claim a win on time) */
\r
11224 if (appData.icsActive) {
\r
11225 SendToICS(ics_prefix);
\r
11226 SendToICS("flag\n");
\r
11228 switch (gameMode) {
\r
11231 case MachinePlaysWhite:
\r
11234 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11237 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11239 DisplayError(_("Your opponent is not out of time"), 0);
\r
11242 case MachinePlaysBlack:
\r
11245 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11248 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11250 DisplayError(_("Your opponent is not out of time"), 0);
\r
11260 /* Offer draw or accept pending draw offer from opponent */
\r
11262 if (appData.icsActive) {
\r
11263 /* Note: tournament rules require draw offers to be
\r
11264 made after you make your move but before you punch
\r
11265 your clock. Currently ICS doesn't let you do that;
\r
11266 instead, you immediately punch your clock after making
\r
11267 a move, but you can offer a draw at any time. */
\r
11269 SendToICS(ics_prefix);
\r
11270 SendToICS("draw\n");
\r
11271 } else if (cmailMsgLoaded) {
\r
11272 if (currentMove == cmailOldMove &&
\r
11273 commentList[cmailOldMove] != NULL &&
\r
11274 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11275 "Black offers a draw" : "White offers a draw")) {
\r
11276 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11277 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11278 } else if (currentMove == cmailOldMove + 1) {
\r
11279 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11280 "White offers a draw" : "Black offers a draw";
\r
11281 AppendComment(currentMove, offer);
\r
11282 DisplayComment(currentMove - 1, offer);
\r
11283 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11285 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11286 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11288 } else if (first.offeredDraw) {
\r
11289 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11291 if (first.sendDrawOffers) {
\r
11292 SendToProgram("draw\n", &first);
\r
11293 userOfferedDraw = TRUE;
\r
11301 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11303 if (appData.icsActive) {
\r
11304 SendToICS(ics_prefix);
\r
11305 SendToICS("adjourn\n");
\r
11307 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11315 /* Offer Abort or accept pending Abort offer from opponent */
\r
11317 if (appData.icsActive) {
\r
11318 SendToICS(ics_prefix);
\r
11319 SendToICS("abort\n");
\r
11321 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11328 /* Resign. You can do this even if it's not your turn. */
\r
11330 if (appData.icsActive) {
\r
11331 SendToICS(ics_prefix);
\r
11332 SendToICS("resign\n");
\r
11334 switch (gameMode) {
\r
11335 case MachinePlaysWhite:
\r
11336 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11338 case MachinePlaysBlack:
\r
11339 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11342 if (cmailMsgLoaded) {
\r
11344 if (WhiteOnMove(cmailOldMove)) {
\r
11345 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11347 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11349 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11360 StopObservingEvent()
\r
11362 /* Stop observing current games */
\r
11363 SendToICS(ics_prefix);
\r
11364 SendToICS("unobserve\n");
\r
11368 StopExaminingEvent()
\r
11370 /* Stop observing current game */
\r
11371 SendToICS(ics_prefix);
\r
11372 SendToICS("unexamine\n");
\r
11376 ForwardInner(target)
\r
11381 if (appData.debugMode)
\r
11382 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11383 target, currentMove, forwardMostMove);
\r
11385 if (gameMode == EditPosition)
\r
11388 if (gameMode == PlayFromGameFile && !pausing)
\r
11391 if (gameMode == IcsExamining && pausing)
\r
11392 limit = pauseExamForwardMostMove;
\r
11394 limit = forwardMostMove;
\r
11396 if (target > limit) target = limit;
\r
11398 if (target > 0 && moveList[target - 1][0]) {
\r
11399 int fromX, fromY, toX, toY;
\r
11400 toX = moveList[target - 1][2] - AAA;
\r
11401 toY = moveList[target - 1][3] - ONE;
\r
11402 if (moveList[target - 1][1] == '@') {
\r
11403 if (appData.highlightLastMove) {
\r
11404 SetHighlights(-1, -1, toX, toY);
\r
11407 fromX = moveList[target - 1][0] - AAA;
\r
11408 fromY = moveList[target - 1][1] - ONE;
\r
11409 if (target == currentMove + 1) {
\r
11410 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11412 if (appData.highlightLastMove) {
\r
11413 SetHighlights(fromX, fromY, toX, toY);
\r
11417 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11418 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11419 gameMode == AnalyzeFile) {
\r
11420 while (currentMove < target) {
\r
11421 SendMoveToProgram(currentMove++, &first);
\r
11424 currentMove = target;
\r
11427 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11428 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11429 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11431 DisplayBothClocks();
\r
11432 DisplayMove(currentMove - 1);
\r
11433 DrawPosition(FALSE, boards[currentMove]);
\r
11434 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11435 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11436 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11444 if (gameMode == IcsExamining && !pausing) {
\r
11445 SendToICS(ics_prefix);
\r
11446 SendToICS("forward\n");
\r
11448 ForwardInner(currentMove + 1);
\r
11455 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11456 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11457 * the remaining moves to the engine. Otherwise we get analysis output
\r
11458 * after each move.
\r
11460 if (first.analysisSupport) {
\r
11461 SendToProgram("exit\nforce\n", &first);
\r
11462 first.analyzing = FALSE;
\r
11466 if (gameMode == IcsExamining && !pausing) {
\r
11467 SendToICS(ics_prefix);
\r
11468 SendToICS("forward 999999\n");
\r
11470 ForwardInner(forwardMostMove);
\r
11473 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11474 /* we have fed all the moves, so reactivate analysis mode */
\r
11475 SendToProgram("analyze\n", &first);
\r
11476 first.analyzing = TRUE;
\r
11477 /*first.maybeThinking = TRUE;*/
\r
11478 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11483 BackwardInner(target)
\r
11486 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11488 if (appData.debugMode)
\r
11489 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11490 target, currentMove, forwardMostMove);
\r
11492 if (gameMode == EditPosition) return;
\r
11493 if (currentMove <= backwardMostMove) {
\r
11494 ClearHighlights();
\r
11495 DrawPosition(full_redraw, boards[currentMove]);
\r
11498 if (gameMode == PlayFromGameFile && !pausing)
\r
11501 if (moveList[target][0]) {
\r
11502 int fromX, fromY, toX, toY;
\r
11503 toX = moveList[target][2] - AAA;
\r
11504 toY = moveList[target][3] - ONE;
\r
11505 if (moveList[target][1] == '@') {
\r
11506 if (appData.highlightLastMove) {
\r
11507 SetHighlights(-1, -1, toX, toY);
\r
11510 fromX = moveList[target][0] - AAA;
\r
11511 fromY = moveList[target][1] - ONE;
\r
11512 if (target == currentMove - 1) {
\r
11513 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11515 if (appData.highlightLastMove) {
\r
11516 SetHighlights(fromX, fromY, toX, toY);
\r
11520 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11521 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11522 while (currentMove > target) {
\r
11523 SendToProgram("undo\n", &first);
\r
11527 currentMove = target;
\r
11530 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11531 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11532 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11534 DisplayBothClocks();
\r
11535 DisplayMove(currentMove - 1);
\r
11536 DrawPosition(full_redraw, boards[currentMove]);
\r
11537 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11538 // [HGM] PV info: routine tests if comment empty
\r
11539 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11545 if (gameMode == IcsExamining && !pausing) {
\r
11546 SendToICS(ics_prefix);
\r
11547 SendToICS("backward\n");
\r
11549 BackwardInner(currentMove - 1);
\r
11556 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11557 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11558 * all the moves. Otherwise we get analysis output after each undo.
\r
11560 if (first.analysisSupport) {
\r
11561 SendToProgram("exit\nforce\n", &first);
\r
11562 first.analyzing = FALSE;
\r
11566 if (gameMode == IcsExamining && !pausing) {
\r
11567 SendToICS(ics_prefix);
\r
11568 SendToICS("backward 999999\n");
\r
11570 BackwardInner(backwardMostMove);
\r
11573 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11574 /* we have fed all the moves, so reactivate analysis mode */
\r
11575 SendToProgram("analyze\n", &first);
\r
11576 first.analyzing = TRUE;
\r
11577 /*first.maybeThinking = TRUE;*/
\r
11578 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11583 ToNrEvent(int to)
\r
11585 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11586 if (to >= forwardMostMove) to = forwardMostMove;
\r
11587 if (to <= backwardMostMove) to = backwardMostMove;
\r
11588 if (to < currentMove) {
\r
11589 BackwardInner(to);
\r
11591 ForwardInner(to);
\r
11598 if (gameMode != IcsExamining) {
\r
11599 DisplayError(_("You are not examining a game"), 0);
\r
11603 DisplayError(_("You can't revert while pausing"), 0);
\r
11606 SendToICS(ics_prefix);
\r
11607 SendToICS("revert\n");
\r
11611 RetractMoveEvent()
\r
11613 switch (gameMode) {
\r
11614 case MachinePlaysWhite:
\r
11615 case MachinePlaysBlack:
\r
11616 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11617 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11620 if (forwardMostMove < 2) return;
\r
11621 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11622 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11623 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11624 DisplayBothClocks();
\r
11625 DisplayMove(currentMove - 1);
\r
11626 ClearHighlights();/*!! could figure this out*/
\r
11627 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11628 SendToProgram("remove\n", &first);
\r
11629 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11632 case BeginningOfGame:
\r
11636 case IcsPlayingWhite:
\r
11637 case IcsPlayingBlack:
\r
11638 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11639 SendToICS(ics_prefix);
\r
11640 SendToICS("takeback 2\n");
\r
11642 SendToICS(ics_prefix);
\r
11643 SendToICS("takeback 1\n");
\r
11652 ChessProgramState *cps;
\r
11654 switch (gameMode) {
\r
11655 case MachinePlaysWhite:
\r
11656 if (!WhiteOnMove(forwardMostMove)) {
\r
11657 DisplayError(_("It is your turn"), 0);
\r
11662 case MachinePlaysBlack:
\r
11663 if (WhiteOnMove(forwardMostMove)) {
\r
11664 DisplayError(_("It is your turn"), 0);
\r
11669 case TwoMachinesPlay:
\r
11670 if (WhiteOnMove(forwardMostMove) ==
\r
11671 (first.twoMachinesColor[0] == 'w')) {
\r
11677 case BeginningOfGame:
\r
11681 SendToProgram("?\n", cps);
\r
11685 TruncateGameEvent()
\r
11688 if (gameMode != EditGame) return;
\r
11695 if (forwardMostMove > currentMove) {
\r
11696 if (gameInfo.resultDetails != NULL) {
\r
11697 free(gameInfo.resultDetails);
\r
11698 gameInfo.resultDetails = NULL;
\r
11699 gameInfo.result = GameUnfinished;
\r
11701 forwardMostMove = currentMove;
\r
11702 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11710 if (appData.noChessProgram) return;
\r
11711 switch (gameMode) {
\r
11712 case MachinePlaysWhite:
\r
11713 if (WhiteOnMove(forwardMostMove)) {
\r
11714 DisplayError(_("Wait until your turn"), 0);
\r
11718 case BeginningOfGame:
\r
11719 case MachinePlaysBlack:
\r
11720 if (!WhiteOnMove(forwardMostMove)) {
\r
11721 DisplayError(_("Wait until your turn"), 0);
\r
11726 DisplayError(_("No hint available"), 0);
\r
11729 SendToProgram("hint\n", &first);
\r
11730 hintRequested = TRUE;
\r
11736 if (appData.noChessProgram) return;
\r
11737 switch (gameMode) {
\r
11738 case MachinePlaysWhite:
\r
11739 if (WhiteOnMove(forwardMostMove)) {
\r
11740 DisplayError(_("Wait until your turn"), 0);
\r
11744 case BeginningOfGame:
\r
11745 case MachinePlaysBlack:
\r
11746 if (!WhiteOnMove(forwardMostMove)) {
\r
11747 DisplayError(_("Wait until your turn"), 0);
\r
11751 case EditPosition:
\r
11752 EditPositionDone();
\r
11754 case TwoMachinesPlay:
\r
11759 SendToProgram("bk\n", &first);
\r
11760 bookOutput[0] = NULLCHAR;
\r
11761 bookRequested = TRUE;
\r
11767 char *tags = PGNTags(&gameInfo);
\r
11768 TagsPopUp(tags, CmailMsg());
\r
11772 /* end button procedures */
\r
11775 PrintPosition(fp, move)
\r
11781 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11782 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11783 char c = PieceToChar(boards[move][i][j]);
\r
11784 fputc(c == 'x' ? '.' : c, fp);
\r
11785 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11788 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11789 fprintf(fp, "white to play\n");
\r
11791 fprintf(fp, "black to play\n");
\r
11795 PrintOpponents(fp)
\r
11798 if (gameInfo.white != NULL) {
\r
11799 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11801 fprintf(fp, "\n");
\r
11805 /* Find last component of program's own name, using some heuristics */
\r
11807 TidyProgramName(prog, host, buf)
\r
11808 char *prog, *host, buf[MSG_SIZ];
\r
11811 int local = (strcmp(host, "localhost") == 0);
\r
11812 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11814 while (*p == ' ') p++;
\r
11817 if (*prog == '"' || *prog == '\'') {
\r
11818 q = strchr(prog + 1, *prog);
\r
11820 q = strchr(prog, ' ');
\r
11822 if (q == NULL) q = prog + strlen(prog);
\r
11824 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11826 if(p == prog && *p == '"') p++;
\r
11827 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11828 memcpy(buf, p, q - p);
\r
11829 buf[q - p] = NULLCHAR;
\r
11831 strcat(buf, "@");
\r
11832 strcat(buf, host);
\r
11837 TimeControlTagValue()
\r
11839 char buf[MSG_SIZ];
\r
11840 if (!appData.clockMode) {
\r
11841 strcpy(buf, "-");
\r
11842 } else if (movesPerSession > 0) {
\r
11843 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11844 } else if (timeIncrement == 0) {
\r
11845 sprintf(buf, "%ld", timeControl/1000);
\r
11847 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11849 return StrSave(buf);
\r
11855 /* This routine is used only for certain modes */
\r
11856 VariantClass v = gameInfo.variant;
\r
11857 ClearGameInfo(&gameInfo);
\r
11858 gameInfo.variant = v;
\r
11860 switch (gameMode) {
\r
11861 case MachinePlaysWhite:
\r
11862 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11863 gameInfo.site = StrSave(HostName());
\r
11864 gameInfo.date = PGNDate();
\r
11865 gameInfo.round = StrSave("-");
\r
11866 gameInfo.white = StrSave(first.tidy);
\r
11867 gameInfo.black = StrSave(UserName());
\r
11868 gameInfo.timeControl = TimeControlTagValue();
\r
11871 case MachinePlaysBlack:
\r
11872 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11873 gameInfo.site = StrSave(HostName());
\r
11874 gameInfo.date = PGNDate();
\r
11875 gameInfo.round = StrSave("-");
\r
11876 gameInfo.white = StrSave(UserName());
\r
11877 gameInfo.black = StrSave(first.tidy);
\r
11878 gameInfo.timeControl = TimeControlTagValue();
\r
11881 case TwoMachinesPlay:
\r
11882 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11883 gameInfo.site = StrSave(HostName());
\r
11884 gameInfo.date = PGNDate();
\r
11885 if (matchGame > 0) {
\r
11886 char buf[MSG_SIZ];
\r
11887 sprintf(buf, "%d", matchGame);
\r
11888 gameInfo.round = StrSave(buf);
\r
11890 gameInfo.round = StrSave("-");
\r
11892 if (first.twoMachinesColor[0] == 'w') {
\r
11893 gameInfo.white = StrSave(first.tidy);
\r
11894 gameInfo.black = StrSave(second.tidy);
\r
11896 gameInfo.white = StrSave(second.tidy);
\r
11897 gameInfo.black = StrSave(first.tidy);
\r
11899 gameInfo.timeControl = TimeControlTagValue();
\r
11903 gameInfo.event = StrSave("Edited game");
\r
11904 gameInfo.site = StrSave(HostName());
\r
11905 gameInfo.date = PGNDate();
\r
11906 gameInfo.round = StrSave("-");
\r
11907 gameInfo.white = StrSave("-");
\r
11908 gameInfo.black = StrSave("-");
\r
11911 case EditPosition:
\r
11912 gameInfo.event = StrSave("Edited position");
\r
11913 gameInfo.site = StrSave(HostName());
\r
11914 gameInfo.date = PGNDate();
\r
11915 gameInfo.round = StrSave("-");
\r
11916 gameInfo.white = StrSave("-");
\r
11917 gameInfo.black = StrSave("-");
\r
11920 case IcsPlayingWhite:
\r
11921 case IcsPlayingBlack:
\r
11922 case IcsObserving:
\r
11923 case IcsExamining:
\r
11926 case PlayFromGameFile:
\r
11927 gameInfo.event = StrSave("Game from non-PGN file");
\r
11928 gameInfo.site = StrSave(HostName());
\r
11929 gameInfo.date = PGNDate();
\r
11930 gameInfo.round = StrSave("-");
\r
11931 gameInfo.white = StrSave("?");
\r
11932 gameInfo.black = StrSave("?");
\r
11941 ReplaceComment(index, text)
\r
11947 while (*text == '\n') text++;
\r
11948 len = strlen(text);
\r
11949 while (len > 0 && text[len - 1] == '\n') len--;
\r
11951 if (commentList[index] != NULL)
\r
11952 free(commentList[index]);
\r
11955 commentList[index] = NULL;
\r
11958 commentList[index] = (char *) malloc(len + 2);
\r
11959 strncpy(commentList[index], text, len);
\r
11960 commentList[index][len] = '\n';
\r
11961 commentList[index][len + 1] = NULLCHAR;
\r
11974 if (ch == '\r') continue;
\r
11976 } while (ch != '\0');
\r
11980 AppendComment(index, text)
\r
11987 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11990 while (*text == '\n') text++;
\r
11991 len = strlen(text);
\r
11992 while (len > 0 && text[len - 1] == '\n') len--;
\r
11994 if (len == 0) return;
\r
11996 if (commentList[index] != NULL) {
\r
11997 old = commentList[index];
\r
11998 oldlen = strlen(old);
\r
11999 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
12000 strcpy(commentList[index], old);
\r
12002 strncpy(&commentList[index][oldlen], text, len);
\r
12003 commentList[index][oldlen + len] = '\n';
\r
12004 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
12006 commentList[index] = (char *) malloc(len + 2);
\r
12007 strncpy(commentList[index], text, len);
\r
12008 commentList[index][len] = '\n';
\r
12009 commentList[index][len + 1] = NULLCHAR;
\r
12013 static char * FindStr( char * text, char * sub_text )
\r
12015 char * result = strstr( text, sub_text );
\r
12017 if( result != NULL ) {
\r
12018 result += strlen( sub_text );
\r
12024 /* [AS] Try to extract PV info from PGN comment */
\r
12025 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
12026 char *GetInfoFromComment( int index, char * text )
\r
12028 char * sep = text;
\r
12030 if( text != NULL && index > 0 ) {
\r
12033 int time = -1, sec = 0, deci;
\r
12034 char * s_eval = FindStr( text, "[%eval " );
\r
12035 char * s_emt = FindStr( text, "[%emt " );
\r
12037 if( s_eval != NULL || s_emt != NULL ) {
\r
12041 if( s_eval != NULL ) {
\r
12042 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
12046 if( delim != ']' ) {
\r
12051 if( s_emt != NULL ) {
\r
12055 /* We expect something like: [+|-]nnn.nn/dd */
\r
12056 int score_lo = 0;
\r
12058 sep = strchr( text, '/' );
\r
12059 if( sep == NULL || sep < (text+4) ) {
\r
12063 time = -1; sec = -1; deci = -1;
\r
12064 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
12065 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12066 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12067 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12071 if( score_lo < 0 || score_lo >= 100 ) {
\r
12075 if(sec >= 0) time = 600*time + 10*sec; else
\r
12076 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12078 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12080 /* [HGM] PV time: now locate end of PV info */
\r
12081 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12083 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12085 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12087 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12088 while(*sep == ' ') sep++;
\r
12091 if( depth <= 0 ) {
\r
12099 pvInfoList[index-1].depth = depth;
\r
12100 pvInfoList[index-1].score = score;
\r
12101 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12107 SendToProgram(message, cps)
\r
12109 ChessProgramState *cps;
\r
12111 int count, outCount, error;
\r
12112 char buf[MSG_SIZ];
\r
12114 if (cps->pr == NULL) return;
\r
12117 if (appData.debugMode) {
\r
12119 GetTimeMark(&now);
\r
12120 fprintf(debugFP, "%ld >%-6s: %s",
\r
12121 SubtractTimeMarks(&now, &programStartTime),
\r
12122 cps->which, message);
\r
12125 count = strlen(message);
\r
12126 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12127 if (outCount < count && !exiting
\r
12128 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12129 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12130 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12131 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12132 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12133 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12135 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12137 gameInfo.resultDetails = buf;
\r
12139 DisplayFatalError(buf, error, 1);
\r
12144 ReceiveFromProgram(isr, closure, message, count, error)
\r
12145 InputSourceRef isr;
\r
12146 VOIDSTAR closure;
\r
12152 char buf[MSG_SIZ];
\r
12153 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12155 if (isr != cps->isr) return; /* Killed intentionally */
\r
12156 if (count <= 0) {
\r
12157 if (count == 0) {
\r
12159 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12160 cps->which, cps->program);
\r
12161 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12162 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12163 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12164 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12166 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12168 gameInfo.resultDetails = buf;
\r
12170 RemoveInputSource(cps->isr);
\r
12171 DisplayFatalError(buf, 0, 1);
\r
12174 _("Error reading from %s chess program (%s)"),
\r
12175 cps->which, cps->program);
\r
12176 RemoveInputSource(cps->isr);
\r
12178 /* [AS] Program is misbehaving badly... kill it */
\r
12179 if( count == -2 ) {
\r
12180 DestroyChildProcess( cps->pr, 9 );
\r
12181 cps->pr = NoProc;
\r
12184 DisplayFatalError(buf, error, 1);
\r
12189 if ((end_str = strchr(message, '\r')) != NULL)
\r
12190 *end_str = NULLCHAR;
\r
12191 if ((end_str = strchr(message, '\n')) != NULL)
\r
12192 *end_str = NULLCHAR;
\r
12194 if (appData.debugMode) {
\r
12195 TimeMark now; int print = 1;
\r
12196 char *quote = ""; char c; int i;
\r
12198 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12199 char start = message[0];
\r
12200 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12201 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12202 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12203 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12204 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12205 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12206 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12207 { quote = "# "; print = (appData.engineComments == 2); }
\r
12208 message[0] = start; // restore original message
\r
12211 GetTimeMark(&now);
\r
12212 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12213 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12219 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12220 if (appData.icsEngineAnalyze) {
\r
12221 if (strstr(message, "whisper") != NULL ||
\r
12222 strstr(message, "kibitz") != NULL ||
\r
12223 strstr(message, "tellics") != NULL) return;
\r
12226 HandleMachineMove(message, cps);
\r
12231 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12232 ChessProgramState *cps;
\r
12233 int mps, inc, sd, st;
\r
12236 char buf[MSG_SIZ];
\r
12239 if( timeControl_2 > 0 ) {
\r
12240 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12241 tc = timeControl_2;
\r
12244 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12245 inc /= cps->timeOdds;
\r
12246 st /= cps->timeOdds;
\r
12248 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12251 /* Set exact time per move, normally using st command */
\r
12252 if (cps->stKludge) {
\r
12253 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12254 seconds = st % 60;
\r
12255 if (seconds == 0) {
\r
12256 sprintf(buf, "level 1 %d\n", st/60);
\r
12258 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12261 sprintf(buf, "st %d\n", st);
\r
12264 /* Set conventional or incremental time control, using level command */
\r
12265 if (seconds == 0) {
\r
12266 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12267 Fixed in later versions, but still avoid :seconds
\r
12268 when seconds is 0. */
\r
12269 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12271 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12272 seconds, inc/1000);
\r
12275 SendToProgram(buf, cps);
\r
12277 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12278 /* Orthogonally, limit search to given depth */
\r
12280 if (cps->sdKludge) {
\r
12281 sprintf(buf, "depth\n%d\n", sd);
\r
12283 sprintf(buf, "sd %d\n", sd);
\r
12285 SendToProgram(buf, cps);
\r
12288 if(cps->nps > 0) { /* [HGM] nps */
\r
12289 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12291 sprintf(buf, "nps %d\n", cps->nps);
\r
12292 SendToProgram(buf, cps);
\r
12297 ChessProgramState *WhitePlayer()
\r
12298 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12300 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12301 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12307 SendTimeRemaining(cps, machineWhite)
\r
12308 ChessProgramState *cps;
\r
12309 int /*boolean*/ machineWhite;
\r
12311 char message[MSG_SIZ];
\r
12312 long time, otime;
\r
12314 /* Note: this routine must be called when the clocks are stopped
\r
12315 or when they have *just* been set or switched; otherwise
\r
12316 it will be off by the time since the current tick started.
\r
12318 if (machineWhite) {
\r
12319 time = whiteTimeRemaining / 10;
\r
12320 otime = blackTimeRemaining / 10;
\r
12322 time = blackTimeRemaining / 10;
\r
12323 otime = whiteTimeRemaining / 10;
\r
12325 /* [HGM] translate opponent's time by time-odds factor */
\r
12326 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12327 if (appData.debugMode) {
\r
12328 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12331 if (time <= 0) time = 1;
\r
12332 if (otime <= 0) otime = 1;
\r
12334 sprintf(message, "time %ld\n", time);
\r
12335 SendToProgram(message, cps);
\r
12337 sprintf(message, "otim %ld\n", otime);
\r
12338 SendToProgram(message, cps);
\r
12342 BoolFeature(p, name, loc, cps)
\r
12346 ChessProgramState *cps;
\r
12348 char buf[MSG_SIZ];
\r
12349 int len = strlen(name);
\r
12351 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12353 sscanf(*p, "%d", &val);
\r
12354 *loc = (val != 0);
\r
12355 while (**p && **p != ' ') (*p)++;
\r
12356 sprintf(buf, "accepted %s\n", name);
\r
12357 SendToProgram(buf, cps);
\r
12364 IntFeature(p, name, loc, cps)
\r
12368 ChessProgramState *cps;
\r
12370 char buf[MSG_SIZ];
\r
12371 int len = strlen(name);
\r
12372 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12374 sscanf(*p, "%d", loc);
\r
12375 while (**p && **p != ' ') (*p)++;
\r
12376 sprintf(buf, "accepted %s\n", name);
\r
12377 SendToProgram(buf, cps);
\r
12384 StringFeature(p, name, loc, cps)
\r
12388 ChessProgramState *cps;
\r
12390 char buf[MSG_SIZ];
\r
12391 int len = strlen(name);
\r
12392 if (strncmp((*p), name, len) == 0
\r
12393 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12395 sscanf(*p, "%[^\"]", loc);
\r
12396 while (**p && **p != '\"') (*p)++;
\r
12397 if (**p == '\"') (*p)++;
\r
12398 sprintf(buf, "accepted %s\n", name);
\r
12399 SendToProgram(buf, cps);
\r
12406 ParseOption(Option *opt, ChessProgramState *cps)
\r
12407 // [HGM] options: process the string that defines an engine option, and determine
\r
12408 // name, type, default value, and allowed value range
\r
12410 char *p, *q, buf[MSG_SIZ];
\r
12411 int n, min = (-1)<<31, max = 1<<31, def;
\r
12413 if(p = strstr(opt->name, " -spin ")) {
\r
12414 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12415 if(max < min) max = min; // enforce consistency
\r
12416 if(def < min) def = min;
\r
12417 if(def > max) def = max;
\r
12418 opt->value = def;
\r
12421 opt->type = Spin;
\r
12422 } else if(p = strstr(opt->name, " -string ")) {
\r
12423 opt->textValue = p+9;
\r
12424 opt->type = TextBox;
\r
12425 } else if(p = strstr(opt->name, " -check ")) {
\r
12426 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12427 opt->value = (def != 0);
\r
12428 opt->type = CheckBox;
\r
12429 } else if(p = strstr(opt->name, " -combo ")) {
\r
12430 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12431 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12432 opt->value = n = 0;
\r
12433 while(q = StrStr(q, " /// ")) {
\r
12434 n++; *q = 0; // count choices, and null-terminate each of them
\r
12436 if(*q == '*') { // remember default, which is marked with * prefix
\r
12440 cps->comboList[cps->comboCnt++] = q;
\r
12442 cps->comboList[cps->comboCnt++] = NULL;
\r
12443 opt->max = n + 1;
\r
12444 opt->type = ComboBox;
\r
12445 } else if(p = strstr(opt->name, " -button")) {
\r
12446 opt->type = Button;
\r
12447 } else if(p = strstr(opt->name, " -save")) {
\r
12448 opt->type = SaveButton;
\r
12449 } else return FALSE;
\r
12450 *p = 0; // terminate option name
\r
12451 // now look if the command-line options define a setting for this engine option.
\r
12452 if(cps->optionSettings && cps->optionSettings[0])
\r
12453 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12454 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12455 sprintf(buf, "option %s", p);
\r
12456 if(p = strstr(buf, ",")) *p = 0;
\r
12457 strcat(buf, "\n");
\r
12458 SendToProgram(buf, cps);
\r
12464 FeatureDone(cps, val)
\r
12465 ChessProgramState* cps;
\r
12468 DelayedEventCallback cb = GetDelayedEvent();
\r
12469 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12470 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12471 CancelDelayedEvent();
\r
12472 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12474 cps->initDone = val;
\r
12477 /* Parse feature command from engine */
\r
12479 ParseFeatures(args, cps)
\r
12481 ChessProgramState *cps;
\r
12486 char buf[MSG_SIZ];
\r
12489 while (*p == ' ') p++;
\r
12490 if (*p == NULLCHAR) return;
\r
12492 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12493 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12494 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12495 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12496 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12497 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12498 /* Engine can disable reuse, but can't enable it if user said no */
\r
12499 if (!val) cps->reuse = FALSE;
\r
12502 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12503 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12504 if (gameMode == TwoMachinesPlay) {
\r
12505 DisplayTwoMachinesTitle();
\r
12507 DisplayTitle("");
\r
12511 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12512 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12513 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12514 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12515 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12516 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12517 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12518 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12519 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12520 if (IntFeature(&p, "done", &val, cps)) {
\r
12521 FeatureDone(cps, val);
\r
12524 /* Added by Tord: */
\r
12525 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12526 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12527 /* End of additions by Tord */
\r
12529 /* [HGM] added features: */
\r
12530 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12531 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12532 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12533 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12534 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12535 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12536 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12537 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12538 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12539 cps->nrOptions--;
\r
12540 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12541 DisplayError(buf, 0);
\r
12545 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12546 /* End of additions by HGM */
\r
12548 /* unknown feature: complain and skip */
\r
12550 while (*q && *q != '=') q++;
\r
12551 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12552 SendToProgram(buf, cps);
\r
12556 if (*p == '\"') {
\r
12558 while (*p && *p != '\"') p++;
\r
12559 if (*p == '\"') p++;
\r
12561 while (*p && *p != ' ') p++;
\r
12569 PeriodicUpdatesEvent(newState)
\r
12572 if (newState == appData.periodicUpdates)
\r
12575 appData.periodicUpdates=newState;
\r
12577 /* Display type changes, so update it now */
\r
12578 DisplayAnalysis();
\r
12580 /* Get the ball rolling again... */
\r
12582 AnalysisPeriodicEvent(1);
\r
12583 StartAnalysisClock();
\r
12588 PonderNextMoveEvent(newState)
\r
12591 if (newState == appData.ponderNextMove) return;
\r
12592 if (gameMode == EditPosition) EditPositionDone();
\r
12594 SendToProgram("hard\n", &first);
\r
12595 if (gameMode == TwoMachinesPlay) {
\r
12596 SendToProgram("hard\n", &second);
\r
12599 SendToProgram("easy\n", &first);
\r
12600 thinkOutput[0] = NULLCHAR;
\r
12601 if (gameMode == TwoMachinesPlay) {
\r
12602 SendToProgram("easy\n", &second);
\r
12605 appData.ponderNextMove = newState;
\r
12609 NewSettingEvent(option, command, value)
\r
12611 int option, value;
\r
12613 char buf[MSG_SIZ];
\r
12615 if (gameMode == EditPosition) EditPositionDone();
\r
12616 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12617 SendToProgram(buf, &first);
\r
12618 if (gameMode == TwoMachinesPlay) {
\r
12619 SendToProgram(buf, &second);
\r
12624 ShowThinkingEvent()
\r
12625 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12627 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12628 int newState = appData.showThinking
\r
12629 // [HGM] thinking: other features now need thinking output as well
\r
12630 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12632 if (oldState == newState) return;
\r
12633 oldState = newState;
\r
12634 if (gameMode == EditPosition) EditPositionDone();
\r
12636 SendToProgram("post\n", &first);
\r
12637 if (gameMode == TwoMachinesPlay) {
\r
12638 SendToProgram("post\n", &second);
\r
12641 SendToProgram("nopost\n", &first);
\r
12642 thinkOutput[0] = NULLCHAR;
\r
12643 if (gameMode == TwoMachinesPlay) {
\r
12644 SendToProgram("nopost\n", &second);
\r
12647 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12651 AskQuestionEvent(title, question, replyPrefix, which)
\r
12652 char *title; char *question; char *replyPrefix; char *which;
\r
12654 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12655 if (pr == NoProc) return;
\r
12656 AskQuestion(title, question, replyPrefix, pr);
\r
12660 DisplayMove(moveNumber)
\r
12663 char message[MSG_SIZ];
\r
12664 char res[MSG_SIZ];
\r
12665 char cpThinkOutput[MSG_SIZ];
\r
12667 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12669 if (moveNumber == forwardMostMove - 1 ||
\r
12670 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12672 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12674 if (strchr(cpThinkOutput, '\n')) {
\r
12675 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12678 *cpThinkOutput = NULLCHAR;
\r
12681 /* [AS] Hide thinking from human user */
\r
12682 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12683 *cpThinkOutput = NULLCHAR;
\r
12684 if( thinkOutput[0] != NULLCHAR ) {
\r
12687 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12688 cpThinkOutput[i] = '.';
\r
12690 cpThinkOutput[i] = NULLCHAR;
\r
12691 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12695 if (moveNumber == forwardMostMove - 1 &&
\r
12696 gameInfo.resultDetails != NULL) {
\r
12697 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12698 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12700 sprintf(res, " {%s} %s",
\r
12701 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12704 res[0] = NULLCHAR;
\r
12707 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12708 DisplayMessage(res, cpThinkOutput);
\r
12710 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12711 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12712 parseList[moveNumber], res);
\r
12713 DisplayMessage(message, cpThinkOutput);
\r
12718 DisplayAnalysisText(text)
\r
12721 char buf[MSG_SIZ];
\r
12723 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12724 || appData.icsEngineAnalyze) {
\r
12725 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12726 AnalysisPopUp(buf, text);
\r
12731 only_one_move(str)
\r
12734 while (*str && isspace(*str)) ++str;
\r
12735 while (*str && !isspace(*str)) ++str;
\r
12736 if (!*str) return 1;
\r
12737 while (*str && isspace(*str)) ++str;
\r
12738 if (!*str) return 1;
\r
12743 DisplayAnalysis()
\r
12745 char buf[MSG_SIZ];
\r
12746 char lst[MSG_SIZ / 2];
\r
12748 static char *xtra[] = { "", " (--)", " (++)" };
\r
12751 if (programStats.time == 0) {
\r
12752 programStats.time = 1;
\r
12755 if (programStats.got_only_move) {
\r
12756 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12758 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12760 nps = (u64ToDouble(programStats.nodes) /
\r
12761 ((double)programStats.time /100.0));
\r
12763 cs = programStats.time % 100;
\r
12764 s = programStats.time / 100;
\r
12765 h = (s / (60*60));
\r
12770 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12771 if (programStats.move_name[0] != NULLCHAR) {
\r
12772 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12773 programStats.depth,
\r
12774 programStats.nr_moves-programStats.moves_left,
\r
12775 programStats.nr_moves, programStats.move_name,
\r
12776 ((float)programStats.score)/100.0, lst,
\r
12777 only_one_move(lst)?
\r
12778 xtra[programStats.got_fail] : "",
\r
12779 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12781 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12782 programStats.depth,
\r
12783 programStats.nr_moves-programStats.moves_left,
\r
12784 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12786 only_one_move(lst)?
\r
12787 xtra[programStats.got_fail] : "",
\r
12788 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12791 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12792 programStats.depth,
\r
12793 ((float)programStats.score)/100.0,
\r
12795 only_one_move(lst)?
\r
12796 xtra[programStats.got_fail] : "",
\r
12797 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12800 DisplayAnalysisText(buf);
\r
12804 DisplayComment(moveNumber, text)
\r
12808 char title[MSG_SIZ];
\r
12809 char buf[8000]; // comment can be long!
\r
12810 int score, depth;
\r
12812 if( appData.autoDisplayComment ) {
\r
12813 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12814 strcpy(title, "Comment");
\r
12816 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12817 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12818 parseList[moveNumber]);
\r
12820 } else title[0] = 0;
\r
12822 // [HGM] PV info: display PV info together with (or as) comment
\r
12823 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12824 if(text == NULL) text = "";
\r
12825 score = pvInfoList[moveNumber].score;
\r
12826 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12827 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12828 CommentPopUp(title, buf);
\r
12830 if (text != NULL)
\r
12831 CommentPopUp(title, text);
\r
12834 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12835 * might be busy thinking or pondering. It can be omitted if your
\r
12836 * gnuchess is configured to stop thinking immediately on any user
\r
12837 * input. However, that gnuchess feature depends on the FIONREAD
\r
12838 * ioctl, which does not work properly on some flavors of Unix.
\r
12842 ChessProgramState *cps;
\r
12845 if (!cps->useSigint) return;
\r
12846 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12847 switch (gameMode) {
\r
12848 case MachinePlaysWhite:
\r
12849 case MachinePlaysBlack:
\r
12850 case TwoMachinesPlay:
\r
12851 case IcsPlayingWhite:
\r
12852 case IcsPlayingBlack:
\r
12853 case AnalyzeMode:
\r
12854 case AnalyzeFile:
\r
12855 /* Skip if we know it isn't thinking */
\r
12856 if (!cps->maybeThinking) return;
\r
12857 if (appData.debugMode)
\r
12858 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12859 InterruptChildProcess(cps->pr);
\r
12860 cps->maybeThinking = FALSE;
\r
12865 #endif /*ATTENTION*/
\r
12871 if (whiteTimeRemaining <= 0) {
\r
12872 if (!whiteFlag) {
\r
12873 whiteFlag = TRUE;
\r
12874 if (appData.icsActive) {
\r
12875 if (appData.autoCallFlag &&
\r
12876 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12877 SendToICS(ics_prefix);
\r
12878 SendToICS("flag\n");
\r
12882 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12884 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12885 if (appData.autoCallFlag) {
\r
12886 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12893 if (blackTimeRemaining <= 0) {
\r
12894 if (!blackFlag) {
\r
12895 blackFlag = TRUE;
\r
12896 if (appData.icsActive) {
\r
12897 if (appData.autoCallFlag &&
\r
12898 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12899 SendToICS(ics_prefix);
\r
12900 SendToICS("flag\n");
\r
12904 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12906 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12907 if (appData.autoCallFlag) {
\r
12908 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12919 CheckTimeControl()
\r
12921 if (!appData.clockMode || appData.icsActive ||
\r
12922 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12925 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12927 if ( !WhiteOnMove(forwardMostMove) )
\r
12928 /* White made time control */
\r
12929 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12930 /* [HGM] time odds: correct new time quota for time odds! */
\r
12931 / WhitePlayer()->timeOdds;
\r
12933 /* Black made time control */
\r
12934 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12935 / WhitePlayer()->other->timeOdds;
\r
12939 DisplayBothClocks()
\r
12941 int wom = gameMode == EditPosition ?
\r
12942 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12943 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12944 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12948 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12949 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12950 to use other calls if you don't. Clocks will be less accurate if
\r
12951 you have neither ftime nor gettimeofday.
\r
12954 /* VS 2008 requires the #include outside of the function */
\r
12955 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12956 #include <sys/timeb.h>
\r
12959 /* Get the current time as a TimeMark */
\r
12964 #if HAVE_GETTIMEOFDAY
\r
12966 struct timeval timeVal;
\r
12967 struct timezone timeZone;
\r
12969 gettimeofday(&timeVal, &timeZone);
\r
12970 tm->sec = (long) timeVal.tv_sec;
\r
12971 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12973 #else /*!HAVE_GETTIMEOFDAY*/
\r
12976 // include <sys/timeb.h> / moved to just above start of function
\r
12977 struct timeb timeB;
\r
12980 tm->sec = (long) timeB.time;
\r
12981 tm->ms = (int) timeB.millitm;
\r
12983 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12984 tm->sec = (long) time(NULL);
\r
12990 /* Return the difference in milliseconds between two
\r
12991 time marks. We assume the difference will fit in a long!
\r
12994 SubtractTimeMarks(tm2, tm1)
\r
12995 TimeMark *tm2, *tm1;
\r
12997 return 1000L*(tm2->sec - tm1->sec) +
\r
12998 (long) (tm2->ms - tm1->ms);
\r
13003 * Code to manage the game clocks.
\r
13005 * In tournament play, black starts the clock and then white makes a move.
\r
13006 * We give the human user a slight advantage if he is playing white---the
\r
13007 * clocks don't run until he makes his first move, so it takes zero time.
\r
13008 * Also, we don't account for network lag, so we could get out of sync
\r
13009 * with GNU Chess's clock -- but then, referees are always right.
\r
13012 static TimeMark tickStartTM;
\r
13013 static long intendedTickLength;
\r
13016 NextTickLength(timeRemaining)
\r
13017 long timeRemaining;
\r
13019 long nominalTickLength, nextTickLength;
\r
13021 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
13022 nominalTickLength = 100L;
\r
13024 nominalTickLength = 1000L;
\r
13025 nextTickLength = timeRemaining % nominalTickLength;
\r
13026 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
13028 return nextTickLength;
\r
13031 /* Adjust clock one minute up or down */
\r
13033 AdjustClock(Boolean which, int dir)
\r
13035 if(which) blackTimeRemaining += 60000*dir;
\r
13036 else whiteTimeRemaining += 60000*dir;
\r
13037 DisplayBothClocks();
\r
13040 /* Stop clocks and reset to a fresh time control */
\r
13044 (void) StopClockTimer();
\r
13045 if (appData.icsActive) {
\r
13046 whiteTimeRemaining = blackTimeRemaining = 0;
\r
13047 } else { /* [HGM] correct new time quote for time odds */
\r
13048 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
13049 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
13051 if (whiteFlag || blackFlag) {
\r
13052 DisplayTitle("");
\r
13053 whiteFlag = blackFlag = FALSE;
\r
13055 DisplayBothClocks();
\r
13058 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
13060 /* Decrement running clock by amount of time that has passed */
\r
13062 DecrementClocks()
\r
13064 long timeRemaining;
\r
13065 long lastTickLength, fudge;
\r
13068 if (!appData.clockMode) return;
\r
13069 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13071 GetTimeMark(&now);
\r
13073 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13075 /* Fudge if we woke up a little too soon */
\r
13076 fudge = intendedTickLength - lastTickLength;
\r
13077 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13079 if (WhiteOnMove(forwardMostMove)) {
\r
13080 if(whiteNPS >= 0) lastTickLength = 0;
\r
13081 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13082 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13083 WhiteOnMove(currentMove));
\r
13085 if(blackNPS >= 0) lastTickLength = 0;
\r
13086 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13087 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13088 !WhiteOnMove(currentMove));
\r
13091 if (CheckFlags()) return;
\r
13093 tickStartTM = now;
\r
13094 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13095 StartClockTimer(intendedTickLength);
\r
13097 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13098 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13099 * with increment) the time remaining has increased to a level above the
\r
13100 * threshold, reset the alarm so it can sound again.
\r
13103 if (appData.icsActive && appData.icsAlarm) {
\r
13105 /* make sure we are dealing with the user's clock */
\r
13106 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13107 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13110 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13111 alarmSounded = FALSE;
\r
13112 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13113 PlayAlarmSound();
\r
13114 alarmSounded = TRUE;
\r
13120 /* A player has just moved, so stop the previously running
\r
13121 clock and (if in clock mode) start the other one.
\r
13122 We redisplay both clocks in case we're in ICS mode, because
\r
13123 ICS gives us an update to both clocks after every move.
\r
13124 Note that this routine is called *after* forwardMostMove
\r
13125 is updated, so the last fractional tick must be subtracted
\r
13126 from the color that is *not* on move now.
\r
13131 long lastTickLength;
\r
13133 int flagged = FALSE;
\r
13135 GetTimeMark(&now);
\r
13137 if (StopClockTimer() && appData.clockMode) {
\r
13138 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13139 if (WhiteOnMove(forwardMostMove)) {
\r
13140 if(blackNPS >= 0) lastTickLength = 0;
\r
13141 blackTimeRemaining -= lastTickLength;
\r
13142 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13143 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13144 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13145 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13147 if(whiteNPS >= 0) lastTickLength = 0;
\r
13148 whiteTimeRemaining -= lastTickLength;
\r
13149 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13150 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13151 pvInfoList[forwardMostMove-1].time =
\r
13152 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13154 flagged = CheckFlags();
\r
13156 CheckTimeControl();
\r
13158 if (flagged || !appData.clockMode) return;
\r
13160 switch (gameMode) {
\r
13161 case MachinePlaysBlack:
\r
13162 case MachinePlaysWhite:
\r
13163 case BeginningOfGame:
\r
13164 if (pausing) return;
\r
13168 case PlayFromGameFile:
\r
13169 case IcsExamining:
\r
13176 tickStartTM = now;
\r
13177 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13178 whiteTimeRemaining : blackTimeRemaining);
\r
13179 StartClockTimer(intendedTickLength);
\r
13183 /* Stop both clocks */
\r
13187 long lastTickLength;
\r
13190 if (!StopClockTimer()) return;
\r
13191 if (!appData.clockMode) return;
\r
13193 GetTimeMark(&now);
\r
13195 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13196 if (WhiteOnMove(forwardMostMove)) {
\r
13197 if(whiteNPS >= 0) lastTickLength = 0;
\r
13198 whiteTimeRemaining -= lastTickLength;
\r
13199 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13201 if(blackNPS >= 0) lastTickLength = 0;
\r
13202 blackTimeRemaining -= lastTickLength;
\r
13203 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13208 /* Start clock of player on move. Time may have been reset, so
\r
13209 if clock is already running, stop and restart it. */
\r
13213 (void) StopClockTimer(); /* in case it was running already */
\r
13214 DisplayBothClocks();
\r
13215 if (CheckFlags()) return;
\r
13217 if (!appData.clockMode) return;
\r
13218 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13220 GetTimeMark(&tickStartTM);
\r
13221 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13222 whiteTimeRemaining : blackTimeRemaining);
\r
13224 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13225 whiteNPS = blackNPS = -1;
\r
13226 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13227 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13228 whiteNPS = first.nps;
\r
13229 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13230 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13231 blackNPS = first.nps;
\r
13232 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13233 whiteNPS = second.nps;
\r
13234 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13235 blackNPS = second.nps;
\r
13236 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13238 StartClockTimer(intendedTickLength);
\r
13245 long second, minute, hour, day;
\r
13247 static char buf[32];
\r
13249 if (ms > 0 && ms <= 9900) {
\r
13250 /* convert milliseconds to tenths, rounding up */
\r
13251 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13253 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13257 /* convert milliseconds to seconds, rounding up */
\r
13258 /* use floating point to avoid strangeness of integer division
\r
13259 with negative dividends on many machines */
\r
13260 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13262 if (second < 0) {
\r
13264 second = -second;
\r
13267 day = second / (60 * 60 * 24);
\r
13268 second = second % (60 * 60 * 24);
\r
13269 hour = second / (60 * 60);
\r
13270 second = second % (60 * 60);
\r
13271 minute = second / 60;
\r
13272 second = second % 60;
\r
13275 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13276 sign, day, hour, minute, second);
\r
13277 else if (hour > 0)
\r
13278 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13280 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13287 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13290 StrStr(string, match)
\r
13291 char *string, *match;
\r
13295 length = strlen(match);
\r
13297 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13298 if (!strncmp(match, string, length))
\r
13305 StrCaseStr(string, match)
\r
13306 char *string, *match;
\r
13308 int i, j, length;
\r
13310 length = strlen(match);
\r
13312 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13313 for (j = 0; j < length; j++) {
\r
13314 if (ToLower(match[j]) != ToLower(string[j]))
\r
13317 if (j == length) return string;
\r
13323 #ifndef _amigados
\r
13325 StrCaseCmp(s1, s2)
\r
13331 c1 = ToLower(*s1++);
\r
13332 c2 = ToLower(*s2++);
\r
13333 if (c1 > c2) return 1;
\r
13334 if (c1 < c2) return -1;
\r
13335 if (c1 == NULLCHAR) return 0;
\r
13344 return isupper(c) ? tolower(c) : c;
\r
13352 return islower(c) ? toupper(c) : c;
\r
13354 #endif /* !_amigados */
\r
13362 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13369 StrSavePtr(s, savePtr)
\r
13370 char *s, **savePtr;
\r
13375 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13376 strcpy(*savePtr, s);
\r
13378 return(*savePtr);
\r
13386 char buf[MSG_SIZ];
\r
13388 clock = time((time_t *)NULL);
\r
13389 tm = localtime(&clock);
\r
13390 sprintf(buf, "%04d.%02d.%02d",
\r
13391 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13392 return StrSave(buf);
\r
13397 PositionToFEN(move, useFEN960)
\r
13401 int i, j, fromX, fromY, toX, toY;
\r
13406 ChessSquare piece;
\r
13408 whiteToPlay = (gameMode == EditPosition) ?
\r
13409 !blackPlaysFirst : (move % 2 == 0);
\r
13412 /* Piece placement data */
\r
13413 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13415 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13416 if (boards[move][i][j] == EmptySquare) {
\r
13418 } else { ChessSquare piece = boards[move][i][j];
\r
13419 if (emptycount > 0) {
\r
13420 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13421 *p++ = '0' + emptycount;
\r
13422 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13425 if(PieceToChar(piece) == '+') {
\r
13426 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13428 piece = (ChessSquare)(DEMOTED piece);
\r
13430 *p++ = PieceToChar(piece);
\r
13431 if(p[-1] == '~') {
\r
13432 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13433 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13438 if (emptycount > 0) {
\r
13439 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13440 *p++ = '0' + emptycount;
\r
13441 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13448 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13449 if( gameInfo.holdingsWidth ) {
\r
13450 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13452 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13453 piece = boards[move][i][BOARD_WIDTH-1];
\r
13454 if( piece != EmptySquare )
\r
13455 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13456 *p++ = PieceToChar(piece);
\r
13458 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13459 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13460 if( piece != EmptySquare )
\r
13461 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13462 *p++ = PieceToChar(piece);
\r
13465 if( q == p ) *p++ = '-';
\r
13470 /* Active color */
\r
13471 *p++ = whiteToPlay ? 'w' : 'b';
\r
13474 if(nrCastlingRights) {
\r
13476 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13477 /* [HGM] write directly from rights */
\r
13478 if(castlingRights[move][2] >= 0 &&
\r
13479 castlingRights[move][0] >= 0 )
\r
13480 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13481 if(castlingRights[move][2] >= 0 &&
\r
13482 castlingRights[move][1] >= 0 )
\r
13483 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13484 if(castlingRights[move][5] >= 0 &&
\r
13485 castlingRights[move][3] >= 0 )
\r
13486 *p++ = castlingRights[move][3] + AAA;
\r
13487 if(castlingRights[move][5] >= 0 &&
\r
13488 castlingRights[move][4] >= 0 )
\r
13489 *p++ = castlingRights[move][4] + AAA;
\r
13492 /* [HGM] write true castling rights */
\r
13493 if( nrCastlingRights == 6 ) {
\r
13494 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13495 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13496 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13497 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13498 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13499 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13500 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13501 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13504 if (q == p) *p++ = '-'; /* No castling rights */
\r
13508 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13509 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13510 /* En passant target square */
\r
13511 if (move > backwardMostMove) {
\r
13512 fromX = moveList[move - 1][0] - AAA;
\r
13513 fromY = moveList[move - 1][1] - ONE;
\r
13514 toX = moveList[move - 1][2] - AAA;
\r
13515 toY = moveList[move - 1][3] - ONE;
\r
13516 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13517 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13518 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13520 /* 2-square pawn move just happened */
\r
13521 *p++ = toX + AAA;
\r
13522 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13532 /* [HGM] find reversible plies */
\r
13533 { int i = 0, j=move;
\r
13535 if (appData.debugMode) { int k;
\r
13536 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13537 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13538 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13542 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13543 if( j == backwardMostMove ) i += initialRulePlies;
\r
13544 sprintf(p, "%d ", i);
\r
13545 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13547 /* Fullmove number */
\r
13548 sprintf(p, "%d", (move / 2) + 1);
\r
13550 return StrSave(buf);
\r
13554 ParseFEN(board, blackPlaysFirst, fen)
\r
13556 int *blackPlaysFirst;
\r
13562 ChessSquare piece;
\r
13566 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13567 if(gameInfo.holdingsWidth) {
\r
13568 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13569 board[i][0] = EmptySquare; /* black holdings */
\r
13570 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13571 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13572 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13576 /* Piece placement data */
\r
13577 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13580 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13581 if (*p == '/') p++;
\r
13582 emptycount = gameInfo.boardWidth - j;
\r
13583 while (emptycount--)
\r
13584 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13586 #if(BOARD_SIZE >= 10)
\r
13587 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13588 p++; emptycount=10;
\r
13589 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13590 while (emptycount--)
\r
13591 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13593 } else if (isdigit(*p)) {
\r
13594 emptycount = *p++ - '0';
\r
13595 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13596 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13597 while (emptycount--)
\r
13598 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13599 } else if (*p == '+' || isalpha(*p)) {
\r
13600 if (j >= gameInfo.boardWidth) return FALSE;
\r
13602 piece = CharToPiece(*++p);
\r
13603 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13604 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13605 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13606 } else piece = CharToPiece(*p++);
\r
13608 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13609 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13610 piece = (ChessSquare) (PROMOTED piece);
\r
13611 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13614 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13620 while (*p == '/' || *p == ' ') p++;
\r
13622 /* [HGM] look for Crazyhouse holdings here */
\r
13623 while(*p==' ') p++;
\r
13624 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13625 if(*p == '[') p++;
\r
13626 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13627 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13628 /* if we would allow FEN reading to set board size, we would */
\r
13629 /* have to add holdings and shift the board read so far here */
\r
13630 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13632 if((int) piece >= (int) BlackPawn ) {
\r
13633 i = (int)piece - (int)BlackPawn;
\r
13634 i = PieceToNumber((ChessSquare)i);
\r
13635 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13636 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13637 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13639 i = (int)piece - (int)WhitePawn;
\r
13640 i = PieceToNumber((ChessSquare)i);
\r
13641 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13642 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13643 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13647 if(*p == ']') *p++;
\r
13650 while(*p == ' ') p++;
\r
13652 /* Active color */
\r
13655 *blackPlaysFirst = FALSE;
\r
13658 *blackPlaysFirst = TRUE;
\r
13664 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13665 /* return the extra info in global variiables */
\r
13667 /* set defaults in case FEN is incomplete */
\r
13668 FENepStatus = EP_UNKNOWN;
\r
13669 for(i=0; i<nrCastlingRights; i++ ) {
\r
13670 FENcastlingRights[i] =
\r
13671 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13672 } /* assume possible unless obviously impossible */
\r
13673 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13674 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13675 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13676 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13677 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13678 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13679 FENrulePlies = 0;
\r
13681 while(*p==' ') p++;
\r
13682 if(nrCastlingRights) {
\r
13683 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13684 /* castling indicator present, so default becomes no castlings */
\r
13685 for(i=0; i<nrCastlingRights; i++ ) {
\r
13686 FENcastlingRights[i] = -1;
\r
13689 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13690 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13691 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13692 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13693 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13695 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13696 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13697 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13701 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13702 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13703 FENcastlingRights[2] = whiteKingFile;
\r
13706 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13707 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13708 FENcastlingRights[2] = whiteKingFile;
\r
13711 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13712 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13713 FENcastlingRights[5] = blackKingFile;
\r
13716 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13717 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13718 FENcastlingRights[5] = blackKingFile;
\r
13721 default: /* FRC castlings */
\r
13722 if(c >= 'a') { /* black rights */
\r
13723 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13724 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13725 if(i == BOARD_RGHT) break;
\r
13726 FENcastlingRights[5] = i;
\r
13728 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13729 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13731 FENcastlingRights[3] = c;
\r
13733 FENcastlingRights[4] = c;
\r
13734 } else { /* white rights */
\r
13735 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13736 if(board[0][i] == WhiteKing) break;
\r
13737 if(i == BOARD_RGHT) break;
\r
13738 FENcastlingRights[2] = i;
\r
13739 c -= AAA - 'a' + 'A';
\r
13740 if(board[0][c] >= WhiteKing) break;
\r
13742 FENcastlingRights[0] = c;
\r
13744 FENcastlingRights[1] = c;
\r
13748 if (appData.debugMode) {
\r
13749 fprintf(debugFP, "FEN castling rights:");
\r
13750 for(i=0; i<nrCastlingRights; i++)
\r
13751 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13752 fprintf(debugFP, "\n");
\r
13755 while(*p==' ') p++;
\r
13758 /* read e.p. field in games that know e.p. capture */
\r
13759 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13760 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13762 p++; FENepStatus = EP_NONE;
\r
13764 char c = *p++ - AAA;
\r
13766 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13767 if(*p >= '0' && *p <='9') *p++;
\r
13773 if(sscanf(p, "%d", &i) == 1) {
\r
13774 FENrulePlies = i; /* 50-move ply counter */
\r
13775 /* (The move number is still ignored) */
\r
13782 EditPositionPasteFEN(char *fen)
\r
13784 if (fen != NULL) {
\r
13785 Board initial_position;
\r
13787 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13788 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13791 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13792 EditPositionEvent();
\r
13793 blackPlaysFirst = savedBlackPlaysFirst;
\r
13794 CopyBoard(boards[0], initial_position);
\r
13795 /* [HGM] copy FEN attributes as well */
\r
13797 initialRulePlies = FENrulePlies;
\r
13798 epStatus[0] = FENepStatus;
\r
13799 for( i=0; i<nrCastlingRights; i++ )
\r
13800 castlingRights[0][i] = FENcastlingRights[i];
\r
13802 EditPositionDone();
\r
13803 DisplayBothClocks();
\r
13804 DrawPosition(FALSE, boards[currentMove]);
\r