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
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
\r
624 ClearProgramStats();
\r
625 programStats.ok_to_send = 1;
\r
626 programStats.seen_stat = 0;
\r
629 * Initialize game list
\r
631 ListNew(&gameList);
\r
635 * Internet chess server status
\r
637 if (appData.icsActive) {
\r
638 appData.matchMode = FALSE;
\r
639 appData.matchGames = 0;
\r
641 appData.noChessProgram = !appData.zippyPlay;
\r
643 appData.zippyPlay = FALSE;
\r
644 appData.zippyTalk = FALSE;
\r
645 appData.noChessProgram = TRUE;
\r
647 if (*appData.icsHelper != NULLCHAR) {
\r
648 appData.useTelnet = TRUE;
\r
649 appData.telnetProgram = appData.icsHelper;
\r
652 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
655 /* [AS] Initialize pv info list [HGM] and game state */
\r
659 for( i=0; i<MAX_MOVES; i++ ) {
\r
660 pvInfoList[i].depth = -1;
\r
661 epStatus[i]=EP_NONE;
\r
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
667 * Parse timeControl resource
\r
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
670 appData.movesPerSession)) {
\r
672 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
673 DisplayFatalError(buf, 0, 2);
\r
677 * Parse searchTime resource
\r
679 if (*appData.searchTime != NULLCHAR) {
\r
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
681 if (matched == 1) {
\r
682 searchTime = min * 60;
\r
683 } else if (matched == 2) {
\r
684 searchTime = min * 60 + sec;
\r
687 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
688 DisplayFatalError(buf, 0, 2);
\r
692 /* [AS] Adjudication threshold */
\r
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
695 first.which = "first";
\r
696 second.which = "second";
\r
697 first.maybeThinking = second.maybeThinking = FALSE;
\r
698 first.pr = second.pr = NoProc;
\r
699 first.isr = second.isr = NULL;
\r
700 first.sendTime = second.sendTime = 2;
\r
701 first.sendDrawOffers = 1;
\r
702 if (appData.firstPlaysBlack) {
\r
703 first.twoMachinesColor = "black\n";
\r
704 second.twoMachinesColor = "white\n";
\r
706 first.twoMachinesColor = "white\n";
\r
707 second.twoMachinesColor = "black\n";
\r
709 first.program = appData.firstChessProgram;
\r
710 second.program = appData.secondChessProgram;
\r
711 first.host = appData.firstHost;
\r
712 second.host = appData.secondHost;
\r
713 first.dir = appData.firstDirectory;
\r
714 second.dir = appData.secondDirectory;
\r
715 first.other = &second;
\r
716 second.other = &first;
\r
717 first.initString = appData.initString;
\r
718 second.initString = appData.secondInitString;
\r
719 first.computerString = appData.firstComputerString;
\r
720 second.computerString = appData.secondComputerString;
\r
721 first.useSigint = second.useSigint = TRUE;
\r
722 first.useSigterm = second.useSigterm = TRUE;
\r
723 first.reuse = appData.reuseFirst;
\r
724 second.reuse = appData.reuseSecond;
\r
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
726 second.nps = appData.secondNPS;
\r
727 first.useSetboard = second.useSetboard = FALSE;
\r
728 first.useSAN = second.useSAN = FALSE;
\r
729 first.usePing = second.usePing = FALSE;
\r
730 first.lastPing = second.lastPing = 0;
\r
731 first.lastPong = second.lastPong = 0;
\r
732 first.usePlayother = second.usePlayother = FALSE;
\r
733 first.useColors = second.useColors = TRUE;
\r
734 first.useUsermove = second.useUsermove = FALSE;
\r
735 first.sendICS = second.sendICS = FALSE;
\r
736 first.sendName = second.sendName = appData.icsActive;
\r
737 first.sdKludge = second.sdKludge = FALSE;
\r
738 first.stKludge = second.stKludge = FALSE;
\r
739 TidyProgramName(first.program, first.host, first.tidy);
\r
740 TidyProgramName(second.program, second.host, second.tidy);
\r
741 first.matchWins = second.matchWins = 0;
\r
742 strcpy(first.variants, appData.variant);
\r
743 strcpy(second.variants, appData.variant);
\r
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
745 first.analyzing = second.analyzing = FALSE;
\r
746 first.initDone = second.initDone = FALSE;
\r
748 /* New features added by Tord: */
\r
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
751 /* End of new features added by Tord. */
\r
753 /* [HGM] time odds: set factor for each machine */
\r
754 first.timeOdds = appData.firstTimeOdds;
\r
755 second.timeOdds = appData.secondTimeOdds;
\r
757 if(appData.timeOddsMode) {
\r
758 norm = first.timeOdds;
\r
759 if(norm > second.timeOdds) norm = second.timeOdds;
\r
761 first.timeOdds /= norm;
\r
762 second.timeOdds /= norm;
\r
765 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
766 first.accumulateTC = appData.firstAccumulateTC;
\r
767 second.accumulateTC = appData.secondAccumulateTC;
\r
768 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
771 first.debug = second.debug = FALSE;
\r
772 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
774 /* [HGM] options */
\r
775 first.optionSettings = appData.firstOptions;
\r
776 second.optionSettings = appData.secondOptions;
\r
778 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
779 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
780 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
781 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
782 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
783 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
785 if (appData.firstProtocolVersion > PROTOVER ||
\r
786 appData.firstProtocolVersion < 1) {
\r
788 sprintf(buf, _("protocol version %d not supported"),
\r
789 appData.firstProtocolVersion);
\r
790 DisplayFatalError(buf, 0, 2);
\r
792 first.protocolVersion = appData.firstProtocolVersion;
\r
795 if (appData.secondProtocolVersion > PROTOVER ||
\r
796 appData.secondProtocolVersion < 1) {
\r
798 sprintf(buf, _("protocol version %d not supported"),
\r
799 appData.secondProtocolVersion);
\r
800 DisplayFatalError(buf, 0, 2);
\r
802 second.protocolVersion = appData.secondProtocolVersion;
\r
805 if (appData.icsActive) {
\r
806 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
807 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
808 appData.clockMode = FALSE;
\r
809 first.sendTime = second.sendTime = 0;
\r
813 /* Override some settings from environment variables, for backward
\r
814 compatibility. Unfortunately it's not feasible to have the env
\r
815 vars just set defaults, at least in xboard. Ugh.
\r
817 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
822 if (appData.noChessProgram) {
\r
823 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
824 + strlen(PATCHLEVEL));
\r
825 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
830 while (*q != ' ' && *q != NULLCHAR) q++;
\r
832 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
833 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
834 + strlen(PATCHLEVEL) + (q - p));
\r
835 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
836 strncat(programVersion, p, q - p);
\r
838 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
839 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
840 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
841 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
845 if (!appData.icsActive) {
\r
847 /* Check for variants that are supported only in ICS mode,
\r
848 or not at all. Some that are accepted here nevertheless
\r
849 have bugs; see comments below.
\r
851 VariantClass variant = StringToVariant(appData.variant);
\r
853 case VariantBughouse: /* need four players and two boards */
\r
854 case VariantKriegspiel: /* need to hide pieces and move details */
\r
855 /* case VariantFischeRandom: (Fabien: moved below) */
\r
856 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
857 DisplayFatalError(buf, 0, 2);
\r
860 case VariantUnknown:
\r
861 case VariantLoadable:
\r
871 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
872 DisplayFatalError(buf, 0, 2);
\r
875 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
876 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
877 case VariantGothic: /* [HGM] should work */
\r
878 case VariantCapablanca: /* [HGM] should work */
\r
879 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
880 case VariantShogi: /* [HGM] drops not tested for legality */
\r
881 case VariantKnightmate: /* [HGM] should work */
\r
882 case VariantCylinder: /* [HGM] untested */
\r
883 case VariantFalcon: /* [HGM] untested */
\r
884 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
885 offboard interposition not understood */
\r
886 case VariantNormal: /* definitely works! */
\r
887 case VariantWildCastle: /* pieces not automatically shuffled */
\r
888 case VariantNoCastle: /* pieces not automatically shuffled */
\r
889 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
890 case VariantLosers: /* should work except for win condition,
\r
891 and doesn't know captures are mandatory */
\r
892 case VariantSuicide: /* should work except for win condition,
\r
893 and doesn't know captures are mandatory */
\r
894 case VariantGiveaway: /* should work except for win condition,
\r
895 and doesn't know captures are mandatory */
\r
896 case VariantTwoKings: /* should work */
\r
897 case VariantAtomic: /* should work except for win condition */
\r
898 case Variant3Check: /* should work except for win condition */
\r
899 case VariantShatranj: /* should work except for all win conditions */
\r
900 case VariantBerolina: /* might work if TestLegality is off */
\r
901 case VariantCapaRandom: /* should work */
\r
902 case VariantJanus: /* should work */
\r
903 case VariantSuper: /* experimental */
\r
904 case VariantGreat: /* experimental, requires legality testing to be off */
\r
909 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
910 InitEngineUCI( installDir, &second );
\r
913 int NextIntegerFromString( char ** str, long * value )
\r
918 while( *s == ' ' || *s == '\t' ) {
\r
924 if( *s >= '0' && *s <= '9' ) {
\r
925 while( *s >= '0' && *s <= '9' ) {
\r
926 *value = *value * 10 + (*s - '0');
\r
938 int NextTimeControlFromString( char ** str, long * value )
\r
941 int result = NextIntegerFromString( str, &temp );
\r
943 if( result == 0 ) {
\r
944 *value = temp * 60; /* Minutes */
\r
945 if( **str == ':' ) {
\r
947 result = NextIntegerFromString( str, &temp );
\r
948 *value += temp; /* Seconds */
\r
955 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
956 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
957 int result = -1; long temp, temp2;
\r
959 if(**str != '+') return -1; // old params remain in force!
\r
961 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
964 /* time only: incremental or sudden-death time control */
\r
965 if(**str == '+') { /* increment follows; read it */
\r
967 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
968 *inc = temp2 * 1000;
\r
970 *moves = 0; *tc = temp * 1000;
\r
972 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
974 (*str)++; /* classical time control */
\r
975 result = NextTimeControlFromString( str, &temp2);
\r
978 *tc = temp2 * 1000;
\r
984 int GetTimeQuota(int movenr)
\r
985 { /* [HGM] get time to add from the multi-session time-control string */
\r
986 int moves=1; /* kludge to force reading of first session */
\r
987 long time, increment;
\r
988 char *s = fullTimeControlString;
\r
990 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
992 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
993 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
994 if(movenr == -1) return time; /* last move before new session */
\r
995 if(!moves) return increment; /* current session is incremental */
\r
996 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
997 } while(movenr >= -1); /* try again for next session */
\r
999 return 0; // no new time quota on this move
\r
1003 ParseTimeControl(tc, ti, mps)
\r
1009 int matched, min, sec;
\r
1011 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1012 if (matched == 1) {
\r
1013 timeControl = min * 60 * 1000;
\r
1014 } else if (matched == 2) {
\r
1015 timeControl = (min * 60 + sec) * 1000;
\r
1022 char buf[MSG_SIZ];
\r
1024 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1027 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1028 else sprintf(buf, "+%s+%d", tc, ti);
\r
1031 sprintf(buf, "+%d/%s", mps, tc);
\r
1032 else sprintf(buf, "+%s", tc);
\r
1034 fullTimeControlString = StrSave(buf);
\r
1036 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1040 if( *tc == '/' ) {
\r
1041 /* Parse second time control */
\r
1044 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1052 timeControl_2 = tc2 * 1000;
\r
1055 timeControl_2 = 0;
\r
1062 timeControl = tc1 * 1000;
\r
1066 timeIncrement = ti * 1000; /* convert to ms */
\r
1067 movesPerSession = 0;
\r
1069 timeIncrement = 0;
\r
1070 movesPerSession = mps;
\r
1078 if (appData.debugMode) {
\r
1079 fprintf(debugFP, "%s\n", programVersion);
\r
1082 if (appData.matchGames > 0) {
\r
1083 appData.matchMode = TRUE;
\r
1084 } else if (appData.matchMode) {
\r
1085 appData.matchGames = 1;
\r
1087 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1088 appData.matchGames = appData.sameColorGames;
\r
1089 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1090 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1091 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1093 Reset(TRUE, FALSE);
\r
1094 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1097 /* kludge: allow timeout for initial "feature" commands */
\r
1099 DisplayMessage("", _("Starting chess program"));
\r
1100 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1105 InitBackEnd3 P((void))
\r
1107 GameMode initialMode;
\r
1108 char buf[MSG_SIZ];
\r
1111 InitChessProgram(&first, startedFromSetupPosition);
\r
1114 if (appData.icsActive) {
\r
1116 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1119 err = establish();
\r
1121 if (*appData.icsCommPort != NULLCHAR) {
\r
1122 sprintf(buf, _("Could not open comm port %s"),
\r
1123 appData.icsCommPort);
\r
1125 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1126 appData.icsHost, appData.icsPort);
\r
1128 DisplayFatalError(buf, err, 1);
\r
1133 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1135 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1136 } else if (appData.noChessProgram) {
\r
1142 if (*appData.cmailGameName != NULLCHAR) {
\r
1144 OpenLoopback(&cmailPR);
\r
1146 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1150 DisplayMessage("", "");
\r
1151 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1152 initialMode = BeginningOfGame;
\r
1153 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1154 initialMode = TwoMachinesPlay;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1156 initialMode = AnalyzeFile;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1158 initialMode = AnalyzeMode;
\r
1159 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1160 initialMode = MachinePlaysWhite;
\r
1161 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1162 initialMode = MachinePlaysBlack;
\r
1163 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1164 initialMode = EditGame;
\r
1165 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1166 initialMode = EditPosition;
\r
1167 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1168 initialMode = Training;
\r
1170 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1171 DisplayFatalError(buf, 0, 2);
\r
1175 if (appData.matchMode) {
\r
1176 /* Set up machine vs. machine match */
\r
1177 if (appData.noChessProgram) {
\r
1178 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1184 if (*appData.loadGameFile != NULLCHAR) {
\r
1185 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1186 if(index<0) lastIndex = index = 1;
\r
1187 if (!LoadGameFromFile(appData.loadGameFile,
\r
1189 appData.loadGameFile, FALSE)) {
\r
1190 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1193 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1194 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1195 if(index<0) lastIndex = index = 1;
\r
1196 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1198 appData.loadPositionFile)) {
\r
1199 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1203 TwoMachinesEvent();
\r
1204 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1205 /* Set up cmail mode */
\r
1206 ReloadCmailMsgEvent(TRUE);
\r
1208 /* Set up other modes */
\r
1209 if (initialMode == AnalyzeFile) {
\r
1210 if (*appData.loadGameFile == NULLCHAR) {
\r
1211 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1215 if (*appData.loadGameFile != NULLCHAR) {
\r
1216 (void) LoadGameFromFile(appData.loadGameFile,
\r
1217 appData.loadGameIndex,
\r
1218 appData.loadGameFile, TRUE);
\r
1219 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1220 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1221 appData.loadPositionIndex,
\r
1222 appData.loadPositionFile);
\r
1223 /* [HGM] try to make self-starting even after FEN load */
\r
1224 /* to allow automatic setup of fairy variants with wtm */
\r
1225 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1226 gameMode = BeginningOfGame;
\r
1227 setboardSpoiledMachineBlack = 1;
\r
1229 /* [HGM] loadPos: make that every new game uses the setup */
\r
1230 /* from file as long as we do not switch variant */
\r
1231 if(!blackPlaysFirst) { int i;
\r
1232 startedFromPositionFile = TRUE;
\r
1233 CopyBoard(filePosition, boards[0]);
\r
1234 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1237 if (initialMode == AnalyzeMode) {
\r
1238 if (appData.noChessProgram) {
\r
1239 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1242 if (appData.icsActive) {
\r
1243 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1246 AnalyzeModeEvent();
\r
1247 } else if (initialMode == AnalyzeFile) {
\r
1248 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1249 ShowThinkingEvent();
\r
1250 AnalyzeFileEvent();
\r
1251 AnalysisPeriodicEvent(1);
\r
1252 } else if (initialMode == MachinePlaysWhite) {
\r
1253 if (appData.noChessProgram) {
\r
1254 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1258 if (appData.icsActive) {
\r
1259 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1263 MachineWhiteEvent();
\r
1264 } else if (initialMode == MachinePlaysBlack) {
\r
1265 if (appData.noChessProgram) {
\r
1266 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1270 if (appData.icsActive) {
\r
1271 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1275 MachineBlackEvent();
\r
1276 } else if (initialMode == TwoMachinesPlay) {
\r
1277 if (appData.noChessProgram) {
\r
1278 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1282 if (appData.icsActive) {
\r
1283 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1287 TwoMachinesEvent();
\r
1288 } else if (initialMode == EditGame) {
\r
1290 } else if (initialMode == EditPosition) {
\r
1291 EditPositionEvent();
\r
1292 } else if (initialMode == Training) {
\r
1293 if (*appData.loadGameFile == NULLCHAR) {
\r
1294 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1303 * Establish will establish a contact to a remote host.port.
\r
1304 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1305 * used to talk to the host.
\r
1306 * Returns 0 if okay, error code if not.
\r
1311 char buf[MSG_SIZ];
\r
1313 if (*appData.icsCommPort != NULLCHAR) {
\r
1314 /* Talk to the host through a serial comm port */
\r
1315 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1317 } else if (*appData.gateway != NULLCHAR) {
\r
1318 if (*appData.remoteShell == NULLCHAR) {
\r
1319 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1320 sprintf(buf, "%s %s %s",
\r
1321 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1322 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1325 /* Use the rsh program to run telnet program on a gateway host */
\r
1326 if (*appData.remoteUser == NULLCHAR) {
\r
1327 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1328 appData.gateway, appData.telnetProgram,
\r
1329 appData.icsHost, appData.icsPort);
\r
1331 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1332 appData.remoteShell, appData.gateway,
\r
1333 appData.remoteUser, appData.telnetProgram,
\r
1334 appData.icsHost, appData.icsPort);
\r
1336 return StartChildProcess(buf, "", &icsPR);
\r
1339 } else if (appData.useTelnet) {
\r
1340 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1343 /* TCP socket interface differs somewhat between
\r
1344 Unix and NT; handle details in the front end.
\r
1346 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1351 show_bytes(fp, buf, count)
\r
1357 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1358 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1367 /* Returns an errno value */
\r
1369 OutputMaybeTelnet(pr, message, count, outError)
\r
1375 char buf[8192], *p, *q, *buflim;
\r
1376 int left, newcount, outcount;
\r
1378 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1379 *appData.gateway != NULLCHAR) {
\r
1380 if (appData.debugMode) {
\r
1381 fprintf(debugFP, ">ICS: ");
\r
1382 show_bytes(debugFP, message, count);
\r
1383 fprintf(debugFP, "\n");
\r
1385 return OutputToProcess(pr, message, count, outError);
\r
1388 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1394 if (q >= buflim) {
\r
1395 if (appData.debugMode) {
\r
1396 fprintf(debugFP, ">ICS: ");
\r
1397 show_bytes(debugFP, buf, newcount);
\r
1398 fprintf(debugFP, "\n");
\r
1400 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1401 if (outcount < newcount) return -1; /* to be sure */
\r
1408 } else if (((unsigned char) *p) == TN_IAC) {
\r
1409 *q++ = (char) TN_IAC;
\r
1416 if (appData.debugMode) {
\r
1417 fprintf(debugFP, ">ICS: ");
\r
1418 show_bytes(debugFP, buf, newcount);
\r
1419 fprintf(debugFP, "\n");
\r
1421 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1422 if (outcount < newcount) return -1; /* to be sure */
\r
1427 read_from_player(isr, closure, message, count, error)
\r
1428 InputSourceRef isr;
\r
1434 int outError, outCount;
\r
1435 static int gotEof = 0;
\r
1437 /* Pass data read from player on to ICS */
\r
1440 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1441 if (outCount < count) {
\r
1442 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1444 } else if (count < 0) {
\r
1445 RemoveInputSource(isr);
\r
1446 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1447 } else if (gotEof++ > 0) {
\r
1448 RemoveInputSource(isr);
\r
1449 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1457 int count, outCount, outError;
\r
1459 if (icsPR == NULL) return;
\r
1461 count = strlen(s);
\r
1462 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1463 if (outCount < count) {
\r
1464 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1468 /* This is used for sending logon scripts to the ICS. Sending
\r
1469 without a delay causes problems when using timestamp on ICC
\r
1470 (at least on my machine). */
\r
1472 SendToICSDelayed(s,msdelay)
\r
1476 int count, outCount, outError;
\r
1478 if (icsPR == NULL) return;
\r
1480 count = strlen(s);
\r
1481 if (appData.debugMode) {
\r
1482 fprintf(debugFP, ">ICS: ");
\r
1483 show_bytes(debugFP, s, count);
\r
1484 fprintf(debugFP, "\n");
\r
1486 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1488 if (outCount < count) {
\r
1489 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1494 /* Remove all highlighting escape sequences in s
\r
1495 Also deletes any suffix starting with '('
\r
1498 StripHighlightAndTitle(s)
\r
1501 static char retbuf[MSG_SIZ];
\r
1504 while (*s != NULLCHAR) {
\r
1505 while (*s == '\033') {
\r
1506 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1507 if (*s != NULLCHAR) s++;
\r
1509 while (*s != NULLCHAR && *s != '\033') {
\r
1510 if (*s == '(' || *s == '[') {
\r
1521 /* Remove all highlighting escape sequences in s */
\r
1526 static char retbuf[MSG_SIZ];
\r
1529 while (*s != NULLCHAR) {
\r
1530 while (*s == '\033') {
\r
1531 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1532 if (*s != NULLCHAR) s++;
\r
1534 while (*s != NULLCHAR && *s != '\033') {
\r
1542 char *variantNames[] = VARIANT_NAMES;
\r
1547 return variantNames[v];
\r
1551 /* Identify a variant from the strings the chess servers use or the
\r
1552 PGN Variant tag names we use. */
\r
1554 StringToVariant(e)
\r
1559 VariantClass v = VariantNormal;
\r
1560 int i, found = FALSE;
\r
1561 char buf[MSG_SIZ];
\r
1565 /* [HGM] skip over optional board-size prefixes */
\r
1566 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1567 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1568 while( *e++ != '_');
\r
1571 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1572 if (StrCaseStr(e, variantNames[i])) {
\r
1573 v = (VariantClass) i;
\r
1580 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1581 || StrCaseStr(e, "wild/fr")
\r
1582 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1583 v = VariantFischeRandom;
\r
1584 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1585 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1587 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1588 if (isdigit(*p)) {
\r
1594 case 0: /* FICS only, actually */
\r
1596 /* Castling legal even if K starts on d-file */
\r
1597 v = VariantWildCastle;
\r
1602 /* Castling illegal even if K & R happen to start in
\r
1603 normal positions. */
\r
1604 v = VariantNoCastle;
\r
1617 /* Castling legal iff K & R start in normal positions */
\r
1618 v = VariantNormal;
\r
1623 /* Special wilds for position setup; unclear what to do here */
\r
1624 v = VariantLoadable;
\r
1627 /* Bizarre ICC game */
\r
1628 v = VariantTwoKings;
\r
1631 v = VariantKriegspiel;
\r
1634 v = VariantLosers;
\r
1637 v = VariantFischeRandom;
\r
1640 v = VariantCrazyhouse;
\r
1643 v = VariantBughouse;
\r
1646 v = Variant3Check;
\r
1649 /* Not quite the same as FICS suicide! */
\r
1650 v = VariantGiveaway;
\r
1653 v = VariantAtomic;
\r
1656 v = VariantShatranj;
\r
1659 /* Temporary names for future ICC types. The name *will* change in
\r
1660 the next xboard/WinBoard release after ICC defines it. */
\r
1689 v = VariantXiangqi;
\r
1692 v = VariantCourier;
\r
1695 v = VariantGothic;
\r
1698 v = VariantCapablanca;
\r
1701 v = VariantKnightmate;
\r
1707 v = VariantCylinder;
\r
1710 v = VariantFalcon;
\r
1713 v = VariantCapaRandom;
\r
1716 v = VariantBerolina;
\r
1728 /* Found "wild" or "w" in the string but no number;
\r
1729 must assume it's normal chess. */
\r
1730 v = VariantNormal;
\r
1733 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1734 DisplayError(buf, 0);
\r
1735 v = VariantUnknown;
\r
1740 if (appData.debugMode) {
\r
1741 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1742 e, wnum, VariantName(v));
\r
1747 static int leftover_start = 0, leftover_len = 0;
\r
1748 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1750 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1751 advance *index beyond it, and set leftover_start to the new value of
\r
1752 *index; else return FALSE. If pattern contains the character '*', it
\r
1753 matches any sequence of characters not containing '\r', '\n', or the
\r
1754 character following the '*' (if any), and the matched sequence(s) are
\r
1755 copied into star_match.
\r
1758 looking_at(buf, index, pattern)
\r
1763 char *bufp = &buf[*index], *patternp = pattern;
\r
1764 int star_count = 0;
\r
1765 char *matchp = star_match[0];
\r
1768 if (*patternp == NULLCHAR) {
\r
1769 *index = leftover_start = bufp - buf;
\r
1770 *matchp = NULLCHAR;
\r
1773 if (*bufp == NULLCHAR) return FALSE;
\r
1774 if (*patternp == '*') {
\r
1775 if (*bufp == *(patternp + 1)) {
\r
1776 *matchp = NULLCHAR;
\r
1777 matchp = star_match[++star_count];
\r
1781 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1783 if (*patternp == NULLCHAR)
\r
1788 *matchp++ = *bufp++;
\r
1792 if (*patternp != *bufp) return FALSE;
\r
1799 SendToPlayer(data, length)
\r
1803 int error, outCount;
\r
1804 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1805 if (outCount < length) {
\r
1806 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1811 PackHolding(packed, holding)
\r
1815 char *p = holding;
\r
1817 int runlength = 0;
\r
1823 switch (runlength) {
\r
1834 sprintf(q, "%d", runlength);
\r
1846 /* Telnet protocol requests from the front end */
\r
1848 TelnetRequest(ddww, option)
\r
1849 unsigned char ddww, option;
\r
1851 unsigned char msg[3];
\r
1852 int outCount, outError;
\r
1854 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1856 if (appData.debugMode) {
\r
1857 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1873 sprintf(buf1, "%d", ddww);
\r
1878 optionStr = "ECHO";
\r
1882 sprintf(buf2, "%d", option);
\r
1885 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1890 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1891 if (outCount < 3) {
\r
1892 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1899 if (!appData.icsActive) return;
\r
1900 TelnetRequest(TN_DO, TN_ECHO);
\r
1906 if (!appData.icsActive) return;
\r
1907 TelnetRequest(TN_DONT, TN_ECHO);
\r
1911 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1913 /* put the holdings sent to us by the server on the board holdings area */
\r
1914 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1916 ChessSquare piece;
\r
1918 if(gameInfo.holdingsWidth < 2) return;
\r
1920 if( (int)lowestPiece >= BlackPawn ) {
\r
1921 holdingsColumn = 0;
\r
1923 holdingsStartRow = BOARD_HEIGHT-1;
\r
1926 holdingsColumn = BOARD_WIDTH-1;
\r
1927 countsColumn = BOARD_WIDTH-2;
\r
1928 holdingsStartRow = 0;
\r
1932 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1933 board[i][holdingsColumn] = EmptySquare;
\r
1934 board[i][countsColumn] = (ChessSquare) 0;
\r
1936 while( (p=*holdings++) != NULLCHAR ) {
\r
1937 piece = CharToPiece( ToUpper(p) );
\r
1938 if(piece == EmptySquare) continue;
\r
1939 /*j = (int) piece - (int) WhitePawn;*/
\r
1940 j = PieceToNumber(piece);
\r
1941 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1942 if(j < 0) continue; /* should not happen */
\r
1943 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1944 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1945 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1952 VariantSwitch(Board board, VariantClass newVariant)
\r
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1955 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1956 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1958 startedFromPositionFile = FALSE;
\r
1959 if(gameInfo.variant == newVariant) return;
\r
1961 /* [HGM] This routine is called each time an assignment is made to
\r
1962 * gameInfo.variant during a game, to make sure the board sizes
\r
1963 * are set to match the new variant. If that means adding or deleting
\r
1964 * holdings, we shift the playing board accordingly
\r
1965 * This kludge is needed because in ICS observe mode, we get boards
\r
1966 * of an ongoing game without knowing the variant, and learn about the
\r
1967 * latter only later. This can be because of the move list we requested,
\r
1968 * in which case the game history is refilled from the beginning anyway,
\r
1969 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1970 * case we want to add those holdings to the already received position.
\r
1974 if (appData.debugMode) {
\r
1975 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1976 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1977 setbuf(debugFP, NULL);
\r
1979 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1980 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1981 switch(newVariant) {
\r
1982 case VariantShogi:
\r
1983 newWidth = 9; newHeight = 9;
\r
1984 gameInfo.holdingsSize = 7;
\r
1985 case VariantBughouse:
\r
1986 case VariantCrazyhouse:
\r
1987 newHoldingsWidth = 2; break;
\r
1989 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1992 if(newWidth != gameInfo.boardWidth ||
\r
1993 newHeight != gameInfo.boardHeight ||
\r
1994 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1996 /* shift position to new playing area, if needed */
\r
1997 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1998 for(i=0; i<BOARD_HEIGHT; i++)
\r
1999 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
2000 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2002 for(i=0; i<newHeight; i++) {
\r
2003 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2004 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2006 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2007 for(i=0; i<BOARD_HEIGHT; i++)
\r
2008 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2009 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2013 gameInfo.boardWidth = newWidth;
\r
2014 gameInfo.boardHeight = newHeight;
\r
2015 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2016 gameInfo.variant = newVariant;
\r
2017 InitDrawingSizes(-2, 0);
\r
2019 /* [HGM] The following should definitely be solved in a better way */
\r
2021 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2022 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2023 saveEP = epStatus[0];
\r
2025 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2027 epStatus[0] = saveEP;
\r
2028 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2029 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2031 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2033 forwardMostMove = oldForwardMostMove;
\r
2034 backwardMostMove = oldBackwardMostMove;
\r
2035 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2038 static int loggedOn = FALSE;
\r
2040 /*-- Game start info cache: --*/
\r
2042 char gs_kind[MSG_SIZ];
\r
2043 static char player1Name[128] = "";
\r
2044 static char player2Name[128] = "";
\r
2045 static int player1Rating = -1;
\r
2046 static int player2Rating = -1;
\r
2047 /*----------------------------*/
\r
2049 ColorClass curColor = ColorNormal;
\r
2050 int suppressKibitz = 0;
\r
2053 read_from_ics(isr, closure, data, count, error)
\r
2054 InputSourceRef isr;
\r
2060 #define BUF_SIZE 8192
\r
2061 #define STARTED_NONE 0
\r
2062 #define STARTED_MOVES 1
\r
2063 #define STARTED_BOARD 2
\r
2064 #define STARTED_OBSERVE 3
\r
2065 #define STARTED_HOLDINGS 4
\r
2066 #define STARTED_CHATTER 5
\r
2067 #define STARTED_COMMENT 6
\r
2068 #define STARTED_MOVES_NOHIDE 7
\r
2070 static int started = STARTED_NONE;
\r
2071 static char parse[20000];
\r
2072 static int parse_pos = 0;
\r
2073 static char buf[BUF_SIZE + 1];
\r
2074 static int firstTime = TRUE, intfSet = FALSE;
\r
2075 static ColorClass prevColor = ColorNormal;
\r
2076 static int savingComment = FALSE;
\r
2082 int backup; /* [DM] For zippy color lines */
\r
2085 if (appData.debugMode) {
\r
2087 fprintf(debugFP, "<ICS: ");
\r
2088 show_bytes(debugFP, data, count);
\r
2089 fprintf(debugFP, "\n");
\r
2093 if (appData.debugMode) { int f = forwardMostMove;
\r
2094 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2095 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2098 /* If last read ended with a partial line that we couldn't parse,
\r
2099 prepend it to the new read and try again. */
\r
2100 if (leftover_len > 0) {
\r
2101 for (i=0; i<leftover_len; i++)
\r
2102 buf[i] = buf[leftover_start + i];
\r
2105 /* Copy in new characters, removing nulls and \r's */
\r
2106 buf_len = leftover_len;
\r
2107 for (i = 0; i < count; i++) {
\r
2108 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2109 buf[buf_len++] = data[i];
\r
2110 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2111 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2112 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2115 buf[buf_len] = NULLCHAR;
\r
2116 next_out = leftover_len;
\r
2117 leftover_start = 0;
\r
2120 while (i < buf_len) {
\r
2121 /* Deal with part of the TELNET option negotiation
\r
2122 protocol. We refuse to do anything beyond the
\r
2123 defaults, except that we allow the WILL ECHO option,
\r
2124 which ICS uses to turn off password echoing when we are
\r
2125 directly connected to it. We reject this option
\r
2126 if localLineEditing mode is on (always on in xboard)
\r
2127 and we are talking to port 23, which might be a real
\r
2128 telnet server that will try to keep WILL ECHO on permanently.
\r
2130 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2131 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2132 unsigned char option;
\r
2134 switch ((unsigned char) buf[++i]) {
\r
2136 if (appData.debugMode)
\r
2137 fprintf(debugFP, "\n<WILL ");
\r
2138 switch (option = (unsigned char) buf[++i]) {
\r
2140 if (appData.debugMode)
\r
2141 fprintf(debugFP, "ECHO ");
\r
2142 /* Reply only if this is a change, according
\r
2143 to the protocol rules. */
\r
2144 if (remoteEchoOption) break;
\r
2145 if (appData.localLineEditing &&
\r
2146 atoi(appData.icsPort) == TN_PORT) {
\r
2147 TelnetRequest(TN_DONT, TN_ECHO);
\r
2150 TelnetRequest(TN_DO, TN_ECHO);
\r
2151 remoteEchoOption = TRUE;
\r
2155 if (appData.debugMode)
\r
2156 fprintf(debugFP, "%d ", option);
\r
2157 /* Whatever this is, we don't want it. */
\r
2158 TelnetRequest(TN_DONT, option);
\r
2163 if (appData.debugMode)
\r
2164 fprintf(debugFP, "\n<WONT ");
\r
2165 switch (option = (unsigned char) buf[++i]) {
\r
2167 if (appData.debugMode)
\r
2168 fprintf(debugFP, "ECHO ");
\r
2169 /* Reply only if this is a change, according
\r
2170 to the protocol rules. */
\r
2171 if (!remoteEchoOption) break;
\r
2173 TelnetRequest(TN_DONT, TN_ECHO);
\r
2174 remoteEchoOption = FALSE;
\r
2177 if (appData.debugMode)
\r
2178 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2179 /* Whatever this is, it must already be turned
\r
2180 off, because we never agree to turn on
\r
2181 anything non-default, so according to the
\r
2182 protocol rules, we don't reply. */
\r
2187 if (appData.debugMode)
\r
2188 fprintf(debugFP, "\n<DO ");
\r
2189 switch (option = (unsigned char) buf[++i]) {
\r
2191 /* Whatever this is, we refuse to do it. */
\r
2192 if (appData.debugMode)
\r
2193 fprintf(debugFP, "%d ", option);
\r
2194 TelnetRequest(TN_WONT, option);
\r
2199 if (appData.debugMode)
\r
2200 fprintf(debugFP, "\n<DONT ");
\r
2201 switch (option = (unsigned char) buf[++i]) {
\r
2203 if (appData.debugMode)
\r
2204 fprintf(debugFP, "%d ", option);
\r
2205 /* Whatever this is, we are already not doing
\r
2206 it, because we never agree to do anything
\r
2207 non-default, so according to the protocol
\r
2208 rules, we don't reply. */
\r
2213 if (appData.debugMode)
\r
2214 fprintf(debugFP, "\n<IAC ");
\r
2215 /* Doubled IAC; pass it through */
\r
2219 if (appData.debugMode)
\r
2220 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2221 /* Drop all other telnet commands on the floor */
\r
2224 if (oldi > next_out)
\r
2225 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2226 if (++i > next_out)
\r
2231 /* OK, this at least will *usually* work */
\r
2232 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2236 if (loggedOn && !intfSet) {
\r
2237 if (ics_type == ICS_ICC) {
\r
2239 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2242 } else if (ics_type == ICS_CHESSNET) {
\r
2243 sprintf(str, "/style 12\n");
\r
2245 strcpy(str, "alias $ @\n$set interface ");
\r
2246 strcat(str, programVersion);
\r
2247 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2249 strcat(str, "$iset nohighlight 1\n");
\r
2251 strcat(str, "$iset lock 1\n$style 12\n");
\r
2257 if (started == STARTED_COMMENT) {
\r
2258 /* Accumulate characters in comment */
\r
2259 parse[parse_pos++] = buf[i];
\r
2260 if (buf[i] == '\n') {
\r
2261 parse[parse_pos] = NULLCHAR;
\r
2262 if(!suppressKibitz) // [HGM] kibitz
\r
2263 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2264 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2265 int nrDigit = 0, nrAlph = 0, i;
\r
2266 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2267 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2268 parse[parse_pos] = NULLCHAR;
\r
2269 // try to be smart: if it does not look like search info, it should go to
\r
2270 // ICS interaction window after all, not to engine-output window.
\r
2271 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2272 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2273 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2274 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2276 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2277 OutputKibitz(suppressKibitz, parse);
\r
2279 char tmp[MSG_SIZ];
\r
2280 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2281 SendToPlayer(tmp, strlen(tmp));
\r
2284 started = STARTED_NONE;
\r
2286 /* Don't match patterns against characters in chatter */
\r
2291 if (started == STARTED_CHATTER) {
\r
2292 if (buf[i] != '\n') {
\r
2293 /* Don't match patterns against characters in chatter */
\r
2297 started = STARTED_NONE;
\r
2300 /* Kludge to deal with rcmd protocol */
\r
2301 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2302 DisplayFatalError(&buf[1], 0, 1);
\r
2305 firstTime = FALSE;
\r
2308 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2309 ics_type = ICS_ICC;
\r
2311 if (appData.debugMode)
\r
2312 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2315 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2316 ics_type = ICS_FICS;
\r
2318 if (appData.debugMode)
\r
2319 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2322 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2323 ics_type = ICS_CHESSNET;
\r
2325 if (appData.debugMode)
\r
2326 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2331 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2332 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2333 looking_at(buf, &i, "will be \"*\""))) {
\r
2334 strcpy(ics_handle, star_match[0]);
\r
2338 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2339 char buf[MSG_SIZ];
\r
2340 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2341 DisplayIcsInteractionTitle(buf);
\r
2342 have_set_title = TRUE;
\r
2345 /* skip finger notes */
\r
2346 if (started == STARTED_NONE &&
\r
2347 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2348 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2349 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2350 started = STARTED_CHATTER;
\r
2355 /* skip formula vars */
\r
2356 if (started == STARTED_NONE &&
\r
2357 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2358 started = STARTED_CHATTER;
\r
2364 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2365 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2366 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2367 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2368 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2369 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2370 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2371 suppressKibitz = TRUE;
\r
2372 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2373 && (gameMode == IcsPlayingWhite)) ||
\r
2374 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2375 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2376 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2378 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2379 parse_pos = 0; parse[0] = NULLCHAR;
\r
2380 savingComment = TRUE;
\r
2381 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2382 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2386 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2387 started = STARTED_CHATTER;
\r
2388 suppressKibitz = TRUE;
\r
2390 } // [HGM] kibitz: end of patch
\r
2392 if (appData.zippyTalk || appData.zippyPlay) {
\r
2393 /* [DM] Backup address for color zippy lines */
\r
2397 if (loggedOn == TRUE)
\r
2398 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2399 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2401 if (ZippyControl(buf, &i) ||
\r
2402 ZippyConverse(buf, &i) ||
\r
2403 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2405 if (!appData.colorize) continue;
\r
2409 } // [DM] 'else { ' deleted
\r
2410 if (/* Don't color "message" or "messages" output */
\r
2411 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2412 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2413 looking_at(buf, &i, "--* (*:*): ") ||
\r
2414 /* Regular tells and says */
\r
2415 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2416 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2417 looking_at(buf, &i, "* says: ") ||
\r
2418 /* Message notifications (same color as tells) */
\r
2419 looking_at(buf, &i, "* has left a message ") ||
\r
2420 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2421 /* Whispers and kibitzes */
\r
2422 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2423 looking_at(buf, &i, "* kibitzes: ") ||
\r
2424 /* Channel tells */
\r
2425 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2427 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2428 /* Avoid "tells you:" spoofs in channels */
\r
2431 if (star_match[0][0] == NULLCHAR ||
\r
2432 strchr(star_match[0], ' ') ||
\r
2433 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2434 /* Reject bogus matches */
\r
2437 if (appData.colorize) {
\r
2438 if (oldi > next_out) {
\r
2439 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2444 Colorize(ColorTell, FALSE);
\r
2445 curColor = ColorTell;
\r
2448 Colorize(ColorKibitz, FALSE);
\r
2449 curColor = ColorKibitz;
\r
2452 p = strrchr(star_match[1], '(');
\r
2454 p = star_match[1];
\r
2458 if (atoi(p) == 1) {
\r
2459 Colorize(ColorChannel1, FALSE);
\r
2460 curColor = ColorChannel1;
\r
2462 Colorize(ColorChannel, FALSE);
\r
2463 curColor = ColorChannel;
\r
2467 curColor = ColorNormal;
\r
2471 if (started == STARTED_NONE && appData.autoComment &&
\r
2472 (gameMode == IcsObserving ||
\r
2473 gameMode == IcsPlayingWhite ||
\r
2474 gameMode == IcsPlayingBlack)) {
\r
2475 parse_pos = i - oldi;
\r
2476 memcpy(parse, &buf[oldi], parse_pos);
\r
2477 parse[parse_pos] = NULLCHAR;
\r
2478 started = STARTED_COMMENT;
\r
2479 savingComment = TRUE;
\r
2481 started = STARTED_CHATTER;
\r
2482 savingComment = FALSE;
\r
2489 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2490 looking_at(buf, &i, "* c-shouts: ")) {
\r
2491 if (appData.colorize) {
\r
2492 if (oldi > next_out) {
\r
2493 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2496 Colorize(ColorSShout, FALSE);
\r
2497 curColor = ColorSShout;
\r
2500 started = STARTED_CHATTER;
\r
2504 if (looking_at(buf, &i, "--->")) {
\r
2509 if (looking_at(buf, &i, "* shouts: ") ||
\r
2510 looking_at(buf, &i, "--> ")) {
\r
2511 if (appData.colorize) {
\r
2512 if (oldi > next_out) {
\r
2513 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2516 Colorize(ColorShout, FALSE);
\r
2517 curColor = ColorShout;
\r
2520 started = STARTED_CHATTER;
\r
2524 if (looking_at( buf, &i, "Challenge:")) {
\r
2525 if (appData.colorize) {
\r
2526 if (oldi > next_out) {
\r
2527 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2530 Colorize(ColorChallenge, FALSE);
\r
2531 curColor = ColorChallenge;
\r
2537 if (looking_at(buf, &i, "* offers you") ||
\r
2538 looking_at(buf, &i, "* offers to be") ||
\r
2539 looking_at(buf, &i, "* would like to") ||
\r
2540 looking_at(buf, &i, "* requests to") ||
\r
2541 looking_at(buf, &i, "Your opponent offers") ||
\r
2542 looking_at(buf, &i, "Your opponent requests")) {
\r
2544 if (appData.colorize) {
\r
2545 if (oldi > next_out) {
\r
2546 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2549 Colorize(ColorRequest, FALSE);
\r
2550 curColor = ColorRequest;
\r
2555 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2556 if (appData.colorize) {
\r
2557 if (oldi > next_out) {
\r
2558 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2561 Colorize(ColorSeek, FALSE);
\r
2562 curColor = ColorSeek;
\r
2567 if (looking_at(buf, &i, "\\ ")) {
\r
2568 if (prevColor != ColorNormal) {
\r
2569 if (oldi > next_out) {
\r
2570 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2573 Colorize(prevColor, TRUE);
\r
2574 curColor = prevColor;
\r
2576 if (savingComment) {
\r
2577 parse_pos = i - oldi;
\r
2578 memcpy(parse, &buf[oldi], parse_pos);
\r
2579 parse[parse_pos] = NULLCHAR;
\r
2580 started = STARTED_COMMENT;
\r
2582 started = STARTED_CHATTER;
\r
2587 if (looking_at(buf, &i, "Black Strength :") ||
\r
2588 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2589 looking_at(buf, &i, "<10>") ||
\r
2590 looking_at(buf, &i, "#@#")) {
\r
2591 /* Wrong board style */
\r
2593 SendToICS(ics_prefix);
\r
2594 SendToICS("set style 12\n");
\r
2595 SendToICS(ics_prefix);
\r
2596 SendToICS("refresh\n");
\r
2600 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2602 have_sent_ICS_logon = 1;
\r
2606 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2607 (looking_at(buf, &i, "\n<12> ") ||
\r
2608 looking_at(buf, &i, "<12> "))) {
\r
2610 if (oldi > next_out) {
\r
2611 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2614 started = STARTED_BOARD;
\r
2619 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2620 looking_at(buf, &i, "<b1> ")) {
\r
2621 if (oldi > next_out) {
\r
2622 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2625 started = STARTED_HOLDINGS;
\r
2630 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2632 /* Header for a move list -- first line */
\r
2634 switch (ics_getting_history) {
\r
2636 switch (gameMode) {
\r
2638 case BeginningOfGame:
\r
2639 /* User typed "moves" or "oldmoves" while we
\r
2640 were idle. Pretend we asked for these
\r
2641 moves and soak them up so user can step
\r
2642 through them and/or save them.
\r
2644 Reset(FALSE, TRUE);
\r
2645 gameMode = IcsObserving;
\r
2648 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2650 case EditGame: /*?*/
\r
2651 case EditPosition: /*?*/
\r
2652 /* Should above feature work in these modes too? */
\r
2653 /* For now it doesn't */
\r
2654 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2657 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2662 /* Is this the right one? */
\r
2663 if (gameInfo.white && gameInfo.black &&
\r
2664 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2665 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2667 ics_getting_history = H_GOT_REQ_HEADER;
\r
2670 case H_GOT_REQ_HEADER:
\r
2671 case H_GOT_UNREQ_HEADER:
\r
2672 case H_GOT_UNWANTED_HEADER:
\r
2673 case H_GETTING_MOVES:
\r
2674 /* Should not happen */
\r
2675 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2676 ics_getting_history = H_FALSE;
\r
2680 /* Save player ratings into gameInfo if needed */
\r
2681 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2682 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2683 (gameInfo.whiteRating == -1 ||
\r
2684 gameInfo.blackRating == -1)) {
\r
2686 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2687 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2688 if (appData.debugMode)
\r
2689 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2690 gameInfo.whiteRating, gameInfo.blackRating);
\r
2695 if (looking_at(buf, &i,
\r
2696 "* * match, initial time: * minute*, increment: * second")) {
\r
2697 /* Header for a move list -- second line */
\r
2698 /* Initial board will follow if this is a wild game */
\r
2699 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2700 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2701 gameInfo.event = StrSave(str);
\r
2702 /* [HGM] we switched variant. Translate boards if needed. */
\r
2703 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2707 if (looking_at(buf, &i, "Move ")) {
\r
2708 /* Beginning of a move list */
\r
2709 switch (ics_getting_history) {
\r
2711 /* Normally should not happen */
\r
2712 /* Maybe user hit reset while we were parsing */
\r
2715 /* Happens if we are ignoring a move list that is not
\r
2716 * the one we just requested. Common if the user
\r
2717 * tries to observe two games without turning off
\r
2720 case H_GETTING_MOVES:
\r
2721 /* Should not happen */
\r
2722 DisplayError(_("Error gathering move list: nested"), 0);
\r
2723 ics_getting_history = H_FALSE;
\r
2725 case H_GOT_REQ_HEADER:
\r
2726 ics_getting_history = H_GETTING_MOVES;
\r
2727 started = STARTED_MOVES;
\r
2729 if (oldi > next_out) {
\r
2730 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2733 case H_GOT_UNREQ_HEADER:
\r
2734 ics_getting_history = H_GETTING_MOVES;
\r
2735 started = STARTED_MOVES_NOHIDE;
\r
2738 case H_GOT_UNWANTED_HEADER:
\r
2739 ics_getting_history = H_FALSE;
\r
2745 if (looking_at(buf, &i, "% ") ||
\r
2746 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2747 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2748 savingComment = FALSE;
\r
2749 switch (started) {
\r
2750 case STARTED_MOVES:
\r
2751 case STARTED_MOVES_NOHIDE:
\r
2752 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2753 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2754 ParseGameHistory(parse);
\r
2756 if (appData.zippyPlay && first.initDone) {
\r
2757 FeedMovesToProgram(&first, forwardMostMove);
\r
2758 if (gameMode == IcsPlayingWhite) {
\r
2759 if (WhiteOnMove(forwardMostMove)) {
\r
2760 if (first.sendTime) {
\r
2761 if (first.useColors) {
\r
2762 SendToProgram("black\n", &first);
\r
2764 SendTimeRemaining(&first, TRUE);
\r
2767 if (first.useColors) {
\r
2768 SendToProgram("white\ngo\n", &first);
\r
2770 SendToProgram("go\n", &first);
\r
2773 if (first.useColors) {
\r
2774 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2776 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2778 first.maybeThinking = TRUE;
\r
2780 if (first.usePlayother) {
\r
2781 if (first.sendTime) {
\r
2782 SendTimeRemaining(&first, TRUE);
\r
2784 SendToProgram("playother\n", &first);
\r
2785 firstMove = FALSE;
\r
2790 } else if (gameMode == IcsPlayingBlack) {
\r
2791 if (!WhiteOnMove(forwardMostMove)) {
\r
2792 if (first.sendTime) {
\r
2793 if (first.useColors) {
\r
2794 SendToProgram("white\n", &first);
\r
2796 SendTimeRemaining(&first, FALSE);
\r
2799 if (first.useColors) {
\r
2800 SendToProgram("black\ngo\n", &first);
\r
2802 SendToProgram("go\n", &first);
\r
2805 if (first.useColors) {
\r
2806 SendToProgram("black\n", &first);
\r
2808 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2810 first.maybeThinking = TRUE;
\r
2812 if (first.usePlayother) {
\r
2813 if (first.sendTime) {
\r
2814 SendTimeRemaining(&first, FALSE);
\r
2816 SendToProgram("playother\n", &first);
\r
2817 firstMove = FALSE;
\r
2825 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2826 /* Moves came from oldmoves or moves command
\r
2827 while we weren't doing anything else.
\r
2829 currentMove = forwardMostMove;
\r
2830 ClearHighlights();/*!!could figure this out*/
\r
2831 flipView = appData.flipView;
\r
2832 DrawPosition(FALSE, boards[currentMove]);
\r
2833 DisplayBothClocks();
\r
2834 sprintf(str, "%s vs. %s",
\r
2835 gameInfo.white, gameInfo.black);
\r
2836 DisplayTitle(str);
\r
2837 gameMode = IcsIdle;
\r
2839 /* Moves were history of an active game */
\r
2840 if (gameInfo.resultDetails != NULL) {
\r
2841 free(gameInfo.resultDetails);
\r
2842 gameInfo.resultDetails = NULL;
\r
2845 HistorySet(parseList, backwardMostMove,
\r
2846 forwardMostMove, currentMove-1);
\r
2847 DisplayMove(currentMove - 1);
\r
2848 if (started == STARTED_MOVES) next_out = i;
\r
2849 started = STARTED_NONE;
\r
2850 ics_getting_history = H_FALSE;
\r
2853 case STARTED_OBSERVE:
\r
2854 started = STARTED_NONE;
\r
2855 SendToICS(ics_prefix);
\r
2856 SendToICS("refresh\n");
\r
2862 if(bookHit) { // [HGM] book: simulate book reply
\r
2863 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2865 programStats.nodes = programStats.depth = programStats.time =
\r
2866 programStats.score = programStats.got_only_move = 0;
\r
2867 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2869 strcpy(bookMove, "move ");
\r
2870 strcat(bookMove, bookHit);
\r
2871 HandleMachineMove(bookMove, &first);
\r
2876 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2877 started == STARTED_HOLDINGS ||
\r
2878 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2879 /* Accumulate characters in move list or board */
\r
2880 parse[parse_pos++] = buf[i];
\r
2883 /* Start of game messages. Mostly we detect start of game
\r
2884 when the first board image arrives. On some versions
\r
2885 of the ICS, though, we need to do a "refresh" after starting
\r
2886 to observe in order to get the current board right away. */
\r
2887 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2888 started = STARTED_OBSERVE;
\r
2892 /* Handle auto-observe */
\r
2893 if (appData.autoObserve &&
\r
2894 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2895 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2897 /* Choose the player that was highlighted, if any. */
\r
2898 if (star_match[0][0] == '\033' ||
\r
2899 star_match[1][0] != '\033') {
\r
2900 player = star_match[0];
\r
2902 player = star_match[2];
\r
2904 sprintf(str, "%sobserve %s\n",
\r
2905 ics_prefix, StripHighlightAndTitle(player));
\r
2908 /* Save ratings from notify string */
\r
2909 strcpy(player1Name, star_match[0]);
\r
2910 player1Rating = string_to_rating(star_match[1]);
\r
2911 strcpy(player2Name, star_match[2]);
\r
2912 player2Rating = string_to_rating(star_match[3]);
\r
2914 if (appData.debugMode)
\r
2916 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2917 player1Name, player1Rating,
\r
2918 player2Name, player2Rating);
\r
2923 /* Deal with automatic examine mode after a game,
\r
2924 and with IcsObserving -> IcsExamining transition */
\r
2925 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2926 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2928 int gamenum = atoi(star_match[0]);
\r
2929 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2930 gamenum == ics_gamenum) {
\r
2931 /* We were already playing or observing this game;
\r
2932 no need to refetch history */
\r
2933 gameMode = IcsExamining;
\r
2935 pauseExamForwardMostMove = forwardMostMove;
\r
2936 } else if (currentMove < forwardMostMove) {
\r
2937 ForwardInner(forwardMostMove);
\r
2940 /* I don't think this case really can happen */
\r
2941 SendToICS(ics_prefix);
\r
2942 SendToICS("refresh\n");
\r
2947 /* Error messages */
\r
2948 if (ics_user_moved) {
\r
2949 if (looking_at(buf, &i, "Illegal move") ||
\r
2950 looking_at(buf, &i, "Not a legal move") ||
\r
2951 looking_at(buf, &i, "Your king is in check") ||
\r
2952 looking_at(buf, &i, "It isn't your turn") ||
\r
2953 looking_at(buf, &i, "It is not your move")) {
\r
2954 /* Illegal move */
\r
2955 ics_user_moved = 0;
\r
2956 if (forwardMostMove > backwardMostMove) {
\r
2957 currentMove = --forwardMostMove;
\r
2958 DisplayMove(currentMove - 1); /* before DMError */
\r
2959 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2960 DrawPosition(FALSE, boards[currentMove]);
\r
2962 DisplayBothClocks();
\r
2968 if (looking_at(buf, &i, "still have time") ||
\r
2969 looking_at(buf, &i, "not out of time") ||
\r
2970 looking_at(buf, &i, "either player is out of time") ||
\r
2971 looking_at(buf, &i, "has timeseal; checking")) {
\r
2972 /* We must have called his flag a little too soon */
\r
2973 whiteFlag = blackFlag = FALSE;
\r
2977 if (looking_at(buf, &i, "added * seconds to") ||
\r
2978 looking_at(buf, &i, "seconds were added to")) {
\r
2979 /* Update the clocks */
\r
2980 SendToICS(ics_prefix);
\r
2981 SendToICS("refresh\n");
\r
2985 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2986 ics_clock_paused = TRUE;
\r
2991 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2992 ics_clock_paused = FALSE;
\r
2997 /* Grab player ratings from the Creating: message.
\r
2998 Note we have to check for the special case when
\r
2999 the ICS inserts things like [white] or [black]. */
\r
3000 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3001 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3003 0 player 1 name (not necessarily white)
\r
3005 2 empty, white, or black (IGNORED)
\r
3006 3 player 2 name (not necessarily black)
\r
3009 The names/ratings are sorted out when the game
\r
3010 actually starts (below).
\r
3012 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3013 player1Rating = string_to_rating(star_match[1]);
\r
3014 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3015 player2Rating = string_to_rating(star_match[4]);
\r
3017 if (appData.debugMode)
\r
3019 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3020 player1Name, player1Rating,
\r
3021 player2Name, player2Rating);
\r
3026 /* Improved generic start/end-of-game messages */
\r
3027 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3028 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3029 /* If tkind == 0: */
\r
3030 /* star_match[0] is the game number */
\r
3031 /* [1] is the white player's name */
\r
3032 /* [2] is the black player's name */
\r
3033 /* For end-of-game: */
\r
3034 /* [3] is the reason for the game end */
\r
3035 /* [4] is a PGN end game-token, preceded by " " */
\r
3036 /* For start-of-game: */
\r
3037 /* [3] begins with "Creating" or "Continuing" */
\r
3038 /* [4] is " *" or empty (don't care). */
\r
3039 int gamenum = atoi(star_match[0]);
\r
3040 char *whitename, *blackname, *why, *endtoken;
\r
3041 ChessMove endtype = (ChessMove) 0;
\r
3044 whitename = star_match[1];
\r
3045 blackname = star_match[2];
\r
3046 why = star_match[3];
\r
3047 endtoken = star_match[4];
\r
3049 whitename = star_match[1];
\r
3050 blackname = star_match[3];
\r
3051 why = star_match[5];
\r
3052 endtoken = star_match[6];
\r
3055 /* Game start messages */
\r
3056 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3057 strncmp(why, "Continuing ", 11) == 0) {
\r
3058 gs_gamenum = gamenum;
\r
3059 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3061 if (appData.zippyPlay) {
\r
3062 ZippyGameStart(whitename, blackname);
\r
3068 /* Game end messages */
\r
3069 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3070 ics_gamenum != gamenum) {
\r
3073 while (endtoken[0] == ' ') endtoken++;
\r
3074 switch (endtoken[0]) {
\r
3077 endtype = GameUnfinished;
\r
3080 endtype = BlackWins;
\r
3083 if (endtoken[1] == '/')
\r
3084 endtype = GameIsDrawn;
\r
3086 endtype = WhiteWins;
\r
3089 GameEnds(endtype, why, GE_ICS);
\r
3091 if (appData.zippyPlay && first.initDone) {
\r
3092 ZippyGameEnd(endtype, why);
\r
3093 if (first.pr == NULL) {
\r
3094 /* Start the next process early so that we'll
\r
3095 be ready for the next challenge */
\r
3096 StartChessProgram(&first);
\r
3098 /* Send "new" early, in case this command takes
\r
3099 a long time to finish, so that we'll be ready
\r
3100 for the next challenge. */
\r
3101 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3102 Reset(TRUE, TRUE);
\r
3108 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3109 looking_at(buf, &i, "no longer observing game *") ||
\r
3110 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3111 if (gameMode == IcsObserving &&
\r
3112 atoi(star_match[0]) == ics_gamenum)
\r
3114 /* icsEngineAnalyze */
\r
3115 if (appData.icsEngineAnalyze) {
\r
3116 ExitAnalyzeMode();
\r
3120 gameMode = IcsIdle;
\r
3122 ics_user_moved = FALSE;
\r
3127 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3128 if (gameMode == IcsExamining &&
\r
3129 atoi(star_match[0]) == ics_gamenum)
\r
3131 gameMode = IcsIdle;
\r
3133 ics_user_moved = FALSE;
\r
3138 /* Advance leftover_start past any newlines we find,
\r
3139 so only partial lines can get reparsed */
\r
3140 if (looking_at(buf, &i, "\n")) {
\r
3141 prevColor = curColor;
\r
3142 if (curColor != ColorNormal) {
\r
3143 if (oldi > next_out) {
\r
3144 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3147 Colorize(ColorNormal, FALSE);
\r
3148 curColor = ColorNormal;
\r
3150 if (started == STARTED_BOARD) {
\r
3151 started = STARTED_NONE;
\r
3152 parse[parse_pos] = NULLCHAR;
\r
3153 ParseBoard12(parse);
\r
3154 ics_user_moved = 0;
\r
3156 /* Send premove here */
\r
3157 if (appData.premove) {
\r
3158 char str[MSG_SIZ];
\r
3159 if (currentMove == 0 &&
\r
3160 gameMode == IcsPlayingWhite &&
\r
3161 appData.premoveWhite) {
\r
3162 sprintf(str, "%s%s\n", ics_prefix,
\r
3163 appData.premoveWhiteText);
\r
3164 if (appData.debugMode)
\r
3165 fprintf(debugFP, "Sending premove:\n");
\r
3167 } else if (currentMove == 1 &&
\r
3168 gameMode == IcsPlayingBlack &&
\r
3169 appData.premoveBlack) {
\r
3170 sprintf(str, "%s%s\n", ics_prefix,
\r
3171 appData.premoveBlackText);
\r
3172 if (appData.debugMode)
\r
3173 fprintf(debugFP, "Sending premove:\n");
\r
3175 } else if (gotPremove) {
\r
3177 ClearPremoveHighlights();
\r
3178 if (appData.debugMode)
\r
3179 fprintf(debugFP, "Sending premove:\n");
\r
3180 UserMoveEvent(premoveFromX, premoveFromY,
\r
3181 premoveToX, premoveToY,
\r
3182 premovePromoChar);
\r
3186 /* Usually suppress following prompt */
\r
3187 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3188 if (looking_at(buf, &i, "*% ")) {
\r
3189 savingComment = FALSE;
\r
3193 } else if (started == STARTED_HOLDINGS) {
\r
3195 char new_piece[MSG_SIZ];
\r
3196 started = STARTED_NONE;
\r
3197 parse[parse_pos] = NULLCHAR;
\r
3198 if (appData.debugMode)
\r
3199 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3200 parse, currentMove);
\r
3201 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3202 gamenum == ics_gamenum) {
\r
3203 if (gameInfo.variant == VariantNormal) {
\r
3204 /* [HGM] We seem to switch variant during a game!
\r
3205 * Presumably no holdings were displayed, so we have
\r
3206 * to move the position two files to the right to
\r
3207 * create room for them!
\r
3209 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3210 /* Get a move list just to see the header, which
\r
3211 will tell us whether this is really bug or zh */
\r
3212 if (ics_getting_history == H_FALSE) {
\r
3213 ics_getting_history = H_REQUESTED;
\r
3214 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3218 new_piece[0] = NULLCHAR;
\r
3219 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3220 &gamenum, white_holding, black_holding,
\r
3222 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3223 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3224 /* [HGM] copy holdings to board holdings area */
\r
3225 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3226 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3228 if (appData.zippyPlay && first.initDone) {
\r
3229 ZippyHoldings(white_holding, black_holding,
\r
3233 if (tinyLayout || smallLayout) {
\r
3234 char wh[16], bh[16];
\r
3235 PackHolding(wh, white_holding);
\r
3236 PackHolding(bh, black_holding);
\r
3237 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3238 gameInfo.white, gameInfo.black);
\r
3240 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3241 gameInfo.white, white_holding,
\r
3242 gameInfo.black, black_holding);
\r
3245 DrawPosition(FALSE, boards[currentMove]);
\r
3246 DisplayTitle(str);
\r
3248 /* Suppress following prompt */
\r
3249 if (looking_at(buf, &i, "*% ")) {
\r
3250 savingComment = FALSE;
\r
3257 i++; /* skip unparsed character and loop back */
\r
3260 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3261 started != STARTED_HOLDINGS && i > next_out) {
\r
3262 SendToPlayer(&buf[next_out], i - next_out);
\r
3265 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3267 leftover_len = buf_len - leftover_start;
\r
3268 /* if buffer ends with something we couldn't parse,
\r
3269 reparse it after appending the next read */
\r
3271 } else if (count == 0) {
\r
3272 RemoveInputSource(isr);
\r
3273 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3275 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3280 /* Board style 12 looks like this:
\r
3282 <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
3284 * The "<12> " is stripped before it gets to this routine. The two
\r
3285 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3286 * some chess servers may not have them, or may have only the first.
\r
3287 * Additional trailing fields may be added in the future.
\r
3290 #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
3292 #define RELATION_OBSERVING_PLAYED 0
\r
3293 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3294 #define RELATION_PLAYING_MYMOVE 1
\r
3295 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3296 #define RELATION_EXAMINING 2
\r
3297 #define RELATION_ISOLATED_BOARD -3
\r
3298 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3301 ParseBoard12(string)
\r
3304 GameMode newGameMode;
\r
3305 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3306 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3307 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3308 char to_play, board_chars[200];
\r
3309 char move_str[500], str[500], elapsed_time[500];
\r
3310 char black[32], white[32];
\r
3312 int prevMove = currentMove;
\r
3314 ChessMove moveType;
\r
3315 int fromX, fromY, toX, toY;
\r
3317 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3318 char *bookHit = NULL; // [HGM] book
\r
3320 fromX = fromY = toX = toY = -1;
\r
3324 if (appData.debugMode)
\r
3325 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3327 move_str[0] = NULLCHAR;
\r
3328 elapsed_time[0] = NULLCHAR;
\r
3329 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3331 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3332 if(string[i] == ' ') { ranks++; files = 0; }
\r
3336 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3337 board_chars[i] = '\0';
\r
3340 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3341 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3342 &gamenum, white, black, &relation, &basetime, &increment,
\r
3343 &white_stren, &black_stren, &white_time, &black_time,
\r
3344 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3348 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3349 DisplayError(str, 0);
\r
3353 /* Convert the move number to internal form */
\r
3354 moveNum = (moveNum - 1) * 2;
\r
3355 if (to_play == 'B') moveNum++;
\r
3356 if (moveNum >= MAX_MOVES) {
\r
3357 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3362 switch (relation) {
\r
3363 case RELATION_OBSERVING_PLAYED:
\r
3364 case RELATION_OBSERVING_STATIC:
\r
3365 if (gamenum == -1) {
\r
3366 /* Old ICC buglet */
\r
3367 relation = RELATION_OBSERVING_STATIC;
\r
3369 newGameMode = IcsObserving;
\r
3371 case RELATION_PLAYING_MYMOVE:
\r
3372 case RELATION_PLAYING_NOTMYMOVE:
\r
3374 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3375 IcsPlayingWhite : IcsPlayingBlack;
\r
3377 case RELATION_EXAMINING:
\r
3378 newGameMode = IcsExamining;
\r
3380 case RELATION_ISOLATED_BOARD:
\r
3382 /* Just display this board. If user was doing something else,
\r
3383 we will forget about it until the next board comes. */
\r
3384 newGameMode = IcsIdle;
\r
3386 case RELATION_STARTING_POSITION:
\r
3387 newGameMode = gameMode;
\r
3391 /* Modify behavior for initial board display on move listing
\r
3394 switch (ics_getting_history) {
\r
3398 case H_GOT_REQ_HEADER:
\r
3399 case H_GOT_UNREQ_HEADER:
\r
3400 /* This is the initial position of the current game */
\r
3401 gamenum = ics_gamenum;
\r
3402 moveNum = 0; /* old ICS bug workaround */
\r
3403 if (to_play == 'B') {
\r
3404 startedFromSetupPosition = TRUE;
\r
3405 blackPlaysFirst = TRUE;
\r
3407 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3408 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3409 if (currentMove == 0) currentMove = 1;
\r
3411 newGameMode = gameMode;
\r
3412 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3414 case H_GOT_UNWANTED_HEADER:
\r
3415 /* This is an initial board that we don't want */
\r
3417 case H_GETTING_MOVES:
\r
3418 /* Should not happen */
\r
3419 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3420 ics_getting_history = H_FALSE;
\r
3424 /* Take action if this is the first board of a new game, or of a
\r
3425 different game than is currently being displayed. */
\r
3426 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3427 relation == RELATION_ISOLATED_BOARD) {
\r
3429 /* Forget the old game and get the history (if any) of the new one */
\r
3430 if (gameMode != BeginningOfGame) {
\r
3431 Reset(FALSE, TRUE);
\r
3434 if (appData.autoRaiseBoard) BoardToTop();
\r
3436 if (gamenum == -1) {
\r
3437 newGameMode = IcsIdle;
\r
3438 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3439 appData.getMoveList) {
\r
3440 /* Need to get game history */
\r
3441 ics_getting_history = H_REQUESTED;
\r
3442 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3446 /* Initially flip the board to have black on the bottom if playing
\r
3447 black or if the ICS flip flag is set, but let the user change
\r
3448 it with the Flip View button. */
\r
3449 flipView = appData.autoFlipView ?
\r
3450 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3453 /* Done with values from previous mode; copy in new ones */
\r
3454 gameMode = newGameMode;
\r
3456 ics_gamenum = gamenum;
\r
3457 if (gamenum == gs_gamenum) {
\r
3458 int klen = strlen(gs_kind);
\r
3459 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3460 sprintf(str, "ICS %s", gs_kind);
\r
3461 gameInfo.event = StrSave(str);
\r
3463 gameInfo.event = StrSave("ICS game");
\r
3465 gameInfo.site = StrSave(appData.icsHost);
\r
3466 gameInfo.date = PGNDate();
\r
3467 gameInfo.round = StrSave("-");
\r
3468 gameInfo.white = StrSave(white);
\r
3469 gameInfo.black = StrSave(black);
\r
3470 timeControl = basetime * 60 * 1000;
\r
3471 timeControl_2 = 0;
\r
3472 timeIncrement = increment * 1000;
\r
3473 movesPerSession = 0;
\r
3474 gameInfo.timeControl = TimeControlTagValue();
\r
3475 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3476 if (appData.debugMode) {
\r
3477 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3478 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3479 setbuf(debugFP, NULL);
\r
3482 gameInfo.outOfBook = NULL;
\r
3484 /* Do we have the ratings? */
\r
3485 if (strcmp(player1Name, white) == 0 &&
\r
3486 strcmp(player2Name, black) == 0) {
\r
3487 if (appData.debugMode)
\r
3488 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3489 player1Rating, player2Rating);
\r
3490 gameInfo.whiteRating = player1Rating;
\r
3491 gameInfo.blackRating = player2Rating;
\r
3492 } else if (strcmp(player2Name, white) == 0 &&
\r
3493 strcmp(player1Name, black) == 0) {
\r
3494 if (appData.debugMode)
\r
3495 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3496 player2Rating, player1Rating);
\r
3497 gameInfo.whiteRating = player2Rating;
\r
3498 gameInfo.blackRating = player1Rating;
\r
3500 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3502 /* Silence shouts if requested */
\r
3503 if (appData.quietPlay &&
\r
3504 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3505 SendToICS(ics_prefix);
\r
3506 SendToICS("set shout 0\n");
\r
3510 /* Deal with midgame name changes */
\r
3512 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3513 if (gameInfo.white) free(gameInfo.white);
\r
3514 gameInfo.white = StrSave(white);
\r
3516 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3517 if (gameInfo.black) free(gameInfo.black);
\r
3518 gameInfo.black = StrSave(black);
\r
3522 /* Throw away game result if anything actually changes in examine mode */
\r
3523 if (gameMode == IcsExamining && !newGame) {
\r
3524 gameInfo.result = GameUnfinished;
\r
3525 if (gameInfo.resultDetails != NULL) {
\r
3526 free(gameInfo.resultDetails);
\r
3527 gameInfo.resultDetails = NULL;
\r
3531 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3532 in if they are in a different variation than we are. */
\r
3533 if (pauseExamInvalid) return;
\r
3534 if (pausing && gameMode == IcsExamining) {
\r
3535 if (moveNum <= pauseExamForwardMostMove) {
\r
3536 pauseExamInvalid = TRUE;
\r
3537 forwardMostMove = pauseExamForwardMostMove;
\r
3542 if (appData.debugMode) {
\r
3543 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3545 /* Parse the board */
\r
3546 for (k = 0; k < ranks; k++) {
\r
3547 for (j = 0; j < files; j++)
\r
3548 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3549 if(gameInfo.holdingsWidth > 1) {
\r
3550 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3551 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3554 CopyBoard(boards[moveNum], board);
\r
3555 if (moveNum == 0) {
\r
3556 startedFromSetupPosition =
\r
3557 !CompareBoards(board, initialPosition);
\r
3558 if(startedFromSetupPosition)
\r
3559 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3562 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3563 to make it also work for FRC opening positions. Note that board12
\r
3564 is really defective for later FRC positions, as it has no way to
\r
3565 indicate which Rook can castle if they are on the same side of King.
\r
3566 For the initial position we grant rights to the outermost Rooks,
\r
3567 and remember thos rights, and we then copy them on positions
\r
3568 later in an FRC game. This means WB might not recognize castlings with
\r
3569 Rooks that have moved back to their original position as illegal,
\r
3570 but in ICS mode that is not its job anyway.
\r
3572 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3573 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3575 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3576 if(board[0][i] == WhiteRook) j = i;
\r
3577 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3578 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3579 if(board[0][i] == WhiteRook) j = i;
\r
3580 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3581 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3582 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3583 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3584 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3585 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3586 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3588 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3589 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3590 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3591 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3592 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3593 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3595 r = castlingRights[moveNum][0] = initialRights[0];
\r
3596 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3597 r = castlingRights[moveNum][1] = initialRights[1];
\r
3598 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3599 r = castlingRights[moveNum][3] = initialRights[3];
\r
3600 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3601 r = castlingRights[moveNum][4] = initialRights[4];
\r
3602 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3603 /* wildcastle kludge: always assume King has rights */
\r
3604 r = castlingRights[moveNum][2] = initialRights[2];
\r
3605 r = castlingRights[moveNum][5] = initialRights[5];
\r
3607 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3608 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3611 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3612 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3613 /* This was an initial position from a move list, not
\r
3614 the current position */
\r
3618 /* Update currentMove and known move number limits */
\r
3619 newMove = newGame || moveNum > forwardMostMove;
\r
3621 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3622 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3623 takeback = forwardMostMove - moveNum;
\r
3624 for (i = 0; i < takeback; i++) {
\r
3625 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3626 SendToProgram("undo\n", &first);
\r
3631 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3632 if (gameMode == IcsExamining && moveNum == 0) {
\r
3633 /* Workaround for ICS limitation: we are not told the wild
\r
3634 type when starting to examine a game. But if we ask for
\r
3635 the move list, the move list header will tell us */
\r
3636 ics_getting_history = H_REQUESTED;
\r
3637 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3640 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3641 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3642 forwardMostMove = moveNum;
\r
3643 if (!pausing || currentMove > forwardMostMove)
\r
3644 currentMove = forwardMostMove;
\r
3646 /* New part of history that is not contiguous with old part */
\r
3647 if (pausing && gameMode == IcsExamining) {
\r
3648 pauseExamInvalid = TRUE;
\r
3649 forwardMostMove = pauseExamForwardMostMove;
\r
3652 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3653 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3654 ics_getting_history = H_REQUESTED;
\r
3655 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3660 /* Update the clocks */
\r
3661 if (strchr(elapsed_time, '.')) {
\r
3662 /* Time is in ms */
\r
3663 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3664 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3666 /* Time is in seconds */
\r
3667 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3668 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3673 if (appData.zippyPlay && newGame &&
\r
3674 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3675 gameMode != IcsExamining)
\r
3676 ZippyFirstBoard(moveNum, basetime, increment);
\r
3679 /* Put the move on the move list, first converting
\r
3680 to canonical algebraic form. */
\r
3681 if (moveNum > 0) {
\r
3682 if (appData.debugMode) {
\r
3683 if (appData.debugMode) { int f = forwardMostMove;
\r
3684 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3685 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3687 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3688 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3689 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3690 setbuf(debugFP, NULL);
\r
3692 if (moveNum <= backwardMostMove) {
\r
3693 /* We don't know what the board looked like before
\r
3694 this move. Punt. */
\r
3695 strcpy(parseList[moveNum - 1], move_str);
\r
3696 strcat(parseList[moveNum - 1], " ");
\r
3697 strcat(parseList[moveNum - 1], elapsed_time);
\r
3698 moveList[moveNum - 1][0] = NULLCHAR;
\r
3699 } else if (strcmp(move_str, "none") == 0) {
\r
3700 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3701 /* Again, we don't know what the board looked like;
\r
3702 this is really the start of the game. */
\r
3703 parseList[moveNum - 1][0] = NULLCHAR;
\r
3704 moveList[moveNum - 1][0] = NULLCHAR;
\r
3705 backwardMostMove = moveNum;
\r
3706 startedFromSetupPosition = TRUE;
\r
3707 fromX = fromY = toX = toY = -1;
\r
3709 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3710 // So we parse the long-algebraic move string in stead of the SAN move
\r
3711 int valid; char buf[MSG_SIZ], *prom;
\r
3713 // str looks something like "Q/a1-a2"; kill the slash
\r
3714 if(str[1] == '/')
\r
3715 sprintf(buf, "%c%s", str[0], str+2);
\r
3716 else strcpy(buf, str); // might be castling
\r
3717 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3718 strcat(buf, prom); // long move lacks promo specification!
\r
3719 if(!appData.testLegality) {
\r
3720 if(appData.debugMode)
\r
3721 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3722 strcpy(move_str, buf);
\r
3724 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3725 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3726 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3727 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3728 // end of long SAN patch
\r
3730 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3731 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3732 fromY, fromX, toY, toX, promoChar,
\r
3733 parseList[moveNum-1]);
\r
3734 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3735 castlingRights[moveNum]) ) {
\r
3737 case MT_STALEMATE:
\r
3741 if(gameInfo.variant != VariantShogi)
\r
3742 strcat(parseList[moveNum - 1], "+");
\r
3744 case MT_CHECKMATE:
\r
3745 strcat(parseList[moveNum - 1], "#");
\r
3748 strcat(parseList[moveNum - 1], " ");
\r
3749 strcat(parseList[moveNum - 1], elapsed_time);
\r
3750 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3751 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3752 strcat(moveList[moveNum - 1], "\n");
\r
3754 /* Move from ICS was illegal!? Punt. */
\r
3755 if (appData.debugMode) {
\r
3756 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3757 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3760 if (appData.testLegality && appData.debugMode) {
\r
3761 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3762 DisplayError(str, 0);
\r
3765 strcpy(parseList[moveNum - 1], move_str);
\r
3766 strcat(parseList[moveNum - 1], " ");
\r
3767 strcat(parseList[moveNum - 1], elapsed_time);
\r
3768 moveList[moveNum - 1][0] = NULLCHAR;
\r
3769 fromX = fromY = toX = toY = -1;
\r
3772 if (appData.debugMode) {
\r
3773 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3774 setbuf(debugFP, NULL);
\r
3778 /* Send move to chess program (BEFORE animating it). */
\r
3779 if (appData.zippyPlay && !newGame && newMove &&
\r
3780 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3782 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3783 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3784 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3785 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3787 DisplayError(str, 0);
\r
3789 if (first.sendTime) {
\r
3790 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3792 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3793 if (firstMove && !bookHit) {
\r
3794 firstMove = FALSE;
\r
3795 if (first.useColors) {
\r
3796 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3798 "black\ngo\n", &first);
\r
3800 SendToProgram("go\n", &first);
\r
3802 first.maybeThinking = TRUE;
\r
3805 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3806 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3807 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3808 DisplayError(str, 0);
\r
3810 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3811 SendMoveToProgram(moveNum - 1, &first);
\r
3818 if (moveNum > 0 && !gotPremove) {
\r
3819 /* If move comes from a remote source, animate it. If it
\r
3820 isn't remote, it will have already been animated. */
\r
3821 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3822 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3824 if (!pausing && appData.highlightLastMove) {
\r
3825 SetHighlights(fromX, fromY, toX, toY);
\r
3829 /* Start the clocks */
\r
3830 whiteFlag = blackFlag = FALSE;
\r
3831 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3832 if (ticking == 0) {
\r
3833 ics_clock_paused = TRUE;
\r
3835 } else if (ticking == 1) {
\r
3836 ics_clock_paused = FALSE;
\r
3838 if (gameMode == IcsIdle ||
\r
3839 relation == RELATION_OBSERVING_STATIC ||
\r
3840 relation == RELATION_EXAMINING ||
\r
3842 DisplayBothClocks();
\r
3846 /* Display opponents and material strengths */
\r
3847 if (gameInfo.variant != VariantBughouse &&
\r
3848 gameInfo.variant != VariantCrazyhouse) {
\r
3849 if (tinyLayout || smallLayout) {
\r
3850 if(gameInfo.variant == VariantNormal)
\r
3851 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3852 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3853 basetime, increment);
\r
3855 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3856 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3857 basetime, increment, (int) gameInfo.variant);
\r
3859 if(gameInfo.variant == VariantNormal)
\r
3860 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3861 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3862 basetime, increment);
\r
3864 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3865 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3866 basetime, increment, VariantName(gameInfo.variant));
\r
3868 DisplayTitle(str);
\r
3869 if (appData.debugMode) {
\r
3870 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3875 /* Display the board */
\r
3878 if (appData.premove)
\r
3879 if (!gotPremove ||
\r
3880 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3881 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3882 ClearPremoveHighlights();
\r
3884 DrawPosition(FALSE, boards[currentMove]);
\r
3885 DisplayMove(moveNum - 1);
\r
3886 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3890 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3892 if(bookHit) { // [HGM] book: simulate book reply
\r
3893 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3895 programStats.nodes = programStats.depth = programStats.time =
\r
3896 programStats.score = programStats.got_only_move = 0;
\r
3897 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3899 strcpy(bookMove, "move ");
\r
3900 strcat(bookMove, bookHit);
\r
3901 HandleMachineMove(bookMove, &first);
\r
3907 GetMoveListEvent()
\r
3909 char buf[MSG_SIZ];
\r
3910 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3911 ics_getting_history = H_REQUESTED;
\r
3912 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3918 AnalysisPeriodicEvent(force)
\r
3921 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3922 && !force) || !appData.periodicUpdates)
\r
3925 /* Send . command to Crafty to collect stats */
\r
3926 SendToProgram(".\n", &first);
\r
3928 /* Don't send another until we get a response (this makes
\r
3929 us stop sending to old Crafty's which don't understand
\r
3930 the "." command (sending illegal cmds resets node count & time,
\r
3931 which looks bad)) */
\r
3932 programStats.ok_to_send = 0;
\r
3936 SendMoveToProgram(moveNum, cps)
\r
3938 ChessProgramState *cps;
\r
3940 char buf[MSG_SIZ];
\r
3942 if (cps->useUsermove) {
\r
3943 SendToProgram("usermove ", cps);
\r
3945 if (cps->useSAN) {
\r
3947 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3948 int len = space - parseList[moveNum];
\r
3949 memcpy(buf, parseList[moveNum], len);
\r
3950 buf[len++] = '\n';
\r
3951 buf[len] = NULLCHAR;
\r
3953 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3955 SendToProgram(buf, cps);
\r
3957 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3958 AlphaRank(moveList[moveNum], 4);
\r
3959 SendToProgram(moveList[moveNum], cps);
\r
3960 AlphaRank(moveList[moveNum], 4); // and back
\r
3962 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3963 * the engine. It would be nice to have a better way to identify castle
\r
3965 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3966 && cps->useOOCastle) {
\r
3967 int fromX = moveList[moveNum][0] - AAA;
\r
3968 int fromY = moveList[moveNum][1] - ONE;
\r
3969 int toX = moveList[moveNum][2] - AAA;
\r
3970 int toY = moveList[moveNum][3] - ONE;
\r
3971 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3972 && boards[moveNum][toY][toX] == WhiteRook)
\r
3973 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3974 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3975 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3976 else SendToProgram("O-O-O\n", cps);
\r
3978 else SendToProgram(moveList[moveNum], cps);
\r
3980 else SendToProgram(moveList[moveNum], cps);
\r
3981 /* End of additions by Tord */
\r
3984 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3985 /* Send 'go' if we are in a mode where machine should play. */
\r
3986 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3987 (gameMode == TwoMachinesPlay ||
\r
3989 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3991 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3992 SendToProgram("go\n", cps);
\r
3993 if (appData.debugMode) {
\r
3994 fprintf(debugFP, "(extra)\n");
\r
3997 setboardSpoiledMachineBlack = 0;
\r
4001 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
4002 ChessMove moveType;
\r
4003 int fromX, fromY, toX, toY;
\r
4005 char user_move[MSG_SIZ];
\r
4007 switch (moveType) {
\r
4009 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
4010 (int)moveType, fromX, fromY, toX, toY);
\r
4011 DisplayError(user_move + strlen("say "), 0);
\r
4013 case WhiteKingSideCastle:
\r
4014 case BlackKingSideCastle:
\r
4015 case WhiteQueenSideCastleWild:
\r
4016 case BlackQueenSideCastleWild:
\r
4018 case WhiteHSideCastleFR:
\r
4019 case BlackHSideCastleFR:
\r
4021 sprintf(user_move, "o-o\n");
\r
4023 case WhiteQueenSideCastle:
\r
4024 case BlackQueenSideCastle:
\r
4025 case WhiteKingSideCastleWild:
\r
4026 case BlackKingSideCastleWild:
\r
4028 case WhiteASideCastleFR:
\r
4029 case BlackASideCastleFR:
\r
4031 sprintf(user_move, "o-o-o\n");
\r
4033 case WhitePromotionQueen:
\r
4034 case BlackPromotionQueen:
\r
4035 case WhitePromotionRook:
\r
4036 case BlackPromotionRook:
\r
4037 case WhitePromotionBishop:
\r
4038 case BlackPromotionBishop:
\r
4039 case WhitePromotionKnight:
\r
4040 case BlackPromotionKnight:
\r
4041 case WhitePromotionKing:
\r
4042 case BlackPromotionKing:
\r
4043 case WhitePromotionChancellor:
\r
4044 case BlackPromotionChancellor:
\r
4045 case WhitePromotionArchbishop:
\r
4046 case BlackPromotionArchbishop:
\r
4047 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4048 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4049 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4050 PieceToChar(WhiteFerz));
\r
4051 else if(gameInfo.variant == VariantGreat)
\r
4052 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4053 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4054 PieceToChar(WhiteMan));
\r
4056 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4057 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4058 PieceToChar(PromoPiece(moveType)));
\r
4062 sprintf(user_move, "%c@%c%c\n",
\r
4063 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4064 AAA + toX, ONE + toY);
\r
4067 case WhiteCapturesEnPassant:
\r
4068 case BlackCapturesEnPassant:
\r
4069 case IllegalMove: /* could be a variant we don't quite understand */
\r
4070 sprintf(user_move, "%c%c%c%c\n",
\r
4071 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4074 SendToICS(user_move);
\r
4078 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4079 int rf, ff, rt, ft;
\r
4083 if (rf == DROP_RANK) {
\r
4084 sprintf(move, "%c@%c%c\n",
\r
4085 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4087 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4088 sprintf(move, "%c%c%c%c\n",
\r
4089 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4091 sprintf(move, "%c%c%c%c%c\n",
\r
4092 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4098 ProcessICSInitScript(f)
\r
4101 char buf[MSG_SIZ];
\r
4103 while (fgets(buf, MSG_SIZ, f)) {
\r
4104 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4111 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4113 AlphaRank(char *move, int n)
\r
4115 // char *p = move, c; int x, y;
\r
4117 if (appData.debugMode) {
\r
4118 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4121 if(move[1]=='*' &&
\r
4122 move[2]>='0' && move[2]<='9' &&
\r
4123 move[3]>='a' && move[3]<='x' ) {
\r
4125 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4126 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4128 if(move[0]>='0' && move[0]<='9' &&
\r
4129 move[1]>='a' && move[1]<='x' &&
\r
4130 move[2]>='0' && move[2]<='9' &&
\r
4131 move[3]>='a' && move[3]<='x' ) {
\r
4132 /* input move, Shogi -> normal */
\r
4133 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4134 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4135 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4136 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4138 if(move[1]=='@' &&
\r
4139 move[3]>='0' && move[3]<='9' &&
\r
4140 move[2]>='a' && move[2]<='x' ) {
\r
4142 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4143 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4146 move[0]>='a' && move[0]<='x' &&
\r
4147 move[3]>='0' && move[3]<='9' &&
\r
4148 move[2]>='a' && move[2]<='x' ) {
\r
4149 /* output move, normal -> Shogi */
\r
4150 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4151 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4152 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4153 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4154 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4156 if (appData.debugMode) {
\r
4157 fprintf(debugFP, " out = '%s'\n", move);
\r
4161 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4163 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4166 ChessMove *moveType;
\r
4167 int *fromX, *fromY, *toX, *toY;
\r
4170 if (appData.debugMode) {
\r
4171 fprintf(debugFP, "move to parse: %s\n", move);
\r
4173 *moveType = yylexstr(moveNum, move);
\r
4175 switch (*moveType) {
\r
4176 case WhitePromotionChancellor:
\r
4177 case BlackPromotionChancellor:
\r
4178 case WhitePromotionArchbishop:
\r
4179 case BlackPromotionArchbishop:
\r
4180 case WhitePromotionQueen:
\r
4181 case BlackPromotionQueen:
\r
4182 case WhitePromotionRook:
\r
4183 case BlackPromotionRook:
\r
4184 case WhitePromotionBishop:
\r
4185 case BlackPromotionBishop:
\r
4186 case WhitePromotionKnight:
\r
4187 case BlackPromotionKnight:
\r
4188 case WhitePromotionKing:
\r
4189 case BlackPromotionKing:
\r
4191 case WhiteCapturesEnPassant:
\r
4192 case BlackCapturesEnPassant:
\r
4193 case WhiteKingSideCastle:
\r
4194 case WhiteQueenSideCastle:
\r
4195 case BlackKingSideCastle:
\r
4196 case BlackQueenSideCastle:
\r
4197 case WhiteKingSideCastleWild:
\r
4198 case WhiteQueenSideCastleWild:
\r
4199 case BlackKingSideCastleWild:
\r
4200 case BlackQueenSideCastleWild:
\r
4201 /* Code added by Tord: */
\r
4202 case WhiteHSideCastleFR:
\r
4203 case WhiteASideCastleFR:
\r
4204 case BlackHSideCastleFR:
\r
4205 case BlackASideCastleFR:
\r
4206 /* End of code added by Tord */
\r
4207 case IllegalMove: /* bug or odd chess variant */
\r
4208 *fromX = currentMoveString[0] - AAA;
\r
4209 *fromY = currentMoveString[1] - ONE;
\r
4210 *toX = currentMoveString[2] - AAA;
\r
4211 *toY = currentMoveString[3] - ONE;
\r
4212 *promoChar = currentMoveString[4];
\r
4213 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4214 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4215 if (appData.debugMode) {
\r
4216 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4218 *fromX = *fromY = *toX = *toY = 0;
\r
4221 if (appData.testLegality) {
\r
4222 return (*moveType != IllegalMove);
\r
4224 return !(fromX == fromY && toX == toY);
\r
4229 *fromX = *moveType == WhiteDrop ?
\r
4230 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4231 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4232 *fromY = DROP_RANK;
\r
4233 *toX = currentMoveString[2] - AAA;
\r
4234 *toY = currentMoveString[3] - ONE;
\r
4235 *promoChar = NULLCHAR;
\r
4238 case AmbiguousMove:
\r
4239 case ImpossibleMove:
\r
4240 case (ChessMove) 0: /* end of file */
\r
4249 if (appData.debugMode) {
\r
4250 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4253 *fromX = *fromY = *toX = *toY = 0;
\r
4254 *promoChar = NULLCHAR;
\r
4259 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
\r
4260 // All positions will have equal probability, but the current method will not provide a unique
\r
4261 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4266 int squaresLeft[4];
\r
4267 int piecesLeft[(int)BlackPawn];
\r
4268 int seed, nrOfShuffles;
\r
4270 void GetPositionNumber()
\r
4271 { // sets global variable seed
\r
4274 seed = appData.defaultFrcPosition;
\r
4275 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4276 for(i=0; i<50; i++) seed += random();
\r
4277 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4278 if(seed<0) seed = -seed;
\r
4282 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4283 // put the piece on the (n-1)-th empty squares of the given shade
\r
4287 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4288 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4289 board[rank][i] = (ChessSquare) pieceType;
\r
4290 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
\r
4291 squaresLeft[ANY]--;
\r
4292 piecesLeft[pieceType]--;
\r
4300 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4301 // calculate where the next piece goes, (any empty square), and put it there
\r
4305 i = seed % squaresLeft[shade];
\r
4306 nrOfShuffles *= squaresLeft[shade];
\r
4307 seed /= squaresLeft[shade];
\r
4308 put(board, pieceType, rank, i, shade);
\r
4311 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4312 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4314 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4316 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4317 i = seed % k; // pick one
\r
4318 nrOfShuffles *= k;
\r
4320 while(i >= j) i -= j--;
\r
4321 j = n - 1 - j; i += j;
\r
4322 put(board, pieceType, rank, j, ANY);
\r
4323 put(board, pieceType, rank, i, ANY);
\r
4326 void SetUpShuffle(Board board, int number)
\r
4328 int i, p, first=1;
\r
4330 GetPositionNumber(); nrOfShuffles = 1;
\r
4332 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4333 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4334 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4336 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4338 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4339 p = (int) board[0][i];
\r
4340 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4341 board[0][i] = EmptySquare;
\r
4344 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4345 // shuffles restricted to allow normal castling put KRR first
\r
4346 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4347 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4348 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4349 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4350 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4351 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4352 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4353 put(board, WhiteRook, 0, 0, ANY);
\r
4354 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4357 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
\r
4358 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4359 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4360 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4361 while(piecesLeft[p] >= 2) {
\r
4362 AddOnePiece(board, p, 0, LITE);
\r
4363 AddOnePiece(board, p, 0, DARK);
\r
4365 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4368 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4369 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4370 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4371 if(p == (int)WhiteRook) continue;
\r
4372 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4373 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4376 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4378 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4379 // Last King gets castling rights
\r
4380 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4381 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4382 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4385 while(piecesLeft[(int)WhiteKing]) {
\r
4386 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4387 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4392 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4393 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4396 // Only Rooks can be left; simply place them all
\r
4397 while(piecesLeft[(int)WhiteRook]) {
\r
4398 i = put(board, WhiteRook, 0, 0, ANY);
\r
4399 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4402 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4404 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4407 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4408 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4411 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4414 int SetCharTable( char *table, const char * map )
\r
4415 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4416 /* Basically a safe strcpy that uses the last character as King */
\r
4418 int result = FALSE; int NrPieces;
\r
4420 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4421 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4422 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4424 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4425 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4426 table[i] = map[i];
\r
4427 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4429 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4430 table[(int) BlackKing] = map[NrPieces-1];
\r
4438 void Prelude(Board board)
\r
4439 { // [HGM] superchess: random selection of exo-pieces
\r
4440 int i, j, k; ChessSquare p;
\r
4441 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4443 GetPositionNumber(); // use FRC position number
\r
4445 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4446 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4447 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4448 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4451 j = seed%4; seed /= 4;
\r
4452 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4453 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4454 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4455 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4456 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4457 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4458 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4459 j = seed%3; seed /= 3;
\r
4460 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4461 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4462 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4463 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4464 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4465 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4466 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4467 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4468 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4469 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4470 put(board, exoPieces[0], 0, 0, ANY);
\r
4471 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4475 InitPosition(redraw)
\r
4478 ChessSquare (* pieces)[BOARD_SIZE];
\r
4479 int i, j, pawnRow, overrule,
\r
4480 oldx = gameInfo.boardWidth,
\r
4481 oldy = gameInfo.boardHeight,
\r
4482 oldh = gameInfo.holdingsWidth,
\r
4483 oldv = gameInfo.variant;
\r
4485 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4486 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4488 /* [AS] Initialize pv info list [HGM] and game status */
\r
4490 for( i=0; i<MAX_MOVES; i++ ) {
\r
4491 pvInfoList[i].depth = 0;
\r
4492 epStatus[i]=EP_NONE;
\r
4493 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4496 initialRulePlies = 0; /* 50-move counter start */
\r
4498 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4499 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4503 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4504 /* the initialized data only consist of the two backranks. The switch */
\r
4505 /* selects which one we will use, which is than copied to the Board */
\r
4506 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4507 /* empty squares. This initial position is then copied to boards[0], */
\r
4508 /* possibly after shuffling, so that it remains available. */
\r
4510 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4511 gameInfo.boardWidth = 8;
\r
4512 gameInfo.boardHeight = 8;
\r
4513 gameInfo.holdingsSize = 0;
\r
4514 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4515 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4516 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4518 switch (gameInfo.variant) {
\r
4519 case VariantFischeRandom:
\r
4520 shuffleOpenings = TRUE;
\r
4522 pieces = FIDEArray;
\r
4524 case VariantShatranj:
\r
4525 pieces = ShatranjArray;
\r
4526 nrCastlingRights = 0;
\r
4527 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4529 case VariantTwoKings:
\r
4530 pieces = twoKingsArray;
\r
4532 case VariantCapaRandom:
\r
4533 shuffleOpenings = TRUE;
\r
4534 case VariantCapablanca:
\r
4535 pieces = CapablancaArray;
\r
4536 gameInfo.boardWidth = 10;
\r
4537 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4539 case VariantGothic:
\r
4540 pieces = GothicArray;
\r
4541 gameInfo.boardWidth = 10;
\r
4542 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4544 case VariantJanus:
\r
4545 pieces = JanusArray;
\r
4546 gameInfo.boardWidth = 10;
\r
4547 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4548 nrCastlingRights = 6;
\r
4549 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4550 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4551 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
\r
4552 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4553 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4554 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
\r
4556 case VariantFalcon:
\r
4557 pieces = FalconArray;
\r
4558 gameInfo.boardWidth = 10;
\r
4559 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4561 case VariantXiangqi:
\r
4562 pieces = XiangqiArray;
\r
4563 gameInfo.boardWidth = 9;
\r
4564 gameInfo.boardHeight = 10;
\r
4565 nrCastlingRights = 0;
\r
4566 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4568 case VariantShogi:
\r
4569 pieces = ShogiArray;
\r
4570 gameInfo.boardWidth = 9;
\r
4571 gameInfo.boardHeight = 9;
\r
4572 gameInfo.holdingsSize = 7;
\r
4573 nrCastlingRights = 0;
\r
4574 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4576 case VariantCourier:
\r
4577 pieces = CourierArray;
\r
4578 gameInfo.boardWidth = 12;
\r
4579 nrCastlingRights = 0;
\r
4580 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4581 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4583 case VariantKnightmate:
\r
4584 pieces = KnightmateArray;
\r
4585 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4587 case VariantFairy:
\r
4588 pieces = fairyArray;
\r
4589 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4591 case VariantGreat:
\r
4592 pieces = GreatArray;
\r
4593 gameInfo.boardWidth = 10;
\r
4594 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4595 gameInfo.holdingsSize = 8;
\r
4597 case VariantSuper:
\r
4598 pieces = FIDEArray;
\r
4599 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4600 gameInfo.holdingsSize = 8;
\r
4601 startedFromSetupPosition = TRUE;
\r
4603 case VariantCrazyhouse:
\r
4604 case VariantBughouse:
\r
4605 pieces = FIDEArray;
\r
4606 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4607 gameInfo.holdingsSize = 5;
\r
4609 case VariantWildCastle:
\r
4610 pieces = FIDEArray;
\r
4611 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4612 shuffleOpenings = 1;
\r
4614 case VariantNoCastle:
\r
4615 pieces = FIDEArray;
\r
4616 nrCastlingRights = 0;
\r
4617 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4618 /* !!?unconstrained back-rank shuffle */
\r
4619 shuffleOpenings = 1;
\r
4624 if(appData.NrFiles >= 0) {
\r
4625 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4626 gameInfo.boardWidth = appData.NrFiles;
\r
4628 if(appData.NrRanks >= 0) {
\r
4629 gameInfo.boardHeight = appData.NrRanks;
\r
4631 if(appData.holdingsSize >= 0) {
\r
4632 i = appData.holdingsSize;
\r
4633 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4634 gameInfo.holdingsSize = i;
\r
4636 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4637 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4638 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4640 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4641 if(pawnRow < 1) pawnRow = 1;
\r
4643 /* User pieceToChar list overrules defaults */
\r
4644 if(appData.pieceToCharTable != NULL)
\r
4645 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4647 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4649 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4650 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4651 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4652 initialPosition[i][j] = s;
\r
4654 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4655 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4656 initialPosition[pawnRow][j] = WhitePawn;
\r
4657 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4658 if(gameInfo.variant == VariantXiangqi) {
\r
4660 initialPosition[pawnRow][j] =
\r
4661 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4662 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4663 initialPosition[2][j] = WhiteCannon;
\r
4664 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4668 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4670 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4673 initialPosition[1][j] = WhiteBishop;
\r
4674 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4676 initialPosition[1][j] = WhiteRook;
\r
4677 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4680 if( nrCastlingRights == -1) {
\r
4681 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4682 /* This sets default castling rights from none to normal corners */
\r
4683 /* Variants with other castling rights must set them themselves above */
\r
4684 nrCastlingRights = 6;
\r
4686 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4687 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4688 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4689 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4690 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4691 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4694 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4695 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4696 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
\r
4697 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
\r
4698 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4699 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4702 if(gameInfo.variant == VariantFischeRandom) {
\r
4703 if( appData.defaultFrcPosition < 0 ) {
\r
4704 ShuffleFRC( initialPosition );
\r
4707 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4709 startedFromSetupPosition = TRUE;
\r
4712 if (appData.debugMode) {
\r
4713 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4715 if(shuffleOpenings) {
\r
4716 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4717 startedFromSetupPosition = TRUE;
\r
4720 if(startedFromPositionFile) {
\r
4721 /* [HGM] loadPos: use PositionFile for every new game */
\r
4722 CopyBoard(initialPosition, filePosition);
\r
4723 for(i=0; i<nrCastlingRights; i++)
\r
4724 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4725 startedFromSetupPosition = TRUE;
\r
4728 CopyBoard(boards[0], initialPosition);
\r
4730 if(oldx != gameInfo.boardWidth ||
\r
4731 oldy != gameInfo.boardHeight ||
\r
4732 oldh != gameInfo.holdingsWidth
\r
4734 || oldv == VariantGothic || // For licensing popups
\r
4735 gameInfo.variant == VariantGothic
\r
4738 || oldv == VariantFalcon ||
\r
4739 gameInfo.variant == VariantFalcon
\r
4742 InitDrawingSizes(-2 ,0);
\r
4745 DrawPosition(TRUE, boards[currentMove]);
\r
4749 SendBoard(cps, moveNum)
\r
4750 ChessProgramState *cps;
\r
4753 char message[MSG_SIZ];
\r
4755 if (cps->useSetboard) {
\r
4756 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4757 sprintf(message, "setboard %s\n", fen);
\r
4758 SendToProgram(message, cps);
\r
4764 /* Kludge to set black to move, avoiding the troublesome and now
\r
4765 * deprecated "black" command.
\r
4767 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4769 SendToProgram("edit\n", cps);
\r
4770 SendToProgram("#\n", cps);
\r
4771 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4772 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4773 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4774 if ((int) *bp < (int) BlackPawn) {
\r
4775 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4776 AAA + j, ONE + i);
\r
4777 if(message[0] == '+' || message[0] == '~') {
\r
4778 sprintf(message, "%c%c%c+\n",
\r
4779 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4780 AAA + j, ONE + i);
\r
4782 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4783 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4784 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4786 SendToProgram(message, cps);
\r
4791 SendToProgram("c\n", cps);
\r
4792 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4793 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4794 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4795 if (((int) *bp != (int) EmptySquare)
\r
4796 && ((int) *bp >= (int) BlackPawn)) {
\r
4797 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4798 AAA + j, ONE + i);
\r
4799 if(message[0] == '+' || message[0] == '~') {
\r
4800 sprintf(message, "%c%c%c+\n",
\r
4801 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4802 AAA + j, ONE + i);
\r
4804 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4805 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4806 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4808 SendToProgram(message, cps);
\r
4813 SendToProgram(".\n", cps);
\r
4815 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4819 IsPromotion(fromX, fromY, toX, toY)
\r
4820 int fromX, fromY, toX, toY;
\r
4822 /* [HGM] add Shogi promotions */
\r
4823 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4824 ChessSquare piece;
\r
4826 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4827 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4828 /* [HGM] Note to self: line above also weeds out drops */
\r
4829 piece = boards[currentMove][fromY][fromX];
\r
4830 if(gameInfo.variant == VariantShogi) {
\r
4831 promotionZoneSize = 3;
\r
4832 highestPromotingPiece = (int)WhiteKing;
\r
4833 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4834 and if in normal chess we then allow promotion to King, why not
\r
4835 allow promotion of other piece in Shogi? */
\r
4837 if((int)piece >= BlackPawn) {
\r
4838 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4840 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4842 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4843 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4845 return ( (int)piece <= highestPromotingPiece );
\r
4849 InPalace(row, column)
\r
4851 { /* [HGM] for Xiangqi */
\r
4852 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4853 column < (BOARD_WIDTH + 4)/2 &&
\r
4854 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4859 PieceForSquare (x, y)
\r
4863 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4866 return boards[currentMove][y][x];
\r
4870 OKToStartUserMove(x, y)
\r
4873 ChessSquare from_piece;
\r
4876 if (matchMode) return FALSE;
\r
4877 if (gameMode == EditPosition) return TRUE;
\r
4879 if (x >= 0 && y >= 0)
\r
4880 from_piece = boards[currentMove][y][x];
\r
4882 from_piece = EmptySquare;
\r
4884 if (from_piece == EmptySquare) return FALSE;
\r
4886 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4887 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4889 switch (gameMode) {
\r
4890 case PlayFromGameFile:
\r
4892 case TwoMachinesPlay:
\r
4896 case IcsObserving:
\r
4900 case MachinePlaysWhite:
\r
4901 case IcsPlayingBlack:
\r
4902 if (appData.zippyPlay) return FALSE;
\r
4903 if (white_piece) {
\r
4904 DisplayMoveError(_("You are playing Black"));
\r
4909 case MachinePlaysBlack:
\r
4910 case IcsPlayingWhite:
\r
4911 if (appData.zippyPlay) return FALSE;
\r
4912 if (!white_piece) {
\r
4913 DisplayMoveError(_("You are playing White"));
\r
4919 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4920 DisplayMoveError(_("It is White's turn"));
\r
4923 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4924 DisplayMoveError(_("It is Black's turn"));
\r
4927 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4928 /* Editing correspondence game history */
\r
4929 /* Could disallow this or prompt for confirmation */
\r
4930 cmailOldMove = -1;
\r
4932 if (currentMove < forwardMostMove) {
\r
4933 /* Discarding moves */
\r
4934 /* Could prompt for confirmation here,
\r
4935 but I don't think that's such a good idea */
\r
4936 forwardMostMove = currentMove;
\r
4940 case BeginningOfGame:
\r
4941 if (appData.icsActive) return FALSE;
\r
4942 if (!appData.noChessProgram) {
\r
4943 if (!white_piece) {
\r
4944 DisplayMoveError(_("You are playing White"));
\r
4951 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4952 DisplayMoveError(_("It is White's turn"));
\r
4955 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4956 DisplayMoveError(_("It is Black's turn"));
\r
4962 case IcsExamining:
\r
4965 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4966 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4967 DisplayMoveError(_("Displayed position is not current"));
\r
4973 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4974 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4975 int lastLoadGameUseList = FALSE;
\r
4976 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4977 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4981 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4982 int fromX, fromY, toX, toY;
\r
4985 ChessMove moveType;
\r
4986 ChessSquare pdown, pup;
\r
4988 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4989 if ((fromX == toX) && (fromY == toY)) {
\r
4990 return ImpossibleMove;
\r
4993 /* [HGM] suppress all moves into holdings area and guard band */
\r
4994 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4995 return ImpossibleMove;
\r
4997 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4998 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
4999 pdown = boards[currentMove][fromY][fromX];
\r
5000 pup = boards[currentMove][toY][toX];
\r
5001 if ( gameMode != EditPosition &&
\r
5002 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5003 WhitePawn <= pup && pup < BlackPawn ||
\r
5004 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5005 BlackPawn <= pup && pup < EmptySquare
\r
5006 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5007 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5008 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5010 return ImpossibleMove;
\r
5012 /* Check if the user is playing in turn. This is complicated because we
\r
5013 let the user "pick up" a piece before it is his turn. So the piece he
\r
5014 tried to pick up may have been captured by the time he puts it down!
\r
5015 Therefore we use the color the user is supposed to be playing in this
\r
5016 test, not the color of the piece that is currently on the starting
\r
5017 square---except in EditGame mode, where the user is playing both
\r
5018 sides; fortunately there the capture race can't happen. (It can
\r
5019 now happen in IcsExamining mode, but that's just too bad. The user
\r
5020 will get a somewhat confusing message in that case.)
\r
5023 switch (gameMode) {
\r
5024 case PlayFromGameFile:
\r
5026 case TwoMachinesPlay:
\r
5028 case IcsObserving:
\r
5030 /* We switched into a game mode where moves are not accepted,
\r
5031 perhaps while the mouse button was down. */
\r
5032 return ImpossibleMove;
\r
5034 case MachinePlaysWhite:
\r
5035 /* User is moving for Black */
\r
5036 if (WhiteOnMove(currentMove)) {
\r
5037 DisplayMoveError(_("It is White's turn"));
\r
5038 return ImpossibleMove;
\r
5042 case MachinePlaysBlack:
\r
5043 /* User is moving for White */
\r
5044 if (!WhiteOnMove(currentMove)) {
\r
5045 DisplayMoveError(_("It is Black's turn"));
\r
5046 return ImpossibleMove;
\r
5051 case IcsExamining:
\r
5052 case BeginningOfGame:
\r
5055 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5056 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5057 /* User is moving for Black */
\r
5058 if (WhiteOnMove(currentMove)) {
\r
5059 DisplayMoveError(_("It is White's turn"));
\r
5060 return ImpossibleMove;
\r
5063 /* User is moving for White */
\r
5064 if (!WhiteOnMove(currentMove)) {
\r
5065 DisplayMoveError(_("It is Black's turn"));
\r
5066 return ImpossibleMove;
\r
5071 case IcsPlayingBlack:
\r
5072 /* User is moving for Black */
\r
5073 if (WhiteOnMove(currentMove)) {
\r
5074 if (!appData.premove) {
\r
5075 DisplayMoveError(_("It is White's turn"));
\r
5076 } else if (toX >= 0 && toY >= 0) {
\r
5079 premoveFromX = fromX;
\r
5080 premoveFromY = fromY;
\r
5081 premovePromoChar = promoChar;
\r
5083 if (appData.debugMode)
\r
5084 fprintf(debugFP, "Got premove: fromX %d,"
\r
5085 "fromY %d, toX %d, toY %d\n",
\r
5086 fromX, fromY, toX, toY);
\r
5088 return ImpossibleMove;
\r
5092 case IcsPlayingWhite:
\r
5093 /* User is moving for White */
\r
5094 if (!WhiteOnMove(currentMove)) {
\r
5095 if (!appData.premove) {
\r
5096 DisplayMoveError(_("It is Black's turn"));
\r
5097 } else if (toX >= 0 && toY >= 0) {
\r
5100 premoveFromX = fromX;
\r
5101 premoveFromY = fromY;
\r
5102 premovePromoChar = promoChar;
\r
5104 if (appData.debugMode)
\r
5105 fprintf(debugFP, "Got premove: fromX %d,"
\r
5106 "fromY %d, toX %d, toY %d\n",
\r
5107 fromX, fromY, toX, toY);
\r
5109 return ImpossibleMove;
\r
5116 case EditPosition:
\r
5117 /* EditPosition, empty square, or different color piece;
\r
5118 click-click move is possible */
\r
5119 if (toX == -2 || toY == -2) {
\r
5120 boards[0][fromY][fromX] = EmptySquare;
\r
5121 return AmbiguousMove;
\r
5122 } else if (toX >= 0 && toY >= 0) {
\r
5123 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5124 boards[0][fromY][fromX] = EmptySquare;
\r
5125 return AmbiguousMove;
\r
5127 return ImpossibleMove;
\r
5130 /* [HGM] If move started in holdings, it means a drop */
\r
5131 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5132 if( pup != EmptySquare ) return ImpossibleMove;
\r
5133 if(appData.testLegality) {
\r
5134 /* it would be more logical if LegalityTest() also figured out
\r
5135 * which drops are legal. For now we forbid pawns on back rank.
\r
5136 * Shogi is on its own here...
\r
5138 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5139 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5140 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5142 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5145 userOfferedDraw = FALSE;
\r
5147 /* [HGM] always test for legality, to get promotion info */
\r
5148 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5149 epStatus[currentMove], castlingRights[currentMove],
\r
5150 fromY, fromX, toY, toX, promoChar);
\r
5152 /* [HGM] but possibly ignore an IllegalMove result */
\r
5153 if (appData.testLegality) {
\r
5154 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5155 DisplayMoveError(_("Illegal move"));
\r
5156 return ImpossibleMove;
\r
5159 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5161 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5162 function is made into one that returns an OK move type if FinishMove
\r
5163 should be called. This to give the calling driver routine the
\r
5164 opportunity to finish the userMove input with a promotion popup,
\r
5165 without bothering the user with this for invalid or illegal moves */
\r
5167 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5170 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5172 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5173 ChessMove moveType;
\r
5174 int fromX, fromY, toX, toY;
\r
5175 /*char*/int promoChar;
\r
5177 char *bookHit = 0;
\r
5178 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5179 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5180 // [HGM] superchess: suppress promotions to non-available piece
\r
5181 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5182 if(WhiteOnMove(currentMove)) {
\r
5183 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5185 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5189 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5190 move type in caller when we know the move is a legal promotion */
\r
5191 if(moveType == NormalMove && promoChar)
\r
5192 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5193 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5194 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5195 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5196 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5197 fromX = boards[currentMove][fromY][fromX];
\r
5198 fromY = DROP_RANK;
\r
5201 /* [HGM] <popupFix> The following if has been moved here from
\r
5202 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5203 piece drops in training games?), and because it can only be
\r
5204 performed after it is known to what we promote. */
\r
5205 if (gameMode == Training) {
\r
5206 /* compare the move played on the board to the next move in the
\r
5207 * game. If they match, display the move and the opponent's response.
\r
5208 * If they don't match, display an error message.
\r
5212 CopyBoard(testBoard, boards[currentMove]);
\r
5213 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5215 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5216 ForwardInner(currentMove+1);
\r
5218 /* Autoplay the opponent's response.
\r
5219 * if appData.animate was TRUE when Training mode was entered,
\r
5220 * the response will be animated.
\r
5222 saveAnimate = appData.animate;
\r
5223 appData.animate = animateTraining;
\r
5224 ForwardInner(currentMove+1);
\r
5225 appData.animate = saveAnimate;
\r
5227 /* check for the end of the game */
\r
5228 if (currentMove >= forwardMostMove) {
\r
5229 gameMode = PlayFromGameFile;
\r
5231 SetTrainingModeOff();
\r
5232 DisplayInformation(_("End of game"));
\r
5235 DisplayError(_("Incorrect move"), 0);
\r
5240 /* Ok, now we know that the move is good, so we can kill
\r
5241 the previous line in Analysis Mode */
\r
5242 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5243 forwardMostMove = currentMove;
\r
5246 /* If we need the chess program but it's dead, restart it */
\r
5247 ResurrectChessProgram();
\r
5249 /* A user move restarts a paused game*/
\r
5253 thinkOutput[0] = NULLCHAR;
\r
5255 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5257 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5258 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5259 // [HGM] superchess: take promotion piece out of holdings
\r
5260 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5261 if(WhiteOnMove(forwardMostMove-1)) {
\r
5262 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5263 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5265 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5266 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5270 if (gameMode == BeginningOfGame) {
\r
5271 if (appData.noChessProgram) {
\r
5272 gameMode = EditGame;
\r
5275 char buf[MSG_SIZ];
\r
5276 gameMode = MachinePlaysBlack;
\r
5279 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5280 DisplayTitle(buf);
\r
5281 if (first.sendName) {
\r
5282 sprintf(buf, "name %s\n", gameInfo.white);
\r
5283 SendToProgram(buf, &first);
\r
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5290 /* Relay move to ICS or chess engine */
\r
5291 if (appData.icsActive) {
\r
5292 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5293 gameMode == IcsExamining) {
\r
5294 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5295 ics_user_moved = 1;
\r
5298 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5299 gameMode == MachinePlaysWhite ||
\r
5300 gameMode == MachinePlaysBlack)) {
\r
5301 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5303 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5304 // [HGM] book: if program might be playing, let it use book
\r
5305 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5306 first.maybeThinking = TRUE;
\r
5307 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5308 if (currentMove == cmailOldMove + 1) {
\r
5309 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5313 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5315 switch (gameMode) {
\r
5317 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5318 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5322 case MT_CHECKMATE:
\r
5323 if (WhiteOnMove(currentMove)) {
\r
5324 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5326 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5329 case MT_STALEMATE:
\r
5330 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5335 case MachinePlaysBlack:
\r
5336 case MachinePlaysWhite:
\r
5337 /* disable certain menu options while machine is thinking */
\r
5338 SetMachineThinkingEnables();
\r
5345 if(bookHit) { // [HGM] book: simulate book reply
\r
5346 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5348 programStats.nodes = programStats.depth = programStats.time =
\r
5349 programStats.score = programStats.got_only_move = 0;
\r
5350 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5352 strcpy(bookMove, "move ");
\r
5353 strcat(bookMove, bookHit);
\r
5354 HandleMachineMove(bookMove, &first);
\r
5360 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5361 int fromX, fromY, toX, toY;
\r
5364 /* [HGM] This routine was added to allow calling of its two logical
\r
5365 parts from other modules in the old way. Before, UserMoveEvent()
\r
5366 automatically called FinishMove() if the move was OK, and returned
\r
5367 otherwise. I separated the two, in order to make it possible to
\r
5368 slip a promotion popup in between. But that it always needs two
\r
5369 calls, to the first part, (now called UserMoveTest() ), and to
\r
5370 FinishMove if the first part succeeded. Calls that do not need
\r
5371 to do anything in between, can call this routine the old way.
\r
5373 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5374 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5375 if(moveType != ImpossibleMove)
\r
5376 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5379 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5381 // char * hint = lastHint;
\r
5382 FrontEndProgramStats stats;
\r
5384 stats.which = cps == &first ? 0 : 1;
\r
5385 stats.depth = cpstats->depth;
\r
5386 stats.nodes = cpstats->nodes;
\r
5387 stats.score = cpstats->score;
\r
5388 stats.time = cpstats->time;
\r
5389 stats.pv = cpstats->movelist;
\r
5390 stats.hint = lastHint;
\r
5391 stats.an_move_index = 0;
\r
5392 stats.an_move_count = 0;
\r
5394 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5395 stats.hint = cpstats->move_name;
\r
5396 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5397 stats.an_move_count = cpstats->nr_moves;
\r
5400 SetProgramStats( &stats );
\r
5403 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5404 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5405 char *bookHit = NULL;
\r
5407 //first determine if the incoming move brings opponent into his book
\r
5408 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5409 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5410 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5411 if(bookHit != NULL && !cps->bookSuspend) {
\r
5412 // make sure opponent is not going to reply after receiving move to book position
\r
5413 SendToProgram("force\n", cps);
\r
5414 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5416 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5417 // now arrange restart after book miss
\r
5419 // after a book hit we never send 'go', and the code after the call to this routine
\r
5420 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5421 char buf[MSG_SIZ];
\r
5422 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5423 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5424 SendToProgram(buf, cps);
\r
5425 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5426 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5427 SendToProgram("go\n", cps);
\r
5428 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5429 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5430 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5431 SendToProgram("go\n", cps);
\r
5432 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5434 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5437 char *savedMessage;
\r
5438 ChessProgramState *savedState;
\r
5439 void DeferredBookMove(void)
\r
5441 if(savedState->lastPing != savedState->lastPong)
\r
5442 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5444 HandleMachineMove(savedMessage, savedState);
\r
5448 HandleMachineMove(message, cps)
\r
5450 ChessProgramState *cps;
\r
5452 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5453 char realname[MSG_SIZ];
\r
5454 int fromX, fromY, toX, toY;
\r
5455 ChessMove moveType;
\r
5461 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5463 * Kludge to ignore BEL characters
\r
5465 while (*message == '\007') message++;
\r
5468 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5470 if(cps->debug && *message == '#') return;
\r
5473 * Look for book output
\r
5475 if (cps == &first && bookRequested) {
\r
5476 if (message[0] == '\t' || message[0] == ' ') {
\r
5477 /* Part of the book output is here; append it */
\r
5478 strcat(bookOutput, message);
\r
5479 strcat(bookOutput, " \n");
\r
5481 } else if (bookOutput[0] != NULLCHAR) {
\r
5482 /* All of book output has arrived; display it */
\r
5483 char *p = bookOutput;
\r
5484 while (*p != NULLCHAR) {
\r
5485 if (*p == '\t') *p = ' ';
\r
5488 DisplayInformation(bookOutput);
\r
5489 bookRequested = FALSE;
\r
5490 /* Fall through to parse the current output */
\r
5495 * Look for machine move.
\r
5497 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5498 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5500 /* This method is only useful on engines that support ping */
\r
5501 if (cps->lastPing != cps->lastPong) {
\r
5502 if (gameMode == BeginningOfGame) {
\r
5503 /* Extra move from before last new; ignore */
\r
5504 if (appData.debugMode) {
\r
5505 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5508 if (appData.debugMode) {
\r
5509 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5510 cps->which, gameMode);
\r
5513 SendToProgram("undo\n", cps);
\r
5518 switch (gameMode) {
\r
5519 case BeginningOfGame:
\r
5520 /* Extra move from before last reset; ignore */
\r
5521 if (appData.debugMode) {
\r
5522 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5529 /* Extra move after we tried to stop. The mode test is
\r
5530 not a reliable way of detecting this problem, but it's
\r
5531 the best we can do on engines that don't support ping.
\r
5533 if (appData.debugMode) {
\r
5534 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5535 cps->which, gameMode);
\r
5537 SendToProgram("undo\n", cps);
\r
5540 case MachinePlaysWhite:
\r
5541 case IcsPlayingWhite:
\r
5542 machineWhite = TRUE;
\r
5545 case MachinePlaysBlack:
\r
5546 case IcsPlayingBlack:
\r
5547 machineWhite = FALSE;
\r
5550 case TwoMachinesPlay:
\r
5551 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5554 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5555 if (appData.debugMode) {
\r
5557 "Ignoring move out of turn by %s, gameMode %d"
\r
5558 ", forwardMost %d\n",
\r
5559 cps->which, gameMode, forwardMostMove);
\r
5564 if (appData.debugMode) { int f = forwardMostMove;
\r
5565 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5566 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5568 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5569 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5570 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5571 /* Machine move could not be parsed; ignore it. */
\r
5572 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5573 machineMove, cps->which);
\r
5574 DisplayError(buf1, 0);
\r
5575 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
\r
5576 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
\r
5577 if (gameMode == TwoMachinesPlay) {
\r
5578 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5584 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5585 /* So we have to redo legality test with true e.p. status here, */
\r
5586 /* to make sure an illegal e.p. capture does not slip through, */
\r
5587 /* to cause a forfeit on a justified illegal-move complaint */
\r
5588 /* of the opponent. */
\r
5589 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5590 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5592 ChessMove moveType;
\r
5593 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5594 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5595 fromY, fromX, toY, toX, promoChar);
\r
5596 if (appData.debugMode) {
\r
5598 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5599 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5600 fprintf(debugFP, "castling rights\n");
\r
5602 if(moveType == IllegalMove) {
\r
5603 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5604 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5605 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5607 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5608 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5609 when they shouldn't (like TSCP-Gothic) */
\r
5610 switch(moveType) {
\r
5611 case WhiteASideCastleFR:
\r
5612 case BlackASideCastleFR:
\r
5614 currentMoveString[2]++;
\r
5616 case WhiteHSideCastleFR:
\r
5617 case BlackHSideCastleFR:
\r
5619 currentMoveString[2]--;
\r
5621 default: ; // nothing to do, but suppresses warning of pedantic compilers
\r
5624 hintRequested = FALSE;
\r
5625 lastHint[0] = NULLCHAR;
\r
5626 bookRequested = FALSE;
\r
5627 /* Program may be pondering now */
\r
5628 cps->maybeThinking = TRUE;
\r
5629 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5630 if (cps->offeredDraw) cps->offeredDraw--;
\r
5633 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5635 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5636 ics_user_moved = 1;
\r
5637 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5638 char buf[3*MSG_SIZ];
\r
5640 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5641 programStats.depth,
\r
5642 programStats.score / 100.,
\r
5643 programStats.time / 100.,
\r
5644 u64ToDouble(programStats.nodes),
\r
5645 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
\r
5646 programStats.movelist);
\r
5651 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5652 strcpy(machineMove, currentMoveString);
\r
5653 strcat(machineMove, "\n");
\r
5654 strcpy(moveList[forwardMostMove], machineMove);
\r
5656 /* [AS] Save move info and clear stats for next move */
\r
5657 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5658 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5659 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5660 ClearProgramStats();
\r
5661 thinkOutput[0] = NULLCHAR;
\r
5662 hiddenThinkOutputState = 0;
\r
5664 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5666 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5667 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5670 while( count < adjudicateLossPlies ) {
\r
5671 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5674 score = -score; /* Flip score for winning side */
\r
5677 if( score > adjudicateLossThreshold ) {
\r
5684 if( count >= adjudicateLossPlies ) {
\r
5685 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5687 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5688 "Xboard adjudication",
\r
5695 if( gameMode == TwoMachinesPlay ) {
\r
5696 // [HGM] some adjudications useful with buggy engines
\r
5697 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5698 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5700 if(appData.testLegality)
\r
5701 // don't wait for engine to announce game end if we can judge ourselves
\r
5702 switch (MateTest(boards[forwardMostMove],
\r
5703 PosFlags(forwardMostMove), epFile,
\r
5704 castlingRights[forwardMostMove]) ) {
\r
5709 case MT_STALEMATE:
\r
5710 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5711 if(appData.checkMates) {
\r
5712 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5713 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5714 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantSuicide
\r
5715 || gameInfo.variant == VariantGiveaway) // [HGM] losers:
\r
5716 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, // stalemated side wins!
\r
5717 "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5719 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", GE_XBOARD );
\r
5723 case MT_CHECKMATE:
\r
5724 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5725 if(appData.checkMates) {
\r
5726 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5727 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5728 GameEnds( WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers) // [HGM] losers:
\r
5729 ? BlackWins : WhiteWins, // reverse the result ( A!=1 is !A for a boolean)
\r
5730 "Xboard adjudication: Checkmate", GE_XBOARD );
\r
5736 if( appData.testLegality )
\r
5737 { /* [HGM] Some more adjudications for obstinate engines */
\r
5738 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5739 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
\r
5740 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
\r
5741 static int moveCount = 6;
\r
5743 /* First absolutely insufficient mating material. Count what is on board. */
\r
5744 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5745 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5749 { /* count B,N,R and other of each side */
\r
5752 NrK++; break; // [HGM] atomic: count Kings
\r
5756 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5757 bishopsColor |= 1 << ((i^j)&1);
\r
5762 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5763 bishopsColor |= 1 << ((i^j)&1);
\r
5773 case EmptySquare:
\r
5778 PawnAdvance += m; NrPawns++;
\r
5780 NrPieces += (p != EmptySquare);
\r
5781 NrW += ((int)p < (int)BlackPawn);
\r
5782 if(gameInfo.variant == VariantXiangqi &&
\r
5783 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5784 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5785 NrW -= ((int)p < (int)BlackPawn);
\r
5789 if(gameInfo.variant == VariantAtomic && NrK < 2) {
\r
5790 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
\r
5791 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
\r
5792 if(appData.checkMates) {
\r
5793 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5794 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5795 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5796 "Xboard adjudication: King destroyed", GE_XBOARD );
\r
5801 /* Bare King in Shatranj (loses) or Losers (wins) */
\r
5802 if( NrW == 1 || NrPieces - NrW == 1) {
\r
5803 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
\r
5804 epStatus[forwardMostMove] = EP_STALEMATE; // kludge to make position claimable as win
\r
5805 if(appData.checkMates) {
\r
5806 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5807 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5808 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5809 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5813 if( gameInfo.variant == VariantShatranj && --bare < 0)
\r
5815 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as win for stm
\r
5816 if(appData.checkMates) {
\r
5817 /* but only adjudicate if adjudication enabled */
\r
5818 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
\r
5819 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5820 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5821 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5828 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5829 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
\r
5830 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5831 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5832 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5834 /* always flag draws, for judging claims */
\r
5835 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5837 if(appData.materialDraws) {
\r
5838 /* but only adjudicate them if adjudication enabled */
\r
5839 SendToProgram("force\n", cps->other); // suppress reply
\r
5840 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5841 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5842 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5847 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5848 if(NrPieces == 4 &&
\r
5849 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5850 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5851 || NrWN==2 || NrBN==2 /* KNNK */
\r
5852 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5854 if(--moveCount < 0 && appData.trivialDraws)
\r
5855 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5856 SendToProgram("force\n", cps->other); // suppress reply
\r
5857 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5858 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5859 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5862 } else moveCount = 6;
\r
5866 if (appData.debugMode) { int i;
\r
5867 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5868 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5869 appData.drawRepeats);
\r
5870 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5871 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5875 /* Check for rep-draws */
\r
5877 for(k = forwardMostMove-2;
\r
5878 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5879 epStatus[k] < EP_UNKNOWN &&
\r
5880 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5884 if (appData.debugMode) {
\r
5885 fprintf(debugFP, " loop\n");
\r
5888 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5890 if (appData.debugMode) {
\r
5891 fprintf(debugFP, "match\n");
\r
5894 /* compare castling rights */
\r
5895 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5896 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5897 rights++; /* King lost rights, while rook still had them */
\r
5898 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5899 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5900 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5901 rights++; /* but at least one rook lost them */
\r
5903 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5904 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5906 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5907 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5908 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5912 if (appData.debugMode) {
\r
5913 for(i=0; i<nrCastlingRights; i++)
\r
5914 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5917 if (appData.debugMode) {
\r
5918 fprintf(debugFP, " %d %d\n", rights, k);
\r
5921 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5922 && appData.drawRepeats > 1) {
\r
5923 /* adjudicate after user-specified nr of repeats */
\r
5924 SendToProgram("force\n", cps->other); // suppress reply
\r
5925 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5926 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5927 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5928 // [HGM] xiangqi: check for forbidden perpetuals
\r
5929 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5930 for(m=forwardMostMove; m>k; m-=2) {
\r
5931 if(MateTest(boards[m], PosFlags(m),
\r
5932 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5933 ourPerpetual = 0; // the current mover did not always check
\r
5934 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5935 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5936 hisPerpetual = 0; // the opponent did not always check
\r
5938 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
\r
5939 ourPerpetual, hisPerpetual);
\r
5940 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5941 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5942 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5945 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5946 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
5947 // Now check for perpetual chases
\r
5948 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
\r
5949 hisPerpetual = PerpetualChase(k, forwardMostMove);
\r
5950 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
\r
5951 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5952 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5953 "Xboard adjudication: perpetual chasing", GE_XBOARD );
\r
5956 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
\r
5957 break; // Abort repetition-checking loop.
\r
5959 // if neither of us is checking or chasing all the time, or both are, it is draw
\r
5961 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5964 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5965 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5969 /* Now we test for 50-move draws. Determine ply count */
\r
5970 count = forwardMostMove;
\r
5971 /* look for last irreversble move */
\r
5972 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5974 /* if we hit starting position, add initial plies */
\r
5975 if( count == backwardMostMove )
\r
5976 count -= initialRulePlies;
\r
5977 count = forwardMostMove - count;
\r
5979 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5980 /* this is used to judge if draw claims are legal */
\r
5981 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5982 SendToProgram("force\n", cps->other); // suppress reply
\r
5983 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5984 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5985 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5989 /* if draw offer is pending, treat it as a draw claim
\r
5990 * when draw condition present, to allow engines a way to
\r
5991 * claim draws before making their move to avoid a race
\r
5992 * condition occurring after their move
\r
5994 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
5996 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
5997 p = "Draw claim: 50-move rule";
\r
5998 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
5999 p = "Draw claim: 3-fold repetition";
\r
6000 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6001 p = "Draw claim: insufficient mating material";
\r
6003 SendToProgram("force\n", cps->other); // suppress reply
\r
6004 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6005 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6006 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6012 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6013 SendToProgram("force\n", cps->other); // suppress reply
\r
6014 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6015 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6017 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6024 if (gameMode == TwoMachinesPlay) {
\r
6025 /* [HGM] relaying draw offers moved to after reception of move */
\r
6026 /* and interpreting offer as claim if it brings draw condition */
\r
6027 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6028 SendToProgram("draw\n", cps->other);
\r
6030 if (cps->other->sendTime) {
\r
6031 SendTimeRemaining(cps->other,
\r
6032 cps->other->twoMachinesColor[0] == 'w');
\r
6034 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6035 if (firstMove && !bookHit) {
\r
6036 firstMove = FALSE;
\r
6037 if (cps->other->useColors) {
\r
6038 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6040 SendToProgram("go\n", cps->other);
\r
6042 cps->other->maybeThinking = TRUE;
\r
6045 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6047 if (!pausing && appData.ringBellAfterMoves) {
\r
6052 * Reenable menu items that were disabled while
\r
6053 * machine was thinking
\r
6055 if (gameMode != TwoMachinesPlay)
\r
6056 SetUserThinkingEnables();
\r
6058 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6059 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6060 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6062 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6064 strcpy(bookMove, "move ");
\r
6065 strcat(bookMove, bookHit);
\r
6066 message = bookMove;
\r
6068 programStats.nodes = programStats.depth = programStats.time =
\r
6069 programStats.score = programStats.got_only_move = 0;
\r
6070 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6072 if(cps->lastPing != cps->lastPong) {
\r
6073 savedMessage = message; // args for deferred call
\r
6075 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6078 goto FakeBookMove;
\r
6084 /* Set special modes for chess engines. Later something general
\r
6085 * could be added here; for now there is just one kludge feature,
\r
6086 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6087 * when "xboard" is given as an interactive command.
\r
6089 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6090 cps->useSigint = FALSE;
\r
6091 cps->useSigterm = FALSE;
\r
6094 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6095 * want this, I was asked to put it in, and obliged.
\r
6097 if (!strncmp(message, "setboard ", 9)) {
\r
6098 Board initial_position; int i;
\r
6100 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6102 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6103 DisplayError(_("Bad FEN received from engine"), 0);
\r
6106 Reset(FALSE, FALSE);
\r
6107 CopyBoard(boards[0], initial_position);
\r
6108 initialRulePlies = FENrulePlies;
\r
6109 epStatus[0] = FENepStatus;
\r
6110 for( i=0; i<nrCastlingRights; i++ )
\r
6111 castlingRights[0][i] = FENcastlingRights[i];
\r
6112 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6113 else gameMode = MachinePlaysBlack;
\r
6114 DrawPosition(FALSE, boards[currentMove]);
\r
6120 * Look for communication commands
\r
6122 if (!strncmp(message, "telluser ", 9)) {
\r
6123 DisplayNote(message + 9);
\r
6126 if (!strncmp(message, "tellusererror ", 14)) {
\r
6127 DisplayError(message + 14, 0);
\r
6130 if (!strncmp(message, "tellopponent ", 13)) {
\r
6131 if (appData.icsActive) {
\r
6133 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6137 DisplayNote(message + 13);
\r
6141 if (!strncmp(message, "tellothers ", 11)) {
\r
6142 if (appData.icsActive) {
\r
6144 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6150 if (!strncmp(message, "tellall ", 8)) {
\r
6151 if (appData.icsActive) {
\r
6153 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6157 DisplayNote(message + 8);
\r
6161 if (strncmp(message, "warning", 7) == 0) {
\r
6162 /* Undocumented feature, use tellusererror in new code */
\r
6163 DisplayError(message, 0);
\r
6166 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6167 strcpy(realname, cps->tidy);
\r
6168 strcat(realname, " query");
\r
6169 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6172 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6173 * sent until we are logged on. Crafty kibitzes have been known to
\r
6174 * interfere with the login process.
\r
6177 if (!strncmp(message, "tellics ", 8)) {
\r
6178 SendToICS(message + 8);
\r
6182 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6183 SendToICS(ics_prefix);
\r
6184 SendToICS(message + 15);
\r
6188 /* The following are for backward compatibility only */
\r
6189 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6190 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6191 SendToICS(ics_prefix);
\r
6192 SendToICS(message);
\r
6197 if (strncmp(message, "feature ", 8) == 0) {
\r
6198 ParseFeatures(message+8, cps);
\r
6200 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6204 * If the move is illegal, cancel it and redraw the board.
\r
6205 * Also deal with other error cases. Matching is rather loose
\r
6206 * here to accommodate engines written before the spec.
\r
6208 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6209 strncmp(message, "Error", 5) == 0) {
\r
6210 if (StrStr(message, "name") ||
\r
6211 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6212 StrStr(message, "result") || StrStr(message, "board") ||
\r
6213 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6214 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6215 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6216 StrStr(message, "accepted")) {
\r
6219 if (StrStr(message, "protover")) {
\r
6220 /* Program is responding to input, so it's apparently done
\r
6221 initializing, and this error message indicates it is
\r
6222 protocol version 1. So we don't need to wait any longer
\r
6223 for it to initialize and send feature commands. */
\r
6224 FeatureDone(cps, 1);
\r
6225 cps->protocolVersion = 1;
\r
6228 cps->maybeThinking = FALSE;
\r
6230 if (StrStr(message, "draw")) {
\r
6231 /* Program doesn't have "draw" command */
\r
6232 cps->sendDrawOffers = 0;
\r
6235 if (cps->sendTime != 1 &&
\r
6236 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6237 /* Program apparently doesn't have "time" or "otim" command */
\r
6238 cps->sendTime = 0;
\r
6241 if (StrStr(message, "analyze")) {
\r
6242 cps->analysisSupport = FALSE;
\r
6243 cps->analyzing = FALSE;
\r
6244 Reset(FALSE, TRUE);
\r
6245 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6246 DisplayError(buf2, 0);
\r
6249 if (StrStr(message, "(no matching move)st")) {
\r
6250 /* Special kludge for GNU Chess 4 only */
\r
6251 cps->stKludge = TRUE;
\r
6252 SendTimeControl(cps, movesPerSession, timeControl,
\r
6253 timeIncrement, appData.searchDepth,
\r
6257 if (StrStr(message, "(no matching move)sd")) {
\r
6258 /* Special kludge for GNU Chess 4 only */
\r
6259 cps->sdKludge = TRUE;
\r
6260 SendTimeControl(cps, movesPerSession, timeControl,
\r
6261 timeIncrement, appData.searchDepth,
\r
6265 if (!StrStr(message, "llegal")) {
\r
6268 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6269 gameMode == IcsIdle) return;
\r
6270 if (forwardMostMove <= backwardMostMove) return;
\r
6272 /* Following removed: it caused a bug where a real illegal move
\r
6273 message in analyze mored would be ignored. */
\r
6274 if (cps == &first && programStats.ok_to_send == 0) {
\r
6275 /* Bogus message from Crafty responding to "." This filtering
\r
6276 can miss some of the bad messages, but fortunately the bug
\r
6277 is fixed in current Crafty versions, so it doesn't matter. */
\r
6281 if (pausing) PauseEvent();
\r
6282 if (gameMode == PlayFromGameFile) {
\r
6283 /* Stop reading this game file */
\r
6284 gameMode = EditGame;
\r
6287 currentMove = --forwardMostMove;
\r
6288 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6290 DisplayBothClocks();
\r
6291 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6292 parseList[currentMove], cps->which);
\r
6293 DisplayMoveError(buf1);
\r
6294 DrawPosition(FALSE, boards[currentMove]);
\r
6296 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6297 /* only passes fully legal moves */
\r
6298 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6299 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6300 "False illegal-move claim", GE_XBOARD );
\r
6304 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6305 /* Program has a broken "time" command that
\r
6306 outputs a string not ending in newline.
\r
6308 cps->sendTime = 0;
\r
6312 * If chess program startup fails, exit with an error message.
\r
6313 * Attempts to recover here are futile.
\r
6315 if ((StrStr(message, "unknown host") != NULL)
\r
6316 || (StrStr(message, "No remote directory") != NULL)
\r
6317 || (StrStr(message, "not found") != NULL)
\r
6318 || (StrStr(message, "No such file") != NULL)
\r
6319 || (StrStr(message, "can't alloc") != NULL)
\r
6320 || (StrStr(message, "Permission denied") != NULL)) {
\r
6322 cps->maybeThinking = FALSE;
\r
6323 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6324 cps->which, cps->program, cps->host, message);
\r
6325 RemoveInputSource(cps->isr);
\r
6326 DisplayFatalError(buf1, 0, 1);
\r
6331 * Look for hint output
\r
6333 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6334 if (cps == &first && hintRequested) {
\r
6335 hintRequested = FALSE;
\r
6336 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6337 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6338 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6339 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6340 fromY, fromX, toY, toX, promoChar, buf1);
\r
6341 sprintf(buf2, _("Hint: %s"), buf1);
\r
6342 DisplayInformation(buf2);
\r
6344 /* Hint move could not be parsed!? */
\r
6346 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6347 buf1, cps->which);
\r
6348 DisplayError(buf2, 0);
\r
6351 strcpy(lastHint, buf1);
\r
6357 * Ignore other messages if game is not in progress
\r
6359 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6360 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6363 * look for win, lose, draw, or draw offer
\r
6365 if (strncmp(message, "1-0", 3) == 0) {
\r
6366 char *p, *q, *r = "";
\r
6367 p = strchr(message, '{');
\r
6369 q = strchr(p, '}');
\r
6375 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6377 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6378 char *p, *q, *r = "";
\r
6379 p = strchr(message, '{');
\r
6381 q = strchr(p, '}');
\r
6387 /* Kludge for Arasan 4.1 bug */
\r
6388 if (strcmp(r, "Black resigns") == 0) {
\r
6389 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6392 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6394 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6395 char *p, *q, *r = "";
\r
6396 p = strchr(message, '{');
\r
6398 q = strchr(p, '}');
\r
6405 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6408 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6409 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6411 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6412 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6414 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6415 strncmp(message, "Black matches", 13) == 0 ) {
\r
6416 /* [HGM] ignore GNUShogi noises */
\r
6418 } else if (strncmp(message, "White", 5) == 0 &&
\r
6419 message[5] != '(' &&
\r
6420 StrStr(message, "Black") == NULL) {
\r
6421 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6423 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6424 message[5] != '(') {
\r
6425 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6427 } else if (strcmp(message, "resign") == 0 ||
\r
6428 strcmp(message, "computer resigns") == 0) {
\r
6429 switch (gameMode) {
\r
6430 case MachinePlaysBlack:
\r
6431 case IcsPlayingBlack:
\r
6432 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6434 case MachinePlaysWhite:
\r
6435 case IcsPlayingWhite:
\r
6436 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6438 case TwoMachinesPlay:
\r
6439 if (cps->twoMachinesColor[0] == 'w')
\r
6440 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6442 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6445 /* can't happen */
\r
6449 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6450 switch (gameMode) {
\r
6451 case MachinePlaysBlack:
\r
6452 case IcsPlayingBlack:
\r
6453 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6455 case MachinePlaysWhite:
\r
6456 case IcsPlayingWhite:
\r
6457 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6459 case TwoMachinesPlay:
\r
6460 if (cps->twoMachinesColor[0] == 'w')
\r
6461 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6463 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6466 /* can't happen */
\r
6470 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6471 switch (gameMode) {
\r
6472 case MachinePlaysBlack:
\r
6473 case IcsPlayingBlack:
\r
6474 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6476 case MachinePlaysWhite:
\r
6477 case IcsPlayingWhite:
\r
6478 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6480 case TwoMachinesPlay:
\r
6481 if (cps->twoMachinesColor[0] == 'w')
\r
6482 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6484 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6487 /* can't happen */
\r
6491 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6492 if (WhiteOnMove(forwardMostMove)) {
\r
6493 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6495 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6498 } else if (strstr(message, "Draw") != NULL ||
\r
6499 strstr(message, "game is a draw") != NULL) {
\r
6500 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6502 } else if (strstr(message, "offer") != NULL &&
\r
6503 strstr(message, "draw") != NULL) {
\r
6505 if (appData.zippyPlay && first.initDone) {
\r
6506 /* Relay offer to ICS */
\r
6507 SendToICS(ics_prefix);
\r
6508 SendToICS("draw\n");
\r
6511 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6512 if (gameMode == TwoMachinesPlay) {
\r
6513 if (cps->other->offeredDraw) {
\r
6514 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6515 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6516 /* until after we also have move, to see if it is really claim */
\r
6520 if (cps->other->sendDrawOffers) {
\r
6521 SendToProgram("draw\n", cps->other);
\r
6525 } else if (gameMode == MachinePlaysWhite ||
\r
6526 gameMode == MachinePlaysBlack) {
\r
6527 if (userOfferedDraw) {
\r
6528 DisplayInformation(_("Machine accepts your draw offer"));
\r
6529 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6531 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6538 * Look for thinking output
\r
6540 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6541 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6543 int plylev, mvleft, mvtot, curscore, time;
\r
6544 char mvname[MOVE_LEN];
\r
6545 u64 nodes; // [DM]
\r
6547 int ignore = FALSE;
\r
6548 int prefixHint = FALSE;
\r
6549 mvname[0] = NULLCHAR;
\r
6551 switch (gameMode) {
\r
6552 case MachinePlaysBlack:
\r
6553 case IcsPlayingBlack:
\r
6554 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6556 case MachinePlaysWhite:
\r
6557 case IcsPlayingWhite:
\r
6558 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6563 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6564 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6566 case TwoMachinesPlay:
\r
6567 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6577 buf1[0] = NULLCHAR;
\r
6578 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6579 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6581 if (plyext != ' ' && plyext != '\t') {
\r
6585 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6586 if( cps->scoreIsAbsolute &&
\r
6587 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6589 curscore = -curscore;
\r
6593 programStats.depth = plylev;
\r
6594 programStats.nodes = nodes;
\r
6595 programStats.time = time;
\r
6596 programStats.score = curscore;
\r
6597 programStats.got_only_move = 0;
\r
6599 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6602 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6603 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
\r
6604 if(WhiteOnMove(forwardMostMove))
\r
6605 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6606 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6609 /* Buffer overflow protection */
\r
6610 if (buf1[0] != NULLCHAR) {
\r
6611 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6612 && appData.debugMode) {
\r
6614 "PV is too long; using the first %d bytes.\n",
\r
6615 sizeof(programStats.movelist) - 1);
\r
6618 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6620 sprintf(programStats.movelist, " no PV\n");
\r
6623 if (programStats.seen_stat) {
\r
6624 programStats.ok_to_send = 1;
\r
6627 if (strchr(programStats.movelist, '(') != NULL) {
\r
6628 programStats.line_is_book = 1;
\r
6629 programStats.nr_moves = 0;
\r
6630 programStats.moves_left = 0;
\r
6632 programStats.line_is_book = 0;
\r
6635 SendProgramStatsToFrontend( cps, &programStats );
\r
6638 [AS] Protect the thinkOutput buffer from overflow... this
\r
6639 is only useful if buf1 hasn't overflowed first!
\r
6641 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6643 (gameMode == TwoMachinesPlay ?
\r
6644 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6645 ((double) curscore) / 100.0,
\r
6646 prefixHint ? lastHint : "",
\r
6647 prefixHint ? " " : "" );
\r
6649 if( buf1[0] != NULLCHAR ) {
\r
6650 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6652 if( strlen(buf1) > max_len ) {
\r
6653 if( appData.debugMode) {
\r
6654 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6656 buf1[max_len+1] = '\0';
\r
6659 strcat( thinkOutput, buf1 );
\r
6662 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6663 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6664 DisplayMove(currentMove - 1);
\r
6665 DisplayAnalysis();
\r
6669 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6670 /* crafty (9.25+) says "(only move) <move>"
\r
6671 * if there is only 1 legal move
\r
6673 sscanf(p, "(only move) %s", buf1);
\r
6674 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6675 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6676 programStats.depth = 1;
\r
6677 programStats.nr_moves = 1;
\r
6678 programStats.moves_left = 1;
\r
6679 programStats.nodes = 1;
\r
6680 programStats.time = 1;
\r
6681 programStats.got_only_move = 1;
\r
6683 /* Not really, but we also use this member to
\r
6684 mean "line isn't going to change" (Crafty
\r
6685 isn't searching, so stats won't change) */
\r
6686 programStats.line_is_book = 1;
\r
6688 SendProgramStatsToFrontend( cps, &programStats );
\r
6690 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6691 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6692 DisplayMove(currentMove - 1);
\r
6693 DisplayAnalysis();
\r
6696 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6697 &time, &nodes, &plylev, &mvleft,
\r
6698 &mvtot, mvname) >= 5) {
\r
6699 /* The stat01: line is from Crafty (9.29+) in response
\r
6700 to the "." command */
\r
6701 programStats.seen_stat = 1;
\r
6702 cps->maybeThinking = TRUE;
\r
6704 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6707 programStats.depth = plylev;
\r
6708 programStats.time = time;
\r
6709 programStats.nodes = nodes;
\r
6710 programStats.moves_left = mvleft;
\r
6711 programStats.nr_moves = mvtot;
\r
6712 strcpy(programStats.move_name, mvname);
\r
6713 programStats.ok_to_send = 1;
\r
6714 programStats.movelist[0] = '\0';
\r
6716 SendProgramStatsToFrontend( cps, &programStats );
\r
6718 DisplayAnalysis();
\r
6721 } else if (strncmp(message,"++",2) == 0) {
\r
6722 /* Crafty 9.29+ outputs this */
\r
6723 programStats.got_fail = 2;
\r
6726 } else if (strncmp(message,"--",2) == 0) {
\r
6727 /* Crafty 9.29+ outputs this */
\r
6728 programStats.got_fail = 1;
\r
6731 } else if (thinkOutput[0] != NULLCHAR &&
\r
6732 strncmp(message, " ", 4) == 0) {
\r
6733 unsigned message_len;
\r
6736 while (*p && *p == ' ') p++;
\r
6738 message_len = strlen( p );
\r
6740 /* [AS] Avoid buffer overflow */
\r
6741 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6742 strcat(thinkOutput, " ");
\r
6743 strcat(thinkOutput, p);
\r
6746 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6747 strcat(programStats.movelist, " ");
\r
6748 strcat(programStats.movelist, p);
\r
6751 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6752 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6753 DisplayMove(currentMove - 1);
\r
6754 DisplayAnalysis();
\r
6760 buf1[0] = NULLCHAR;
\r
6762 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6763 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6765 ChessProgramStats cpstats;
\r
6767 if (plyext != ' ' && plyext != '\t') {
\r
6771 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6772 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6773 curscore = -curscore;
\r
6776 cpstats.depth = plylev;
\r
6777 cpstats.nodes = nodes;
\r
6778 cpstats.time = time;
\r
6779 cpstats.score = curscore;
\r
6780 cpstats.got_only_move = 0;
\r
6781 cpstats.movelist[0] = '\0';
\r
6783 if (buf1[0] != NULLCHAR) {
\r
6784 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6787 cpstats.ok_to_send = 0;
\r
6788 cpstats.line_is_book = 0;
\r
6789 cpstats.nr_moves = 0;
\r
6790 cpstats.moves_left = 0;
\r
6792 SendProgramStatsToFrontend( cps, &cpstats );
\r
6799 /* Parse a game score from the character string "game", and
\r
6800 record it as the history of the current game. The game
\r
6801 score is NOT assumed to start from the standard position.
\r
6802 The display is not updated in any way.
\r
6805 ParseGameHistory(game)
\r
6808 ChessMove moveType;
\r
6809 int fromX, fromY, toX, toY, boardIndex;
\r
6812 char buf[MSG_SIZ];
\r
6814 if (appData.debugMode)
\r
6815 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6817 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6818 gameInfo.site = StrSave(appData.icsHost);
\r
6819 gameInfo.date = PGNDate();
\r
6820 gameInfo.round = StrSave("-");
\r
6822 /* Parse out names of players */
\r
6823 while (*game == ' ') game++;
\r
6825 while (*game != ' ') *p++ = *game++;
\r
6827 gameInfo.white = StrSave(buf);
\r
6828 while (*game == ' ') game++;
\r
6830 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6832 gameInfo.black = StrSave(buf);
\r
6835 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6838 yyboardindex = boardIndex;
\r
6839 moveType = (ChessMove) yylex();
\r
6840 switch (moveType) {
\r
6841 case IllegalMove: /* maybe suicide chess, etc. */
\r
6842 if (appData.debugMode) {
\r
6843 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6844 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6845 setbuf(debugFP, NULL);
\r
6847 case WhitePromotionChancellor:
\r
6848 case BlackPromotionChancellor:
\r
6849 case WhitePromotionArchbishop:
\r
6850 case BlackPromotionArchbishop:
\r
6851 case WhitePromotionQueen:
\r
6852 case BlackPromotionQueen:
\r
6853 case WhitePromotionRook:
\r
6854 case BlackPromotionRook:
\r
6855 case WhitePromotionBishop:
\r
6856 case BlackPromotionBishop:
\r
6857 case WhitePromotionKnight:
\r
6858 case BlackPromotionKnight:
\r
6859 case WhitePromotionKing:
\r
6860 case BlackPromotionKing:
\r
6862 case WhiteCapturesEnPassant:
\r
6863 case BlackCapturesEnPassant:
\r
6864 case WhiteKingSideCastle:
\r
6865 case WhiteQueenSideCastle:
\r
6866 case BlackKingSideCastle:
\r
6867 case BlackQueenSideCastle:
\r
6868 case WhiteKingSideCastleWild:
\r
6869 case WhiteQueenSideCastleWild:
\r
6870 case BlackKingSideCastleWild:
\r
6871 case BlackQueenSideCastleWild:
\r
6873 case WhiteHSideCastleFR:
\r
6874 case WhiteASideCastleFR:
\r
6875 case BlackHSideCastleFR:
\r
6876 case BlackASideCastleFR:
\r
6878 fromX = currentMoveString[0] - AAA;
\r
6879 fromY = currentMoveString[1] - ONE;
\r
6880 toX = currentMoveString[2] - AAA;
\r
6881 toY = currentMoveString[3] - ONE;
\r
6882 promoChar = currentMoveString[4];
\r
6886 fromX = moveType == WhiteDrop ?
\r
6887 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6888 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6889 fromY = DROP_RANK;
\r
6890 toX = currentMoveString[2] - AAA;
\r
6891 toY = currentMoveString[3] - ONE;
\r
6892 promoChar = NULLCHAR;
\r
6894 case AmbiguousMove:
\r
6896 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6897 if (appData.debugMode) {
\r
6898 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6899 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6900 setbuf(debugFP, NULL);
\r
6902 DisplayError(buf, 0);
\r
6904 case ImpossibleMove:
\r
6906 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6907 if (appData.debugMode) {
\r
6908 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6909 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6910 setbuf(debugFP, NULL);
\r
6912 DisplayError(buf, 0);
\r
6914 case (ChessMove) 0: /* end of file */
\r
6915 if (boardIndex < backwardMostMove) {
\r
6916 /* Oops, gap. How did that happen? */
\r
6917 DisplayError(_("Gap in move list"), 0);
\r
6920 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6921 if (boardIndex > forwardMostMove) {
\r
6922 forwardMostMove = boardIndex;
\r
6926 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6927 strcat(parseList[boardIndex-1], " ");
\r
6928 strcat(parseList[boardIndex-1], yy_text);
\r
6940 case GameUnfinished:
\r
6941 if (gameMode == IcsExamining) {
\r
6942 if (boardIndex < backwardMostMove) {
\r
6943 /* Oops, gap. How did that happen? */
\r
6946 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6949 gameInfo.result = moveType;
\r
6950 p = strchr(yy_text, '{');
\r
6951 if (p == NULL) p = strchr(yy_text, '(');
\r
6954 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6956 q = strchr(p, *p == '{' ? '}' : ')');
\r
6957 if (q != NULL) *q = NULLCHAR;
\r
6960 gameInfo.resultDetails = StrSave(p);
\r
6963 if (boardIndex >= forwardMostMove &&
\r
6964 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6965 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6968 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6969 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6970 parseList[boardIndex]);
\r
6971 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6972 /* currentMoveString is set as a side-effect of yylex */
\r
6973 strcpy(moveList[boardIndex], currentMoveString);
\r
6974 strcat(moveList[boardIndex], "\n");
\r
6976 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6977 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6978 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6980 case MT_STALEMATE:
\r
6984 if(gameInfo.variant != VariantShogi)
\r
6985 strcat(parseList[boardIndex - 1], "+");
\r
6987 case MT_CHECKMATE:
\r
6988 strcat(parseList[boardIndex - 1], "#");
\r
6995 /* Apply a move to the given board */
\r
6997 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
6998 int fromX, fromY, toX, toY;
\r
7002 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7004 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7005 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7006 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7009 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7010 oldEP = epStatus[p-1];
\r
7011 epStatus[p] = EP_NONE;
\r
7013 if( board[toY][toX] != EmptySquare )
\r
7014 epStatus[p] = EP_CAPTURE;
\r
7016 if( board[fromY][fromX] == WhitePawn ) {
\r
7017 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7018 epStatus[p] = EP_PAWN_MOVE;
\r
7019 if( toY-fromY==2) {
\r
7020 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7021 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7022 epStatus[p] = toX | berolina;
\r
7023 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7024 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7025 epStatus[p] = toX;
\r
7028 if( board[fromY][fromX] == BlackPawn ) {
\r
7029 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7030 epStatus[p] = EP_PAWN_MOVE;
\r
7031 if( toY-fromY== -2) {
\r
7032 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7033 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7034 epStatus[p] = toX | berolina;
\r
7035 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7036 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7037 epStatus[p] = toX;
\r
7041 for(i=0; i<nrCastlingRights; i++) {
\r
7042 castlingRights[p][i] = castlingRights[p-1][i];
\r
7043 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7044 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7045 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7050 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7051 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7052 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7054 if (fromX == toX && fromY == toY) return;
\r
7056 if (fromY == DROP_RANK) {
\r
7057 /* must be first */
\r
7058 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7060 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7061 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7062 if(gameInfo.variant == VariantKnightmate)
\r
7063 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7065 /* Code added by Tord: */
\r
7066 /* FRC castling assumed when king captures friendly rook. */
\r
7067 if (board[fromY][fromX] == WhiteKing &&
\r
7068 board[toY][toX] == WhiteRook) {
\r
7069 board[fromY][fromX] = EmptySquare;
\r
7070 board[toY][toX] = EmptySquare;
\r
7072 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7074 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7076 } else if (board[fromY][fromX] == BlackKing &&
\r
7077 board[toY][toX] == BlackRook) {
\r
7078 board[fromY][fromX] = EmptySquare;
\r
7079 board[toY][toX] = EmptySquare;
\r
7081 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7083 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7085 /* End of code added by Tord */
\r
7087 } else if (board[fromY][fromX] == king
\r
7088 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7089 && toY == fromY && toX > fromX+1) {
\r
7090 board[fromY][fromX] = EmptySquare;
\r
7091 board[toY][toX] = king;
\r
7092 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7093 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7094 } else if (board[fromY][fromX] == king
\r
7095 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7096 && toY == fromY && toX < fromX-1) {
\r
7097 board[fromY][fromX] = EmptySquare;
\r
7098 board[toY][toX] = king;
\r
7099 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7100 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7101 } else if (board[fromY][fromX] == WhitePawn
\r
7102 && toY == BOARD_HEIGHT-1
\r
7103 && gameInfo.variant != VariantXiangqi
\r
7105 /* white pawn promotion */
\r
7106 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7107 if (board[toY][toX] == EmptySquare) {
\r
7108 board[toY][toX] = WhiteQueen;
\r
7110 if(gameInfo.variant==VariantBughouse ||
\r
7111 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7112 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7113 board[fromY][fromX] = EmptySquare;
\r
7114 } else if ((fromY == BOARD_HEIGHT-4)
\r
7116 && gameInfo.variant != VariantXiangqi
\r
7117 && gameInfo.variant != VariantBerolina
\r
7118 && (board[fromY][fromX] == WhitePawn)
\r
7119 && (board[toY][toX] == EmptySquare)) {
\r
7120 board[fromY][fromX] = EmptySquare;
\r
7121 board[toY][toX] = WhitePawn;
\r
7122 captured = board[toY - 1][toX];
\r
7123 board[toY - 1][toX] = EmptySquare;
\r
7124 } else if ((fromY == BOARD_HEIGHT-4)
\r
7126 && gameInfo.variant == VariantBerolina
\r
7127 && (board[fromY][fromX] == WhitePawn)
\r
7128 && (board[toY][toX] == EmptySquare)) {
\r
7129 board[fromY][fromX] = EmptySquare;
\r
7130 board[toY][toX] = WhitePawn;
\r
7131 if(oldEP & EP_BEROLIN_A) {
\r
7132 captured = board[fromY][fromX-1];
\r
7133 board[fromY][fromX-1] = EmptySquare;
\r
7134 }else{ captured = board[fromY][fromX+1];
\r
7135 board[fromY][fromX+1] = EmptySquare;
\r
7137 } else if (board[fromY][fromX] == king
\r
7138 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7139 && toY == fromY && toX > fromX+1) {
\r
7140 board[fromY][fromX] = EmptySquare;
\r
7141 board[toY][toX] = king;
\r
7142 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7143 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7144 } else if (board[fromY][fromX] == king
\r
7145 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7146 && toY == fromY && toX < fromX-1) {
\r
7147 board[fromY][fromX] = EmptySquare;
\r
7148 board[toY][toX] = king;
\r
7149 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7150 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7151 } else if (fromY == 7 && fromX == 3
\r
7152 && board[fromY][fromX] == BlackKing
\r
7153 && toY == 7 && toX == 5) {
\r
7154 board[fromY][fromX] = EmptySquare;
\r
7155 board[toY][toX] = BlackKing;
\r
7156 board[fromY][7] = EmptySquare;
\r
7157 board[toY][4] = BlackRook;
\r
7158 } else if (fromY == 7 && fromX == 3
\r
7159 && board[fromY][fromX] == BlackKing
\r
7160 && toY == 7 && toX == 1) {
\r
7161 board[fromY][fromX] = EmptySquare;
\r
7162 board[toY][toX] = BlackKing;
\r
7163 board[fromY][0] = EmptySquare;
\r
7164 board[toY][2] = BlackRook;
\r
7165 } else if (board[fromY][fromX] == BlackPawn
\r
7167 && gameInfo.variant != VariantXiangqi
\r
7169 /* black pawn promotion */
\r
7170 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7171 if (board[0][toX] == EmptySquare) {
\r
7172 board[0][toX] = BlackQueen;
\r
7174 if(gameInfo.variant==VariantBughouse ||
\r
7175 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7176 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7177 board[fromY][fromX] = EmptySquare;
\r
7178 } else if ((fromY == 3)
\r
7180 && gameInfo.variant != VariantXiangqi
\r
7181 && gameInfo.variant != VariantBerolina
\r
7182 && (board[fromY][fromX] == BlackPawn)
\r
7183 && (board[toY][toX] == EmptySquare)) {
\r
7184 board[fromY][fromX] = EmptySquare;
\r
7185 board[toY][toX] = BlackPawn;
\r
7186 captured = board[toY + 1][toX];
\r
7187 board[toY + 1][toX] = EmptySquare;
\r
7188 } else if ((fromY == 3)
\r
7190 && gameInfo.variant == VariantBerolina
\r
7191 && (board[fromY][fromX] == BlackPawn)
\r
7192 && (board[toY][toX] == EmptySquare)) {
\r
7193 board[fromY][fromX] = EmptySquare;
\r
7194 board[toY][toX] = BlackPawn;
\r
7195 if(oldEP & EP_BEROLIN_A) {
\r
7196 captured = board[fromY][fromX-1];
\r
7197 board[fromY][fromX-1] = EmptySquare;
\r
7198 }else{ captured = board[fromY][fromX+1];
\r
7199 board[fromY][fromX+1] = EmptySquare;
\r
7202 board[toY][toX] = board[fromY][fromX];
\r
7203 board[fromY][fromX] = EmptySquare;
\r
7206 /* [HGM] now we promote for Shogi, if needed */
\r
7207 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7208 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7211 if (gameInfo.holdingsWidth != 0) {
\r
7213 /* !!A lot more code needs to be written to support holdings */
\r
7214 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7215 /* penultimate board files, so they are automaticlly stored */
\r
7216 /* in the game history. */
\r
7217 if (fromY == DROP_RANK) {
\r
7218 /* Delete from holdings, by decreasing count */
\r
7219 /* and erasing image if necessary */
\r
7221 if(p < (int) BlackPawn) { /* white drop */
\r
7222 p -= (int)WhitePawn;
\r
7223 if(p >= gameInfo.holdingsSize) p = 0;
\r
7224 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7225 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7226 } else { /* black drop */
\r
7227 p -= (int)BlackPawn;
\r
7228 if(p >= gameInfo.holdingsSize) p = 0;
\r
7229 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7230 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7233 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7234 && gameInfo.variant != VariantBughouse ) {
\r
7235 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7236 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7237 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7238 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7240 p = (int) captured;
\r
7241 if (p >= (int) BlackPawn) {
\r
7242 p -= (int)BlackPawn;
\r
7243 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7244 /* in Shogi restore piece to its original first */
\r
7245 captured = (ChessSquare) (DEMOTED captured);
\r
7248 p = PieceToNumber((ChessSquare)p);
\r
7249 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7250 board[p][BOARD_WIDTH-2]++;
\r
7251 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7253 p -= (int)WhitePawn;
\r
7254 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7255 captured = (ChessSquare) (DEMOTED captured);
\r
7258 p = PieceToNumber((ChessSquare)p);
\r
7259 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7260 board[BOARD_HEIGHT-1-p][1]++;
\r
7261 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7265 } else if (gameInfo.variant == VariantAtomic) {
\r
7266 if (captured != EmptySquare) {
\r
7268 for (y = toY-1; y <= toY+1; y++) {
\r
7269 for (x = toX-1; x <= toX+1; x++) {
\r
7270 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7271 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7272 board[y][x] = EmptySquare;
\r
7276 board[toY][toX] = EmptySquare;
\r
7279 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7280 /* [HGM] Shogi promotions */
\r
7281 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7286 /* Updates forwardMostMove */
\r
7288 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7289 int fromX, fromY, toX, toY;
\r
7292 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7294 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7295 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7296 piece = boards[forwardMostMove][fromY][fromX];
\r
7297 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7298 if(gameInfo.variant == VariantKnightmate)
\r
7299 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7300 if(forwardMostMove == 0) {
\r
7301 if(blackPlaysFirst)
\r
7302 fprintf(serverMoves, "%s;", second.tidy);
\r
7303 fprintf(serverMoves, "%s;", first.tidy);
\r
7304 if(!blackPlaysFirst)
\r
7305 fprintf(serverMoves, "%s;", second.tidy);
\r
7306 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7307 lastLoadFlag = loadFlag;
\r
7308 // print base move
\r
7309 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7310 // print castling suffix
\r
7311 if( toY == fromY && piece == king ) {
\r
7313 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7315 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7318 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7319 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7320 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7322 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7323 // promotion suffix
\r
7324 if(promoChar != NULLCHAR)
\r
7325 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7327 fprintf(serverMoves, "/%d/%d",
\r
7328 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7329 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7330 else timeLeft = blackTimeRemaining/1000;
\r
7331 fprintf(serverMoves, "/%d", timeLeft);
\r
7333 fflush(serverMoves);
\r
7336 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7337 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7342 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7343 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7344 if (commentList[forwardMostMove+1] != NULL) {
\r
7345 free(commentList[forwardMostMove+1]);
\r
7346 commentList[forwardMostMove+1] = NULL;
\r
7348 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7349 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7350 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7351 gameInfo.result = GameUnfinished;
\r
7352 if (gameInfo.resultDetails != NULL) {
\r
7353 free(gameInfo.resultDetails);
\r
7354 gameInfo.resultDetails = NULL;
\r
7356 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7357 moveList[forwardMostMove - 1]);
\r
7358 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7359 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7360 fromY, fromX, toY, toX, promoChar,
\r
7361 parseList[forwardMostMove - 1]);
\r
7362 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7363 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7364 castlingRights[forwardMostMove]) ) {
\r
7366 case MT_STALEMATE:
\r
7370 if(gameInfo.variant != VariantShogi)
\r
7371 strcat(parseList[forwardMostMove - 1], "+");
\r
7373 case MT_CHECKMATE:
\r
7374 strcat(parseList[forwardMostMove - 1], "#");
\r
7377 if (appData.debugMode) {
\r
7378 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7383 /* Updates currentMove if not pausing */
\r
7385 ShowMove(fromX, fromY, toX, toY)
\r
7387 int instant = (gameMode == PlayFromGameFile) ?
\r
7388 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7389 if(appData.noGUI) return;
\r
7390 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7392 if (forwardMostMove == currentMove + 1) {
\r
7393 AnimateMove(boards[forwardMostMove - 1],
\r
7394 fromX, fromY, toX, toY);
\r
7396 if (appData.highlightLastMove) {
\r
7397 SetHighlights(fromX, fromY, toX, toY);
\r
7400 currentMove = forwardMostMove;
\r
7403 if (instant) return;
\r
7405 DisplayMove(currentMove - 1);
\r
7406 DrawPosition(FALSE, boards[currentMove]);
\r
7407 DisplayBothClocks();
\r
7408 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7411 void SendEgtPath(ChessProgramState *cps)
\r
7412 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7413 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7415 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7418 char c, *q = name+1, *r, *s;
\r
7420 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7421 while(*p && *p != ',') *q++ = *p++;
\r
7422 *q++ = ':'; *q = 0;
\r
7423 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7424 strcmp(name, ",nalimov:") == 0 ) {
\r
7425 // take nalimov path from the menu-changeable option first, if it is defined
\r
7426 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7427 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7429 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7430 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7431 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7432 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7433 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7434 c = *r; *r = 0; // temporarily null-terminate path info
\r
7435 *--q = 0; // strip of trailig ':' from name
\r
7436 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7438 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7440 if(*p == ',') p++; // read away comma to position for next format name
\r
7445 InitChessProgram(cps, setup)
\r
7446 ChessProgramState *cps;
\r
7447 int setup; /* [HGM] needed to setup FRC opening position */
\r
7449 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7450 if (appData.noChessProgram) return;
\r
7451 hintRequested = FALSE;
\r
7452 bookRequested = FALSE;
\r
7454 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7455 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7456 if(cps->memSize) { /* [HGM] memory */
\r
7457 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7458 SendToProgram(buf, cps);
\r
7460 SendEgtPath(cps); /* [HGM] EGT */
\r
7461 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7462 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7463 SendToProgram(buf, cps);
\r
7466 SendToProgram(cps->initString, cps);
\r
7467 if (gameInfo.variant != VariantNormal &&
\r
7468 gameInfo.variant != VariantLoadable
\r
7469 /* [HGM] also send variant if board size non-standard */
\r
7470 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7472 char *v = VariantName(gameInfo.variant);
\r
7473 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7474 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7475 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7476 DisplayFatalError(buf, 0, 1);
\r
7480 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7481 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7482 if( gameInfo.variant == VariantXiangqi )
\r
7483 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7484 if( gameInfo.variant == VariantShogi )
\r
7485 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7486 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7487 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7488 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7489 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7490 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7491 if( gameInfo.variant == VariantCourier )
\r
7492 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7493 if( gameInfo.variant == VariantSuper )
\r
7494 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7495 if( gameInfo.variant == VariantGreat )
\r
7496 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7499 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7500 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7501 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7502 if(StrStr(cps->variants, b) == NULL) {
\r
7503 // specific sized variant not known, check if general sizing allowed
\r
7504 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7505 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7506 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7507 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7508 DisplayFatalError(buf, 0, 1);
\r
7511 /* [HGM] here we really should compare with the maximum supported board size */
\r
7514 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7515 sprintf(buf, "variant %s\n", b);
\r
7516 SendToProgram(buf, cps);
\r
7518 currentlyInitializedVariant = gameInfo.variant;
\r
7520 /* [HGM] send opening position in FRC to first engine */
\r
7522 SendToProgram("force\n", cps);
\r
7523 SendBoard(cps, 0);
\r
7524 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7525 setboardSpoiledMachineBlack = 1;
\r
7528 if (cps->sendICS) {
\r
7529 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7530 SendToProgram(buf, cps);
\r
7532 cps->maybeThinking = FALSE;
\r
7533 cps->offeredDraw = 0;
\r
7534 if (!appData.icsActive) {
\r
7535 SendTimeControl(cps, movesPerSession, timeControl,
\r
7536 timeIncrement, appData.searchDepth,
\r
7539 if (appData.showThinking
\r
7540 // [HGM] thinking: four options require thinking output to be sent
\r
7541 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7543 SendToProgram("post\n", cps);
\r
7545 SendToProgram("hard\n", cps);
\r
7546 if (!appData.ponderNextMove) {
\r
7547 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7548 it without being sure what state we are in first. "hard"
\r
7549 is not a toggle, so that one is OK.
\r
7551 SendToProgram("easy\n", cps);
\r
7553 if (cps->usePing) {
\r
7554 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7555 SendToProgram(buf, cps);
\r
7557 cps->initDone = TRUE;
\r
7562 StartChessProgram(cps)
\r
7563 ChessProgramState *cps;
\r
7565 char buf[MSG_SIZ];
\r
7568 if (appData.noChessProgram) return;
\r
7569 cps->initDone = FALSE;
\r
7571 if (strcmp(cps->host, "localhost") == 0) {
\r
7572 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7573 } else if (*appData.remoteShell == NULLCHAR) {
\r
7574 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7576 if (*appData.remoteUser == NULLCHAR) {
\r
7577 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7580 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7581 cps->host, appData.remoteUser, cps->program);
\r
7583 err = StartChildProcess(buf, "", &cps->pr);
\r
7587 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7588 DisplayFatalError(buf, err, 1);
\r
7594 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7595 if (cps->protocolVersion > 1) {
\r
7596 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7597 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7598 cps->comboCnt = 0; // and values of combo boxes
\r
7599 SendToProgram(buf, cps);
\r
7601 SendToProgram("xboard\n", cps);
\r
7607 TwoMachinesEventIfReady P((void))
\r
7609 if (first.lastPing != first.lastPong) {
\r
7610 DisplayMessage("", _("Waiting for first chess program"));
\r
7611 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7614 if (second.lastPing != second.lastPong) {
\r
7615 DisplayMessage("", _("Waiting for second chess program"));
\r
7616 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7620 TwoMachinesEvent();
\r
7624 NextMatchGame P((void))
\r
7626 int index; /* [HGM] autoinc: step lod index during match */
\r
7627 Reset(FALSE, TRUE);
\r
7628 if (*appData.loadGameFile != NULLCHAR) {
\r
7629 index = appData.loadGameIndex;
\r
7630 if(index < 0) { // [HGM] autoinc
\r
7631 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7632 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7634 LoadGameFromFile(appData.loadGameFile,
\r
7636 appData.loadGameFile, FALSE);
\r
7637 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7638 index = appData.loadPositionIndex;
\r
7639 if(index < 0) { // [HGM] autoinc
\r
7640 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7641 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7643 LoadPositionFromFile(appData.loadPositionFile,
\r
7645 appData.loadPositionFile);
\r
7647 TwoMachinesEventIfReady();
\r
7650 void UserAdjudicationEvent( int result )
\r
7652 ChessMove gameResult = GameIsDrawn;
\r
7654 if( result > 0 ) {
\r
7655 gameResult = WhiteWins;
\r
7657 else if( result < 0 ) {
\r
7658 gameResult = BlackWins;
\r
7661 if( gameMode == TwoMachinesPlay ) {
\r
7662 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7668 GameEnds(result, resultDetails, whosays)
\r
7670 char *resultDetails;
\r
7673 GameMode nextGameMode;
\r
7675 char buf[MSG_SIZ];
\r
7677 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7680 if (appData.debugMode) {
\r
7681 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7682 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7685 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7686 /* If we are playing on ICS, the server decides when the
\r
7687 game is over, but the engine can offer to draw, claim
\r
7688 a draw, or resign.
\r
7691 if (appData.zippyPlay && first.initDone) {
\r
7692 if (result == GameIsDrawn) {
\r
7693 /* In case draw still needs to be claimed */
\r
7694 SendToICS(ics_prefix);
\r
7695 SendToICS("draw\n");
\r
7696 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7697 SendToICS(ics_prefix);
\r
7698 SendToICS("resign\n");
\r
7702 endingGame = 0; /* [HGM] crash */
\r
7706 /* If we're loading the game from a file, stop */
\r
7707 if (whosays == GE_FILE) {
\r
7708 (void) StopLoadGameTimer();
\r
7709 gameFileFP = NULL;
\r
7712 /* Cancel draw offers */
\r
7713 first.offeredDraw = second.offeredDraw = 0;
\r
7715 /* If this is an ICS game, only ICS can really say it's done;
\r
7716 if not, anyone can. */
\r
7717 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7718 gameMode == IcsPlayingBlack ||
\r
7719 gameMode == IcsObserving ||
\r
7720 gameMode == IcsExamining);
\r
7722 if (!isIcsGame || whosays == GE_ICS) {
\r
7723 /* OK -- not an ICS game, or ICS said it was done */
\r
7725 if (!isIcsGame && !appData.noChessProgram)
\r
7726 SetUserThinkingEnables();
\r
7728 /* [HGM] if a machine claims the game end we verify this claim */
\r
7729 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7730 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7732 ChessMove trueResult = (ChessMove) -1;
\r
7734 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7735 first.twoMachinesColor[0] :
\r
7736 second.twoMachinesColor[0] ;
\r
7738 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
\r
7739 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
\r
7740 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7741 trueResult = WhiteOnMove(forwardMostMove) != (gameInfo.variant == VariantLosers)
\r
7742 ? BlackWins : WhiteWins; // [HGM] losers: reverse the result in VariantLosers!
\r
7744 if(epStatus[forwardMostMove] == EP_STALEMATE) {
\r
7745 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
\r
7746 if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantSuicide ||
\r
7747 gameInfo.variant == VariantLosers) // [HGM] losers: in giveaway variants stalemate wins
\r
7748 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
\r
7751 // now verify win claims, but not in drop games, as we don't understand those yet
\r
7752 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
\r
7753 || gameInfo.variant == VariantGreat) &&
\r
7754 (result == WhiteWins && claimer == 'w' ||
\r
7755 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
\r
7756 if (appData.debugMode) {
\r
7757 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7758 result, epStatus[forwardMostMove], forwardMostMove);
\r
7760 if(result != trueResult) {
\r
7761 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7762 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7763 resultDetails = buf;
\r
7766 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7767 && (forwardMostMove <= backwardMostMove ||
\r
7768 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7769 (claimer=='b')==(forwardMostMove&1))
\r
7771 /* [HGM] verify: draws that were not flagged are false claims */
\r
7772 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7773 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7774 resultDetails = buf;
\r
7776 /* (Claiming a loss is accepted no questions asked!) */
\r
7778 /* [HGM] bare: don't allow bare King to win */
\r
7779 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7780 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
\r
7781 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
\r
7782 && result != GameIsDrawn)
\r
7783 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7784 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7785 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7786 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7788 if (appData.debugMode) {
\r
7789 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7790 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7793 result = GameIsDrawn;
\r
7794 sprintf(buf, "%s but bare king", resultDetails);
\r
7795 resultDetails = buf;
\r
7801 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7802 if(result==WhiteWins) c = '+';
\r
7803 if(result==BlackWins) c = '-';
\r
7804 if(resultDetails != NULL)
\r
7805 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7807 if (resultDetails != NULL) {
\r
7808 gameInfo.result = result;
\r
7809 gameInfo.resultDetails = StrSave(resultDetails);
\r
7811 /* display last move only if game was not loaded from file */
\r
7812 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7813 DisplayMove(currentMove - 1);
\r
7815 if (forwardMostMove != 0) {
\r
7816 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7817 if (*appData.saveGameFile != NULLCHAR) {
\r
7818 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7819 } else if (appData.autoSaveGames) {
\r
7822 if (*appData.savePositionFile != NULLCHAR) {
\r
7823 SavePositionToFile(appData.savePositionFile);
\r
7828 /* Tell program how game ended in case it is learning */
\r
7829 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7830 /* engine died and we got here through time loss. In that */
\r
7831 /* case we will get a fatal error writing the pipe, which */
\r
7832 /* would otherwise lose us the PGN. */
\r
7833 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7834 /* output during GameEnds should never be fatal anymore */
\r
7835 if (gameMode == MachinePlaysWhite ||
\r
7836 gameMode == MachinePlaysBlack ||
\r
7837 gameMode == TwoMachinesPlay ||
\r
7838 gameMode == IcsPlayingWhite ||
\r
7839 gameMode == IcsPlayingBlack ||
\r
7840 gameMode == BeginningOfGame) {
\r
7841 char buf[MSG_SIZ];
\r
7842 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7844 if (first.pr != NoProc) {
\r
7845 SendToProgram(buf, &first);
\r
7847 if (second.pr != NoProc &&
\r
7848 gameMode == TwoMachinesPlay) {
\r
7849 SendToProgram(buf, &second);
\r
7854 if (appData.icsActive) {
\r
7855 if (appData.quietPlay &&
\r
7856 (gameMode == IcsPlayingWhite ||
\r
7857 gameMode == IcsPlayingBlack)) {
\r
7858 SendToICS(ics_prefix);
\r
7859 SendToICS("set shout 1\n");
\r
7861 nextGameMode = IcsIdle;
\r
7862 ics_user_moved = FALSE;
\r
7863 /* clean up premove. It's ugly when the game has ended and the
\r
7864 * premove highlights are still on the board.
\r
7867 gotPremove = FALSE;
\r
7868 ClearPremoveHighlights();
\r
7869 DrawPosition(FALSE, boards[currentMove]);
\r
7871 if (whosays == GE_ICS) {
\r
7874 if (gameMode == IcsPlayingWhite)
\r
7875 PlayIcsWinSound();
\r
7876 else if(gameMode == IcsPlayingBlack)
\r
7877 PlayIcsLossSound();
\r
7880 if (gameMode == IcsPlayingBlack)
\r
7881 PlayIcsWinSound();
\r
7882 else if(gameMode == IcsPlayingWhite)
\r
7883 PlayIcsLossSound();
\r
7886 PlayIcsDrawSound();
\r
7889 PlayIcsUnfinishedSound();
\r
7892 } else if (gameMode == EditGame ||
\r
7893 gameMode == PlayFromGameFile ||
\r
7894 gameMode == AnalyzeMode ||
\r
7895 gameMode == AnalyzeFile) {
\r
7896 nextGameMode = gameMode;
\r
7898 nextGameMode = EndOfGame;
\r
7903 nextGameMode = gameMode;
\r
7906 if (appData.noChessProgram) {
\r
7907 gameMode = nextGameMode;
\r
7909 endingGame = 0; /* [HGM] crash */
\r
7913 if (first.reuse) {
\r
7914 /* Put first chess program into idle state */
\r
7915 if (first.pr != NoProc &&
\r
7916 (gameMode == MachinePlaysWhite ||
\r
7917 gameMode == MachinePlaysBlack ||
\r
7918 gameMode == TwoMachinesPlay ||
\r
7919 gameMode == IcsPlayingWhite ||
\r
7920 gameMode == IcsPlayingBlack ||
\r
7921 gameMode == BeginningOfGame)) {
\r
7922 SendToProgram("force\n", &first);
\r
7923 if (first.usePing) {
\r
7924 char buf[MSG_SIZ];
\r
7925 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7926 SendToProgram(buf, &first);
\r
7929 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7930 /* Kill off first chess program */
\r
7931 if (first.isr != NULL)
\r
7932 RemoveInputSource(first.isr);
\r
7935 if (first.pr != NoProc) {
\r
7936 ExitAnalyzeMode();
\r
7937 DoSleep( appData.delayBeforeQuit );
\r
7938 SendToProgram("quit\n", &first);
\r
7939 DoSleep( appData.delayAfterQuit );
\r
7940 DestroyChildProcess(first.pr, first.useSigterm);
\r
7942 first.pr = NoProc;
\r
7944 if (second.reuse) {
\r
7945 /* Put second chess program into idle state */
\r
7946 if (second.pr != NoProc &&
\r
7947 gameMode == TwoMachinesPlay) {
\r
7948 SendToProgram("force\n", &second);
\r
7949 if (second.usePing) {
\r
7950 char buf[MSG_SIZ];
\r
7951 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7952 SendToProgram(buf, &second);
\r
7955 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7956 /* Kill off second chess program */
\r
7957 if (second.isr != NULL)
\r
7958 RemoveInputSource(second.isr);
\r
7959 second.isr = NULL;
\r
7961 if (second.pr != NoProc) {
\r
7962 DoSleep( appData.delayBeforeQuit );
\r
7963 SendToProgram("quit\n", &second);
\r
7964 DoSleep( appData.delayAfterQuit );
\r
7965 DestroyChildProcess(second.pr, second.useSigterm);
\r
7967 second.pr = NoProc;
\r
7970 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7973 if (first.twoMachinesColor[0] == 'w') {
\r
7974 first.matchWins++;
\r
7976 second.matchWins++;
\r
7980 if (first.twoMachinesColor[0] == 'b') {
\r
7981 first.matchWins++;
\r
7983 second.matchWins++;
\r
7989 if (matchGame < appData.matchGames) {
\r
7991 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
7992 tmp = first.twoMachinesColor;
\r
7993 first.twoMachinesColor = second.twoMachinesColor;
\r
7994 second.twoMachinesColor = tmp;
\r
7996 gameMode = nextGameMode;
\r
7998 if(appData.matchPause>10000 || appData.matchPause<10)
\r
7999 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8000 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8001 endingGame = 0; /* [HGM] crash */
\r
8004 char buf[MSG_SIZ];
\r
8005 gameMode = nextGameMode;
\r
8006 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8007 first.tidy, second.tidy,
\r
8008 first.matchWins, second.matchWins,
\r
8009 appData.matchGames - (first.matchWins + second.matchWins));
\r
8010 DisplayFatalError(buf, 0, 0);
\r
8013 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8014 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8015 ExitAnalyzeMode();
\r
8016 gameMode = nextGameMode;
\r
8018 endingGame = 0; /* [HGM] crash */
\r
8021 /* Assumes program was just initialized (initString sent).
\r
8022 Leaves program in force mode. */
\r
8024 FeedMovesToProgram(cps, upto)
\r
8025 ChessProgramState *cps;
\r
8030 if (appData.debugMode)
\r
8031 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8032 startedFromSetupPosition ? "position and " : "",
\r
8033 backwardMostMove, upto, cps->which);
\r
8034 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8035 // [HGM] variantswitch: make engine aware of new variant
\r
8036 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8037 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8038 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8039 SendToProgram(buf, cps);
\r
8040 currentlyInitializedVariant = gameInfo.variant;
\r
8042 SendToProgram("force\n", cps);
\r
8043 if (startedFromSetupPosition) {
\r
8044 SendBoard(cps, backwardMostMove);
\r
8045 if (appData.debugMode) {
\r
8046 fprintf(debugFP, "feedMoves\n");
\r
8049 for (i = backwardMostMove; i < upto; i++) {
\r
8050 SendMoveToProgram(i, cps);
\r
8056 ResurrectChessProgram()
\r
8058 /* The chess program may have exited.
\r
8059 If so, restart it and feed it all the moves made so far. */
\r
8061 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8063 StartChessProgram(&first);
\r
8064 InitChessProgram(&first, FALSE);
\r
8065 FeedMovesToProgram(&first, currentMove);
\r
8067 if (!first.sendTime) {
\r
8068 /* can't tell gnuchess what its clock should read,
\r
8069 so we bow to its notion. */
\r
8071 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8072 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8075 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8076 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8077 SendToProgram("analyze\n", &first);
\r
8078 first.analyzing = TRUE;
\r
8083 * Button procedures
\r
8086 Reset(redraw, init)
\r
8091 if (appData.debugMode) {
\r
8092 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8093 redraw, init, gameMode);
\r
8095 pausing = pauseExamInvalid = FALSE;
\r
8096 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8098 whiteFlag = blackFlag = FALSE;
\r
8099 userOfferedDraw = FALSE;
\r
8100 hintRequested = bookRequested = FALSE;
\r
8101 first.maybeThinking = FALSE;
\r
8102 second.maybeThinking = FALSE;
\r
8103 first.bookSuspend = FALSE; // [HGM] book
\r
8104 second.bookSuspend = FALSE;
\r
8105 thinkOutput[0] = NULLCHAR;
\r
8106 lastHint[0] = NULLCHAR;
\r
8107 ClearGameInfo(&gameInfo);
\r
8108 gameInfo.variant = StringToVariant(appData.variant);
\r
8109 ics_user_moved = ics_clock_paused = FALSE;
\r
8110 ics_getting_history = H_FALSE;
\r
8112 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8113 ClearProgramStats();
\r
8114 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8117 ClearHighlights();
\r
8118 flipView = appData.flipView;
\r
8119 ClearPremoveHighlights();
\r
8120 gotPremove = FALSE;
\r
8121 alarmSounded = FALSE;
\r
8123 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8124 if(appData.serverMovesName != NULL) {
\r
8125 /* [HGM] prepare to make moves file for broadcasting */
\r
8126 clock_t t = clock();
\r
8127 if(serverMoves != NULL) fclose(serverMoves);
\r
8128 serverMoves = fopen(appData.serverMovesName, "r");
\r
8129 if(serverMoves != NULL) {
\r
8130 fclose(serverMoves);
\r
8131 /* delay 15 sec before overwriting, so all clients can see end */
\r
8132 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8134 serverMoves = fopen(appData.serverMovesName, "w");
\r
8137 ExitAnalyzeMode();
\r
8138 gameMode = BeginningOfGame;
\r
8140 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8141 InitPosition(redraw);
\r
8142 for (i = 0; i < MAX_MOVES; i++) {
\r
8143 if (commentList[i] != NULL) {
\r
8144 free(commentList[i]);
\r
8145 commentList[i] = NULL;
\r
8149 timeRemaining[0][0] = whiteTimeRemaining;
\r
8150 timeRemaining[1][0] = blackTimeRemaining;
\r
8151 if (first.pr == NULL) {
\r
8152 StartChessProgram(&first);
\r
8155 InitChessProgram(&first, startedFromSetupPosition);
\r
8158 DisplayMessage("", "");
\r
8159 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8163 AutoPlayGameLoop()
\r
8166 if (!AutoPlayOneMove())
\r
8168 if (matchMode || appData.timeDelay == 0)
\r
8170 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8172 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8181 int fromX, fromY, toX, toY;
\r
8183 if (appData.debugMode) {
\r
8184 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8187 if (gameMode != PlayFromGameFile)
\r
8190 if (currentMove >= forwardMostMove) {
\r
8191 gameMode = EditGame;
\r
8194 /* [AS] Clear current move marker at the end of a game */
\r
8195 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8200 toX = moveList[currentMove][2] - AAA;
\r
8201 toY = moveList[currentMove][3] - ONE;
\r
8203 if (moveList[currentMove][1] == '@') {
\r
8204 if (appData.highlightLastMove) {
\r
8205 SetHighlights(-1, -1, toX, toY);
\r
8208 fromX = moveList[currentMove][0] - AAA;
\r
8209 fromY = moveList[currentMove][1] - ONE;
\r
8211 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8213 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8215 if (appData.highlightLastMove) {
\r
8216 SetHighlights(fromX, fromY, toX, toY);
\r
8219 DisplayMove(currentMove);
\r
8220 SendMoveToProgram(currentMove++, &first);
\r
8221 DisplayBothClocks();
\r
8222 DrawPosition(FALSE, boards[currentMove]);
\r
8223 // [HGM] PV info: always display, routine tests if empty
\r
8224 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8230 LoadGameOneMove(readAhead)
\r
8231 ChessMove readAhead;
\r
8233 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8234 char promoChar = NULLCHAR;
\r
8235 ChessMove moveType;
\r
8236 char move[MSG_SIZ];
\r
8239 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8240 gameMode != AnalyzeMode && gameMode != Training) {
\r
8241 gameFileFP = NULL;
\r
8245 yyboardindex = forwardMostMove;
\r
8246 if (readAhead != (ChessMove)0) {
\r
8247 moveType = readAhead;
\r
8249 if (gameFileFP == NULL)
\r
8251 moveType = (ChessMove) yylex();
\r
8255 switch (moveType) {
\r
8257 if (appData.debugMode)
\r
8258 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8260 if (*p == '{' || *p == '[' || *p == '(') {
\r
8261 p[strlen(p) - 1] = NULLCHAR;
\r
8265 /* append the comment but don't display it */
\r
8266 while (*p == '\n') p++;
\r
8267 AppendComment(currentMove, p);
\r
8270 case WhiteCapturesEnPassant:
\r
8271 case BlackCapturesEnPassant:
\r
8272 case WhitePromotionChancellor:
\r
8273 case BlackPromotionChancellor:
\r
8274 case WhitePromotionArchbishop:
\r
8275 case BlackPromotionArchbishop:
\r
8276 case WhitePromotionCentaur:
\r
8277 case BlackPromotionCentaur:
\r
8278 case WhitePromotionQueen:
\r
8279 case BlackPromotionQueen:
\r
8280 case WhitePromotionRook:
\r
8281 case BlackPromotionRook:
\r
8282 case WhitePromotionBishop:
\r
8283 case BlackPromotionBishop:
\r
8284 case WhitePromotionKnight:
\r
8285 case BlackPromotionKnight:
\r
8286 case WhitePromotionKing:
\r
8287 case BlackPromotionKing:
\r
8289 case WhiteKingSideCastle:
\r
8290 case WhiteQueenSideCastle:
\r
8291 case BlackKingSideCastle:
\r
8292 case BlackQueenSideCastle:
\r
8293 case WhiteKingSideCastleWild:
\r
8294 case WhiteQueenSideCastleWild:
\r
8295 case BlackKingSideCastleWild:
\r
8296 case BlackQueenSideCastleWild:
\r
8298 case WhiteHSideCastleFR:
\r
8299 case WhiteASideCastleFR:
\r
8300 case BlackHSideCastleFR:
\r
8301 case BlackASideCastleFR:
\r
8303 if (appData.debugMode)
\r
8304 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8305 fromX = currentMoveString[0] - AAA;
\r
8306 fromY = currentMoveString[1] - ONE;
\r
8307 toX = currentMoveString[2] - AAA;
\r
8308 toY = currentMoveString[3] - ONE;
\r
8309 promoChar = currentMoveString[4];
\r
8314 if (appData.debugMode)
\r
8315 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8316 fromX = moveType == WhiteDrop ?
\r
8317 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8318 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8319 fromY = DROP_RANK;
\r
8320 toX = currentMoveString[2] - AAA;
\r
8321 toY = currentMoveString[3] - ONE;
\r
8327 case GameUnfinished:
\r
8328 if (appData.debugMode)
\r
8329 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8330 p = strchr(yy_text, '{');
\r
8331 if (p == NULL) p = strchr(yy_text, '(');
\r
8334 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8336 q = strchr(p, *p == '{' ? '}' : ')');
\r
8337 if (q != NULL) *q = NULLCHAR;
\r
8340 GameEnds(moveType, p, GE_FILE);
\r
8342 if (cmailMsgLoaded) {
\r
8343 ClearHighlights();
\r
8344 flipView = WhiteOnMove(currentMove);
\r
8345 if (moveType == GameUnfinished) flipView = !flipView;
\r
8346 if (appData.debugMode)
\r
8347 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8351 case (ChessMove) 0: /* end of file */
\r
8352 if (appData.debugMode)
\r
8353 fprintf(debugFP, "Parser hit end of file\n");
\r
8354 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8355 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8359 case MT_CHECKMATE:
\r
8360 if (WhiteOnMove(currentMove)) {
\r
8361 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8363 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8366 case MT_STALEMATE:
\r
8367 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8373 case MoveNumberOne:
\r
8374 if (lastLoadGameStart == GNUChessGame) {
\r
8375 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8376 if (appData.debugMode)
\r
8377 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8378 yy_text, (int) moveType);
\r
8379 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8381 /* else fall thru */
\r
8384 case GNUChessGame:
\r
8386 /* Reached start of next game in file */
\r
8387 if (appData.debugMode)
\r
8388 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8389 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8390 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8394 case MT_CHECKMATE:
\r
8395 if (WhiteOnMove(currentMove)) {
\r
8396 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8398 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8401 case MT_STALEMATE:
\r
8402 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8408 case PositionDiagram: /* should not happen; ignore */
\r
8409 case ElapsedTime: /* ignore */
\r
8410 case NAG: /* ignore */
\r
8411 if (appData.debugMode)
\r
8412 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8413 yy_text, (int) moveType);
\r
8414 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8417 if (appData.testLegality) {
\r
8418 if (appData.debugMode)
\r
8419 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8420 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8421 (forwardMostMove / 2) + 1,
\r
8422 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8423 DisplayError(move, 0);
\r
8426 if (appData.debugMode)
\r
8427 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8428 yy_text, currentMoveString);
\r
8429 fromX = currentMoveString[0] - AAA;
\r
8430 fromY = currentMoveString[1] - ONE;
\r
8431 toX = currentMoveString[2] - AAA;
\r
8432 toY = currentMoveString[3] - ONE;
\r
8433 promoChar = currentMoveString[4];
\r
8437 case AmbiguousMove:
\r
8438 if (appData.debugMode)
\r
8439 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8440 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8441 (forwardMostMove / 2) + 1,
\r
8442 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8443 DisplayError(move, 0);
\r
8448 case ImpossibleMove:
\r
8449 if (appData.debugMode)
\r
8450 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8451 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8452 (forwardMostMove / 2) + 1,
\r
8453 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8454 DisplayError(move, 0);
\r
8460 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8461 DrawPosition(FALSE, boards[currentMove]);
\r
8462 DisplayBothClocks();
\r
8463 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8464 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8466 (void) StopLoadGameTimer();
\r
8467 gameFileFP = NULL;
\r
8468 cmailOldMove = forwardMostMove;
\r
8471 /* currentMoveString is set as a side-effect of yylex */
\r
8472 strcat(currentMoveString, "\n");
\r
8473 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8475 thinkOutput[0] = NULLCHAR;
\r
8476 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8477 currentMove = forwardMostMove;
\r
8482 /* Load the nth game from the given file */
\r
8484 LoadGameFromFile(filename, n, title, useList)
\r
8488 /*Boolean*/ int useList;
\r
8491 char buf[MSG_SIZ];
\r
8493 if (strcmp(filename, "-") == 0) {
\r
8497 f = fopen(filename, "rb");
\r
8499 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8500 DisplayError(buf, errno);
\r
8504 if (fseek(f, 0, 0) == -1) {
\r
8505 /* f is not seekable; probably a pipe */
\r
8508 if (useList && n == 0) {
\r
8509 int error = GameListBuild(f);
\r
8511 DisplayError(_("Cannot build game list"), error);
\r
8512 } else if (!ListEmpty(&gameList) &&
\r
8513 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8514 GameListPopUp(f, title);
\r
8517 GameListDestroy();
\r
8520 if (n == 0) n = 1;
\r
8521 return LoadGame(f, n, title, FALSE);
\r
8526 MakeRegisteredMove()
\r
8528 int fromX, fromY, toX, toY;
\r
8530 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8531 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8534 if (appData.debugMode)
\r
8535 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8536 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8538 thinkOutput[0] = NULLCHAR;
\r
8539 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8540 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8541 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8542 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8543 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8544 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8545 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8546 ShowMove(fromX, fromY, toX, toY);
\r
8548 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8549 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8554 case MT_CHECKMATE:
\r
8555 if (WhiteOnMove(currentMove)) {
\r
8556 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8558 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8562 case MT_STALEMATE:
\r
8563 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8569 case CMAIL_RESIGN:
\r
8570 if (WhiteOnMove(currentMove)) {
\r
8571 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8573 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8577 case CMAIL_ACCEPT:
\r
8578 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8589 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8591 CmailLoadGame(f, gameNumber, title, useList)
\r
8599 if (gameNumber > nCmailGames) {
\r
8600 DisplayError(_("No more games in this message"), 0);
\r
8603 if (f == lastLoadGameFP) {
\r
8604 int offset = gameNumber - lastLoadGameNumber;
\r
8605 if (offset == 0) {
\r
8606 cmailMsg[0] = NULLCHAR;
\r
8607 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8608 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8609 nCmailMovesRegistered--;
\r
8611 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8612 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8613 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8616 if (! RegisterMove()) return FALSE;
\r
8620 retVal = LoadGame(f, gameNumber, title, useList);
\r
8622 /* Make move registered during previous look at this game, if any */
\r
8623 MakeRegisteredMove();
\r
8625 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8626 commentList[currentMove]
\r
8627 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8628 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8634 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8636 ReloadGame(offset)
\r
8639 int gameNumber = lastLoadGameNumber + offset;
\r
8640 if (lastLoadGameFP == NULL) {
\r
8641 DisplayError(_("No game has been loaded yet"), 0);
\r
8644 if (gameNumber <= 0) {
\r
8645 DisplayError(_("Can't back up any further"), 0);
\r
8648 if (cmailMsgLoaded) {
\r
8649 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8650 lastLoadGameTitle, lastLoadGameUseList);
\r
8652 return LoadGame(lastLoadGameFP, gameNumber,
\r
8653 lastLoadGameTitle, lastLoadGameUseList);
\r
8659 /* Load the nth game from open file f */
\r
8661 LoadGame(f, gameNumber, title, useList)
\r
8668 char buf[MSG_SIZ];
\r
8669 int gn = gameNumber;
\r
8670 ListGame *lg = NULL;
\r
8671 int numPGNTags = 0;
\r
8673 GameMode oldGameMode;
\r
8674 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8676 if (appData.debugMode)
\r
8677 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8679 if (gameMode == Training )
\r
8680 SetTrainingModeOff();
\r
8682 oldGameMode = gameMode;
\r
8683 if (gameMode != BeginningOfGame) {
\r
8684 Reset(FALSE, TRUE);
\r
8688 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8689 fclose(lastLoadGameFP);
\r
8693 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8696 fseek(f, lg->offset, 0);
\r
8697 GameListHighlight(gameNumber);
\r
8701 DisplayError(_("Game number out of range"), 0);
\r
8705 GameListDestroy();
\r
8706 if (fseek(f, 0, 0) == -1) {
\r
8707 if (f == lastLoadGameFP ?
\r
8708 gameNumber == lastLoadGameNumber + 1 :
\r
8709 gameNumber == 1) {
\r
8712 DisplayError(_("Can't seek on game file"), 0);
\r
8717 lastLoadGameFP = f;
\r
8718 lastLoadGameNumber = gameNumber;
\r
8719 strcpy(lastLoadGameTitle, title);
\r
8720 lastLoadGameUseList = useList;
\r
8724 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8725 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8726 lg->gameInfo.black);
\r
8727 DisplayTitle(buf);
\r
8728 } else if (*title != NULLCHAR) {
\r
8729 if (gameNumber > 1) {
\r
8730 sprintf(buf, "%s %d", title, gameNumber);
\r
8731 DisplayTitle(buf);
\r
8733 DisplayTitle(title);
\r
8737 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8738 gameMode = PlayFromGameFile;
\r
8742 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8743 CopyBoard(boards[0], initialPosition);
\r
8747 * Skip the first gn-1 games in the file.
\r
8748 * Also skip over anything that precedes an identifiable
\r
8749 * start of game marker, to avoid being confused by
\r
8750 * garbage at the start of the file. Currently
\r
8751 * recognized start of game markers are the move number "1",
\r
8752 * the pattern "gnuchess .* game", the pattern
\r
8753 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8754 * A game that starts with one of the latter two patterns
\r
8755 * will also have a move number 1, possibly
\r
8756 * following a position diagram.
\r
8757 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8758 * start with an unnumbered move. Does that break anything?
\r
8760 cm = lastLoadGameStart = (ChessMove) 0;
\r
8762 yyboardindex = forwardMostMove;
\r
8763 cm = (ChessMove) yylex();
\r
8765 case (ChessMove) 0:
\r
8766 if (cmailMsgLoaded) {
\r
8767 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8769 Reset(TRUE, TRUE);
\r
8770 DisplayError(_("Game not found in file"), 0);
\r
8774 case GNUChessGame:
\r
8777 lastLoadGameStart = cm;
\r
8780 case MoveNumberOne:
\r
8781 switch (lastLoadGameStart) {
\r
8782 case GNUChessGame:
\r
8786 case MoveNumberOne:
\r
8787 case (ChessMove) 0:
\r
8788 gn--; /* count this game */
\r
8789 lastLoadGameStart = cm;
\r
8798 switch (lastLoadGameStart) {
\r
8799 case GNUChessGame:
\r
8801 case MoveNumberOne:
\r
8802 case (ChessMove) 0:
\r
8803 gn--; /* count this game */
\r
8804 lastLoadGameStart = cm;
\r
8807 lastLoadGameStart = cm; /* game counted already */
\r
8815 yyboardindex = forwardMostMove;
\r
8816 cm = (ChessMove) yylex();
\r
8817 } while (cm == PGNTag || cm == Comment);
\r
8824 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8825 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8826 != CMAIL_OLD_RESULT) {
\r
8827 nCmailResults ++ ;
\r
8828 cmailResult[ CMAIL_MAX_GAMES
\r
8829 - gn - 1] = CMAIL_OLD_RESULT;
\r
8835 /* Only a NormalMove can be at the start of a game
\r
8836 * without a position diagram. */
\r
8837 if (lastLoadGameStart == (ChessMove) 0) {
\r
8839 lastLoadGameStart = MoveNumberOne;
\r
8848 if (appData.debugMode)
\r
8849 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8851 if (cm == XBoardGame) {
\r
8852 /* Skip any header junk before position diagram and/or move 1 */
\r
8854 yyboardindex = forwardMostMove;
\r
8855 cm = (ChessMove) yylex();
\r
8857 if (cm == (ChessMove) 0 ||
\r
8858 cm == GNUChessGame || cm == XBoardGame) {
\r
8859 /* Empty game; pretend end-of-file and handle later */
\r
8860 cm = (ChessMove) 0;
\r
8864 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8865 cm == PGNTag || cm == Comment)
\r
8868 } else if (cm == GNUChessGame) {
\r
8869 if (gameInfo.event != NULL) {
\r
8870 free(gameInfo.event);
\r
8872 gameInfo.event = StrSave(yy_text);
\r
8875 startedFromSetupPosition = FALSE;
\r
8876 while (cm == PGNTag) {
\r
8877 if (appData.debugMode)
\r
8878 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8879 err = ParsePGNTag(yy_text, &gameInfo);
\r
8880 if (!err) numPGNTags++;
\r
8882 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8883 if(gameInfo.variant != oldVariant) {
\r
8884 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8885 InitPosition(TRUE);
\r
8886 oldVariant = gameInfo.variant;
\r
8887 if (appData.debugMode)
\r
8888 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8892 if (gameInfo.fen != NULL) {
\r
8893 Board initial_position;
\r
8894 startedFromSetupPosition = TRUE;
\r
8895 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8896 Reset(TRUE, TRUE);
\r
8897 DisplayError(_("Bad FEN position in file"), 0);
\r
8900 CopyBoard(boards[0], initial_position);
\r
8901 if (blackPlaysFirst) {
\r
8902 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8903 CopyBoard(boards[1], initial_position);
\r
8904 strcpy(moveList[0], "");
\r
8905 strcpy(parseList[0], "");
\r
8906 timeRemaining[0][1] = whiteTimeRemaining;
\r
8907 timeRemaining[1][1] = blackTimeRemaining;
\r
8908 if (commentList[0] != NULL) {
\r
8909 commentList[1] = commentList[0];
\r
8910 commentList[0] = NULL;
\r
8913 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8915 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8917 initialRulePlies = FENrulePlies;
\r
8918 epStatus[forwardMostMove] = FENepStatus;
\r
8919 for( i=0; i< nrCastlingRights; i++ )
\r
8920 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8922 yyboardindex = forwardMostMove;
\r
8923 free(gameInfo.fen);
\r
8924 gameInfo.fen = NULL;
\r
8927 yyboardindex = forwardMostMove;
\r
8928 cm = (ChessMove) yylex();
\r
8930 /* Handle comments interspersed among the tags */
\r
8931 while (cm == Comment) {
\r
8933 if (appData.debugMode)
\r
8934 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8936 if (*p == '{' || *p == '[' || *p == '(') {
\r
8937 p[strlen(p) - 1] = NULLCHAR;
\r
8940 while (*p == '\n') p++;
\r
8941 AppendComment(currentMove, p);
\r
8942 yyboardindex = forwardMostMove;
\r
8943 cm = (ChessMove) yylex();
\r
8947 /* don't rely on existence of Event tag since if game was
\r
8948 * pasted from clipboard the Event tag may not exist
\r
8950 if (numPGNTags > 0){
\r
8952 if (gameInfo.variant == VariantNormal) {
\r
8953 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8956 if( appData.autoDisplayTags ) {
\r
8957 tags = PGNTags(&gameInfo);
\r
8958 TagsPopUp(tags, CmailMsg());
\r
8963 /* Make something up, but don't display it now */
\r
8968 if (cm == PositionDiagram) {
\r
8971 Board initial_position;
\r
8973 if (appData.debugMode)
\r
8974 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8976 if (!startedFromSetupPosition) {
\r
8978 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8979 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8989 initial_position[i][j++] = CharToPiece(*p);
\r
8992 while (*p == ' ' || *p == '\t' ||
\r
8993 *p == '\n' || *p == '\r') p++;
\r
8995 if (strncmp(p, "black", strlen("black"))==0)
\r
8996 blackPlaysFirst = TRUE;
\r
8998 blackPlaysFirst = FALSE;
\r
8999 startedFromSetupPosition = TRUE;
\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
9017 yyboardindex = forwardMostMove;
\r
9018 cm = (ChessMove) yylex();
\r
9021 if (first.pr == NoProc) {
\r
9022 StartChessProgram(&first);
\r
9024 InitChessProgram(&first, FALSE);
\r
9025 SendToProgram("force\n", &first);
\r
9026 if (startedFromSetupPosition) {
\r
9027 SendBoard(&first, forwardMostMove);
\r
9028 if (appData.debugMode) {
\r
9029 fprintf(debugFP, "Load Game\n");
\r
9031 DisplayBothClocks();
\r
9034 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9035 loadFlag = appData.suppressLoadMoves;
\r
9037 while (cm == Comment) {
\r
9039 if (appData.debugMode)
\r
9040 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9042 if (*p == '{' || *p == '[' || *p == '(') {
\r
9043 p[strlen(p) - 1] = NULLCHAR;
\r
9046 while (*p == '\n') p++;
\r
9047 AppendComment(currentMove, p);
\r
9048 yyboardindex = forwardMostMove;
\r
9049 cm = (ChessMove) yylex();
\r
9052 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9053 cm == WhiteWins || cm == BlackWins ||
\r
9054 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9055 DisplayMessage("", _("No moves in game"));
\r
9056 if (cmailMsgLoaded) {
\r
9057 if (appData.debugMode)
\r
9058 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9059 ClearHighlights();
\r
9062 DrawPosition(FALSE, boards[currentMove]);
\r
9063 DisplayBothClocks();
\r
9064 gameMode = EditGame;
\r
9066 gameFileFP = NULL;
\r
9071 // [HGM] PV info: routine tests if comment empty
\r
9072 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9073 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9075 if (!matchMode && appData.timeDelay != 0)
\r
9076 DrawPosition(FALSE, boards[currentMove]);
\r
9078 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9079 programStats.ok_to_send = 1;
\r
9082 /* if the first token after the PGN tags is a move
\r
9083 * and not move number 1, retrieve it from the parser
\r
9085 if (cm != MoveNumberOne)
\r
9086 LoadGameOneMove(cm);
\r
9088 /* load the remaining moves from the file */
\r
9089 while (LoadGameOneMove((ChessMove)0)) {
\r
9090 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9091 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9094 /* rewind to the start of the game */
\r
9095 currentMove = backwardMostMove;
\r
9097 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9099 if (oldGameMode == AnalyzeFile ||
\r
9100 oldGameMode == AnalyzeMode) {
\r
9101 AnalyzeFileEvent();
\r
9104 if (matchMode || appData.timeDelay == 0) {
\r
9106 gameMode = EditGame;
\r
9108 } else if (appData.timeDelay > 0) {
\r
9109 AutoPlayGameLoop();
\r
9112 if (appData.debugMode)
\r
9113 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9115 loadFlag = 0; /* [HGM] true game starts */
\r
9119 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9121 ReloadPosition(offset)
\r
9124 int positionNumber = lastLoadPositionNumber + offset;
\r
9125 if (lastLoadPositionFP == NULL) {
\r
9126 DisplayError(_("No position has been loaded yet"), 0);
\r
9129 if (positionNumber <= 0) {
\r
9130 DisplayError(_("Can't back up any further"), 0);
\r
9133 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9134 lastLoadPositionTitle);
\r
9137 /* Load the nth position from the given file */
\r
9139 LoadPositionFromFile(filename, n, title)
\r
9145 char buf[MSG_SIZ];
\r
9147 if (strcmp(filename, "-") == 0) {
\r
9148 return LoadPosition(stdin, n, "stdin");
\r
9150 f = fopen(filename, "rb");
\r
9152 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9153 DisplayError(buf, errno);
\r
9156 return LoadPosition(f, n, title);
\r
9161 /* Load the nth position from the given open file, and close it */
\r
9163 LoadPosition(f, positionNumber, title)
\r
9165 int positionNumber;
\r
9168 char *p, line[MSG_SIZ];
\r
9169 Board initial_position;
\r
9170 int i, j, fenMode, pn;
\r
9172 if (gameMode == Training )
\r
9173 SetTrainingModeOff();
\r
9175 if (gameMode != BeginningOfGame) {
\r
9176 Reset(FALSE, TRUE);
\r
9178 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9179 fclose(lastLoadPositionFP);
\r
9181 if (positionNumber == 0) positionNumber = 1;
\r
9182 lastLoadPositionFP = f;
\r
9183 lastLoadPositionNumber = positionNumber;
\r
9184 strcpy(lastLoadPositionTitle, title);
\r
9185 if (first.pr == NoProc) {
\r
9186 StartChessProgram(&first);
\r
9187 InitChessProgram(&first, FALSE);
\r
9189 pn = positionNumber;
\r
9190 if (positionNumber < 0) {
\r
9191 /* Negative position number means to seek to that byte offset */
\r
9192 if (fseek(f, -positionNumber, 0) == -1) {
\r
9193 DisplayError(_("Can't seek on position file"), 0);
\r
9198 if (fseek(f, 0, 0) == -1) {
\r
9199 if (f == lastLoadPositionFP ?
\r
9200 positionNumber == lastLoadPositionNumber + 1 :
\r
9201 positionNumber == 1) {
\r
9204 DisplayError(_("Can't seek on position file"), 0);
\r
9209 /* See if this file is FEN or old-style xboard */
\r
9210 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9211 DisplayError(_("Position not found in file"), 0);
\r
9215 switch (line[0]) {
\r
9216 case '#': case 'x':
\r
9220 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9221 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9222 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9223 case '7': case '8': case '9':
\r
9224 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9225 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9226 case 'C': case 'W': case 'c': case 'w':
\r
9231 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9232 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9236 if (fenMode || line[0] == '#') pn--;
\r
9238 /* skip positions before number pn */
\r
9239 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9240 Reset(TRUE, TRUE);
\r
9241 DisplayError(_("Position not found in file"), 0);
\r
9244 if (fenMode || line[0] == '#') pn--;
\r
9249 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9250 DisplayError(_("Bad FEN position in file"), 0);
\r
9254 (void) fgets(line, MSG_SIZ, f);
\r
9255 (void) fgets(line, MSG_SIZ, f);
\r
9257 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9258 (void) fgets(line, MSG_SIZ, f);
\r
9259 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9262 initial_position[i][j++] = CharToPiece(*p);
\r
9266 blackPlaysFirst = FALSE;
\r
9268 (void) fgets(line, MSG_SIZ, f);
\r
9269 if (strncmp(line, "black", strlen("black"))==0)
\r
9270 blackPlaysFirst = TRUE;
\r
9273 startedFromSetupPosition = TRUE;
\r
9275 SendToProgram("force\n", &first);
\r
9276 CopyBoard(boards[0], initial_position);
\r
9277 if (blackPlaysFirst) {
\r
9278 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9279 strcpy(moveList[0], "");
\r
9280 strcpy(parseList[0], "");
\r
9281 CopyBoard(boards[1], initial_position);
\r
9282 DisplayMessage("", _("Black to play"));
\r
9284 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9285 DisplayMessage("", _("White to play"));
\r
9287 /* [HGM] copy FEN attributes as well */
\r
9289 initialRulePlies = FENrulePlies;
\r
9290 epStatus[forwardMostMove] = FENepStatus;
\r
9291 for( i=0; i< nrCastlingRights; i++ )
\r
9292 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9294 SendBoard(&first, forwardMostMove);
\r
9295 if (appData.debugMode) {
\r
9297 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9298 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9299 fprintf(debugFP, "Load Position\n");
\r
9302 if (positionNumber > 1) {
\r
9303 sprintf(line, "%s %d", title, positionNumber);
\r
9304 DisplayTitle(line);
\r
9306 DisplayTitle(title);
\r
9308 gameMode = EditGame;
\r
9311 timeRemaining[0][1] = whiteTimeRemaining;
\r
9312 timeRemaining[1][1] = blackTimeRemaining;
\r
9313 DrawPosition(FALSE, boards[currentMove]);
\r
9320 CopyPlayerNameIntoFileName(dest, src)
\r
9321 char **dest, *src;
\r
9323 while (*src != NULLCHAR && *src != ',') {
\r
9324 if (*src == ' ') {
\r
9328 *(*dest)++ = *src++;
\r
9333 char *DefaultFileName(ext)
\r
9336 static char def[MSG_SIZ];
\r
9339 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9341 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9343 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9347 def[0] = NULLCHAR;
\r
9352 /* Save the current game to the given file */
\r
9354 SaveGameToFile(filename, append)
\r
9359 char buf[MSG_SIZ];
\r
9361 if (strcmp(filename, "-") == 0) {
\r
9362 return SaveGame(stdout, 0, NULL);
\r
9364 f = fopen(filename, append ? "a" : "w");
\r
9366 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9367 DisplayError(buf, errno);
\r
9370 return SaveGame(f, 0, NULL);
\r
9379 static char buf[MSG_SIZ];
\r
9382 p = strchr(str, ' ');
\r
9383 if (p == NULL) return str;
\r
9384 strncpy(buf, str, p - str);
\r
9385 buf[p - str] = NULLCHAR;
\r
9389 #define PGN_MAX_LINE 75
\r
9391 #define PGN_SIDE_WHITE 0
\r
9392 #define PGN_SIDE_BLACK 1
\r
9395 static int FindFirstMoveOutOfBook( int side )
\r
9399 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9400 int index = backwardMostMove;
\r
9401 int has_book_hit = 0;
\r
9403 if( (index % 2) != side ) {
\r
9407 while( index < forwardMostMove ) {
\r
9408 /* Check to see if engine is in book */
\r
9409 int depth = pvInfoList[index].depth;
\r
9410 int score = pvInfoList[index].score;
\r
9413 if( depth <= 2 ) {
\r
9416 else if( score == 0 && depth == 63 ) {
\r
9417 in_book = 1; /* Zappa */
\r
9419 else if( score == 2 && depth == 99 ) {
\r
9420 in_book = 1; /* Abrok */
\r
9423 has_book_hit += in_book;
\r
9439 void GetOutOfBookInfo( char * buf )
\r
9443 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9445 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9446 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9450 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9451 for( i=0; i<2; i++ ) {
\r
9455 if( i > 0 && oob[0] >= 0 ) {
\r
9456 strcat( buf, " " );
\r
9459 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9460 sprintf( buf+strlen(buf), "%s%.2f",
\r
9461 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9462 pvInfoList[idx].score / 100.0 );
\r
9468 /* Save game in PGN style and close the file */
\r
9473 int i, offset, linelen, newblock;
\r
9475 // char *movetext;
\r
9477 int movelen, numlen, blank;
\r
9478 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9480 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9482 tm = time((time_t *) NULL);
\r
9484 PrintPGNTags(f, &gameInfo);
\r
9486 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9487 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9488 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9489 fprintf(f, "\n{--------------\n");
\r
9490 PrintPosition(f, backwardMostMove);
\r
9491 fprintf(f, "--------------}\n");
\r
9495 /* [AS] Out of book annotation */
\r
9496 if( appData.saveOutOfBookInfo ) {
\r
9499 GetOutOfBookInfo( buf );
\r
9501 if( buf[0] != '\0' ) {
\r
9502 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9509 i = backwardMostMove;
\r
9513 while (i < forwardMostMove) {
\r
9514 /* Print comments preceding this move */
\r
9515 if (commentList[i] != NULL) {
\r
9516 if (linelen > 0) fprintf(f, "\n");
\r
9517 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9522 /* Format move number */
\r
9523 if ((i % 2) == 0) {
\r
9524 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9527 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9529 numtext[0] = NULLCHAR;
\r
9532 numlen = strlen(numtext);
\r
9535 /* Print move number */
\r
9536 blank = linelen > 0 && numlen > 0;
\r
9537 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9546 fprintf(f, numtext);
\r
9547 linelen += numlen;
\r
9550 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9553 blank = linelen > 0 && movelen > 0;
\r
9554 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9563 fprintf(f, parseList[i]);
\r
9564 linelen += movelen;
\r
9566 /* [AS] Add PV info if present */
\r
9567 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9568 /* [HGM] add time */
\r
9569 char buf[MSG_SIZ]; int seconds = 0;
\r
9572 if(i >= backwardMostMove) {
\r
9573 if(WhiteOnMove(i))
\r
9574 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9575 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9577 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9578 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9580 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9582 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9585 if( seconds <= 0) buf[0] = 0; else
\r
9586 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9587 seconds = (seconds + 4)/10; // round to full seconds
\r
9588 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9589 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9592 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9593 pvInfoList[i].score >= 0 ? "+" : "",
\r
9594 pvInfoList[i].score / 100.0,
\r
9595 pvInfoList[i].depth,
\r
9598 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9600 /* Print score/depth */
\r
9601 blank = linelen > 0 && movelen > 0;
\r
9602 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9611 fprintf(f, move_buffer);
\r
9612 linelen += movelen;
\r
9618 /* Start a new line */
\r
9619 if (linelen > 0) fprintf(f, "\n");
\r
9621 /* Print comments after last move */
\r
9622 if (commentList[i] != NULL) {
\r
9623 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9626 /* Print result */
\r
9627 if (gameInfo.resultDetails != NULL &&
\r
9628 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9629 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9630 PGNResult(gameInfo.result));
\r
9632 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9639 /* Save game in old style and close the file */
\r
9641 SaveGameOldStyle(f)
\r
9647 tm = time((time_t *) NULL);
\r
9649 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9650 PrintOpponents(f);
\r
9652 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9653 fprintf(f, "\n[--------------\n");
\r
9654 PrintPosition(f, backwardMostMove);
\r
9655 fprintf(f, "--------------]\n");
\r
9660 i = backwardMostMove;
\r
9661 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9663 while (i < forwardMostMove) {
\r
9664 if (commentList[i] != NULL) {
\r
9665 fprintf(f, "[%s]\n", commentList[i]);
\r
9668 if ((i % 2) == 1) {
\r
9669 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9672 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9674 if (commentList[i] != NULL) {
\r
9678 if (i >= forwardMostMove) {
\r
9682 fprintf(f, "%s\n", parseList[i]);
\r
9687 if (commentList[i] != NULL) {
\r
9688 fprintf(f, "[%s]\n", commentList[i]);
\r
9691 /* This isn't really the old style, but it's close enough */
\r
9692 if (gameInfo.resultDetails != NULL &&
\r
9693 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9694 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9695 gameInfo.resultDetails);
\r
9697 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9704 /* Save the current game to open file f and close the file */
\r
9706 SaveGame(f, dummy, dummy2)
\r
9711 if (gameMode == EditPosition) EditPositionDone();
\r
9712 if (appData.oldSaveStyle)
\r
9713 return SaveGameOldStyle(f);
\r
9715 return SaveGamePGN(f);
\r
9718 /* Save the current position to the given file */
\r
9720 SavePositionToFile(filename)
\r
9724 char buf[MSG_SIZ];
\r
9726 if (strcmp(filename, "-") == 0) {
\r
9727 return SavePosition(stdout, 0, NULL);
\r
9729 f = fopen(filename, "a");
\r
9731 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9732 DisplayError(buf, errno);
\r
9735 SavePosition(f, 0, NULL);
\r
9741 /* Save the current position to the given open file and close the file */
\r
9743 SavePosition(f, dummy, dummy2)
\r
9751 if (appData.oldSaveStyle) {
\r
9752 tm = time((time_t *) NULL);
\r
9754 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9755 PrintOpponents(f);
\r
9756 fprintf(f, "[--------------\n");
\r
9757 PrintPosition(f, currentMove);
\r
9758 fprintf(f, "--------------]\n");
\r
9760 fen = PositionToFEN(currentMove, 1);
\r
9761 fprintf(f, "%s\n", fen);
\r
9769 ReloadCmailMsgEvent(unregister)
\r
9773 static char *inFilename = NULL;
\r
9774 static char *outFilename;
\r
9776 struct stat inbuf, outbuf;
\r
9779 /* Any registered moves are unregistered if unregister is set, */
\r
9780 /* i.e. invoked by the signal handler */
\r
9782 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9783 cmailMoveRegistered[i] = FALSE;
\r
9784 if (cmailCommentList[i] != NULL) {
\r
9785 free(cmailCommentList[i]);
\r
9786 cmailCommentList[i] = NULL;
\r
9789 nCmailMovesRegistered = 0;
\r
9792 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9793 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9795 nCmailResults = 0;
\r
9797 if (inFilename == NULL) {
\r
9798 /* Because the filenames are static they only get malloced once */
\r
9799 /* and they never get freed */
\r
9800 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9801 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9803 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9804 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9807 status = stat(outFilename, &outbuf);
\r
9809 cmailMailedMove = FALSE;
\r
9811 status = stat(inFilename, &inbuf);
\r
9812 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9815 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9816 counts the games, notes how each one terminated, etc.
\r
9818 It would be nice to remove this kludge and instead gather all
\r
9819 the information while building the game list. (And to keep it
\r
9820 in the game list nodes instead of having a bunch of fixed-size
\r
9821 parallel arrays.) Note this will require getting each game's
\r
9822 termination from the PGN tags, as the game list builder does
\r
9823 not process the game moves. --mann
\r
9825 cmailMsgLoaded = TRUE;
\r
9826 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9828 /* Load first game in the file or popup game menu */
\r
9829 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9831 #endif /* !WIN32 */
\r
9839 char string[MSG_SIZ];
\r
9841 if ( cmailMailedMove
\r
9842 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9843 return TRUE; /* Allow free viewing */
\r
9846 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9847 /* with the move registered when the conditions for registering no */
\r
9849 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9850 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9851 nCmailMovesRegistered --;
\r
9853 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9855 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9856 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9860 if (cmailOldMove == -1) {
\r
9861 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9865 if (currentMove > cmailOldMove + 1) {
\r
9866 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9870 if (currentMove < cmailOldMove) {
\r
9871 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9875 if (forwardMostMove > currentMove) {
\r
9876 /* Silently truncate extra moves */
\r
9880 if ( (currentMove == cmailOldMove + 1)
\r
9881 || ( (currentMove == cmailOldMove)
\r
9882 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9883 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9884 if (gameInfo.result != GameUnfinished) {
\r
9885 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9888 if (commentList[currentMove] != NULL) {
\r
9889 cmailCommentList[lastLoadGameNumber - 1]
\r
9890 = StrSave(commentList[currentMove]);
\r
9892 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9894 if (appData.debugMode)
\r
9895 fprintf(debugFP, "Saving %s for game %d\n",
\r
9896 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9899 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9901 f = fopen(string, "w");
\r
9902 if (appData.oldSaveStyle) {
\r
9903 SaveGameOldStyle(f); /* also closes the file */
\r
9905 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9906 f = fopen(string, "w");
\r
9907 SavePosition(f, 0, NULL); /* also closes the file */
\r
9909 fprintf(f, "{--------------\n");
\r
9910 PrintPosition(f, currentMove);
\r
9911 fprintf(f, "--------------}\n\n");
\r
9913 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9916 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9917 nCmailMovesRegistered ++;
\r
9918 } else if (nCmailGames == 1) {
\r
9919 DisplayError(_("You have not made a move yet"), 0);
\r
9930 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9931 FILE *commandOutput;
\r
9932 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9933 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9939 if (! cmailMsgLoaded) {
\r
9940 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9944 if (nCmailGames == nCmailResults) {
\r
9945 DisplayError(_("No unfinished games"), 0);
\r
9949 #if CMAIL_PROHIBIT_REMAIL
\r
9950 if (cmailMailedMove) {
\r
9951 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
9952 DisplayError(msg, 0);
\r
9957 if (! (cmailMailedMove || RegisterMove())) return;
\r
9959 if ( cmailMailedMove
\r
9960 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9961 sprintf(string, partCommandString,
\r
9962 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9963 commandOutput = popen(string, "r");
\r
9965 if (commandOutput == NULL) {
\r
9966 DisplayError(_("Failed to invoke cmail"), 0);
\r
9968 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9969 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9971 if (nBuffers > 1) {
\r
9972 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9973 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9974 nBytes = MSG_SIZ - 1;
\r
9976 (void) memcpy(msg, buffer, nBytes);
\r
9978 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9980 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9981 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9984 for (i = 0; i < nCmailGames; i ++) {
\r
9985 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9990 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9992 sprintf(buffer, "%s/%s.%s.archive",
\r
9994 appData.cmailGameName,
\r
9996 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
9997 cmailMsgLoaded = FALSE;
\r
10001 DisplayInformation(msg);
\r
10002 pclose(commandOutput);
\r
10005 if ((*cmailMsg) != '\0') {
\r
10006 DisplayInformation(cmailMsg);
\r
10011 #endif /* !WIN32 */
\r
10020 int prependComma = 0;
\r
10022 char string[MSG_SIZ]; /* Space for game-list */
\r
10025 if (!cmailMsgLoaded) return "";
\r
10027 if (cmailMailedMove) {
\r
10028 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10030 /* Create a list of games left */
\r
10031 sprintf(string, "[");
\r
10032 for (i = 0; i < nCmailGames; i ++) {
\r
10033 if (! ( cmailMoveRegistered[i]
\r
10034 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10035 if (prependComma) {
\r
10036 sprintf(number, ",%d", i + 1);
\r
10038 sprintf(number, "%d", i + 1);
\r
10039 prependComma = 1;
\r
10042 strcat(string, number);
\r
10045 strcat(string, "]");
\r
10047 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10048 switch (nCmailGames) {
\r
10050 sprintf(cmailMsg,
\r
10051 _("Still need to make move for game\n"));
\r
10055 sprintf(cmailMsg,
\r
10056 _("Still need to make moves for both games\n"));
\r
10060 sprintf(cmailMsg,
\r
10061 _("Still need to make moves for all %d games\n"),
\r
10066 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10068 sprintf(cmailMsg,
\r
10069 _("Still need to make a move for game %s\n"),
\r
10074 if (nCmailResults == nCmailGames) {
\r
10075 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10077 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10082 sprintf(cmailMsg,
\r
10083 _("Still need to make moves for games %s\n"),
\r
10089 #endif /* WIN32 */
\r
10095 if (gameMode == Training)
\r
10096 SetTrainingModeOff();
\r
10098 Reset(TRUE, TRUE);
\r
10099 cmailMsgLoaded = FALSE;
\r
10100 if (appData.icsActive) {
\r
10101 SendToICS(ics_prefix);
\r
10102 SendToICS("refresh\n");
\r
10107 ExitEvent(status)
\r
10111 if (exiting > 2) {
\r
10112 /* Give up on clean exit */
\r
10115 if (exiting > 1) {
\r
10116 /* Keep trying for clean exit */
\r
10120 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10122 if (telnetISR != NULL) {
\r
10123 RemoveInputSource(telnetISR);
\r
10125 if (icsPR != NoProc) {
\r
10126 DestroyChildProcess(icsPR, TRUE);
\r
10129 /* Save game if resource set and not already saved by GameEnds() */
\r
10130 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10131 && forwardMostMove > 0) {
\r
10132 if (*appData.saveGameFile != NULLCHAR) {
\r
10133 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10134 } else if (appData.autoSaveGames) {
\r
10137 if (*appData.savePositionFile != NULLCHAR) {
\r
10138 SavePositionToFile(appData.savePositionFile);
\r
10141 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10143 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10144 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10146 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10147 /* make sure this other one finishes before killing it! */
\r
10148 if(endingGame) { int count = 0;
\r
10149 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10150 while(endingGame && count++ < 10) DoSleep(1);
\r
10151 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10154 /* Kill off chess programs */
\r
10155 if (first.pr != NoProc) {
\r
10156 ExitAnalyzeMode();
\r
10158 DoSleep( appData.delayBeforeQuit );
\r
10159 SendToProgram("quit\n", &first);
\r
10160 DoSleep( appData.delayAfterQuit );
\r
10161 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10163 if (second.pr != NoProc) {
\r
10164 DoSleep( appData.delayBeforeQuit );
\r
10165 SendToProgram("quit\n", &second);
\r
10166 DoSleep( appData.delayAfterQuit );
\r
10167 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10169 if (first.isr != NULL) {
\r
10170 RemoveInputSource(first.isr);
\r
10172 if (second.isr != NULL) {
\r
10173 RemoveInputSource(second.isr);
\r
10176 ShutDownFrontEnd();
\r
10183 if (appData.debugMode)
\r
10184 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10188 if (gameMode == MachinePlaysWhite ||
\r
10189 gameMode == MachinePlaysBlack) {
\r
10192 DisplayBothClocks();
\r
10194 if (gameMode == PlayFromGameFile) {
\r
10195 if (appData.timeDelay >= 0)
\r
10196 AutoPlayGameLoop();
\r
10197 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10198 Reset(FALSE, TRUE);
\r
10199 SendToICS(ics_prefix);
\r
10200 SendToICS("refresh\n");
\r
10201 } else if (currentMove < forwardMostMove) {
\r
10202 ForwardInner(forwardMostMove);
\r
10204 pauseExamInvalid = FALSE;
\r
10206 switch (gameMode) {
\r
10209 case IcsExamining:
\r
10210 pauseExamForwardMostMove = forwardMostMove;
\r
10211 pauseExamInvalid = FALSE;
\r
10212 /* fall through */
\r
10213 case IcsObserving:
\r
10214 case IcsPlayingWhite:
\r
10215 case IcsPlayingBlack:
\r
10219 case PlayFromGameFile:
\r
10220 (void) StopLoadGameTimer();
\r
10224 case BeginningOfGame:
\r
10225 if (appData.icsActive) return;
\r
10226 /* else fall through */
\r
10227 case MachinePlaysWhite:
\r
10228 case MachinePlaysBlack:
\r
10229 case TwoMachinesPlay:
\r
10230 if (forwardMostMove == 0)
\r
10231 return; /* don't pause if no one has moved */
\r
10232 if ((gameMode == MachinePlaysWhite &&
\r
10233 !WhiteOnMove(forwardMostMove)) ||
\r
10234 (gameMode == MachinePlaysBlack &&
\r
10235 WhiteOnMove(forwardMostMove))) {
\r
10246 EditCommentEvent()
\r
10248 char title[MSG_SIZ];
\r
10250 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10251 strcpy(title, _("Edit comment"));
\r
10253 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10254 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10255 parseList[currentMove - 1]);
\r
10258 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10265 char *tags = PGNTags(&gameInfo);
\r
10266 EditTagsPopUp(tags);
\r
10271 AnalyzeModeEvent()
\r
10273 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10276 if (gameMode != AnalyzeFile) {
\r
10277 if (!appData.icsEngineAnalyze) {
\r
10279 if (gameMode != EditGame) return;
\r
10281 ResurrectChessProgram();
\r
10282 SendToProgram("analyze\n", &first);
\r
10283 first.analyzing = TRUE;
\r
10284 /*first.maybeThinking = TRUE;*/
\r
10285 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10286 AnalysisPopUp(_("Analysis"),
\r
10287 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10289 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10294 StartAnalysisClock();
\r
10295 GetTimeMark(&lastNodeCountTime);
\r
10296 lastNodeCount = 0;
\r
10300 AnalyzeFileEvent()
\r
10302 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10305 if (gameMode != AnalyzeMode) {
\r
10307 if (gameMode != EditGame) return;
\r
10308 ResurrectChessProgram();
\r
10309 SendToProgram("analyze\n", &first);
\r
10310 first.analyzing = TRUE;
\r
10311 /*first.maybeThinking = TRUE;*/
\r
10312 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10313 AnalysisPopUp(_("Analysis"),
\r
10314 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10316 gameMode = AnalyzeFile;
\r
10321 StartAnalysisClock();
\r
10322 GetTimeMark(&lastNodeCountTime);
\r
10323 lastNodeCount = 0;
\r
10327 MachineWhiteEvent()
\r
10329 char buf[MSG_SIZ];
\r
10330 char *bookHit = NULL;
\r
10332 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10336 if (gameMode == PlayFromGameFile ||
\r
10337 gameMode == TwoMachinesPlay ||
\r
10338 gameMode == Training ||
\r
10339 gameMode == AnalyzeMode ||
\r
10340 gameMode == EndOfGame)
\r
10343 if (gameMode == EditPosition)
\r
10344 EditPositionDone();
\r
10346 if (!WhiteOnMove(currentMove)) {
\r
10347 DisplayError(_("It is not White's turn"), 0);
\r
10351 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10352 ExitAnalyzeMode();
\r
10354 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10355 gameMode == AnalyzeFile)
\r
10358 ResurrectChessProgram(); /* in case it isn't running */
\r
10359 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10360 gameMode = MachinePlaysWhite;
\r
10363 gameMode = MachinePlaysWhite;
\r
10367 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10368 DisplayTitle(buf);
\r
10369 if (first.sendName) {
\r
10370 sprintf(buf, "name %s\n", gameInfo.black);
\r
10371 SendToProgram(buf, &first);
\r
10373 if (first.sendTime) {
\r
10374 if (first.useColors) {
\r
10375 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10377 SendTimeRemaining(&first, TRUE);
\r
10379 if (first.useColors) {
\r
10380 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10382 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10383 SetMachineThinkingEnables();
\r
10384 first.maybeThinking = TRUE;
\r
10387 if (appData.autoFlipView && !flipView) {
\r
10388 flipView = !flipView;
\r
10389 DrawPosition(FALSE, NULL);
\r
10390 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10393 if(bookHit) { // [HGM] book: simulate book reply
\r
10394 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10396 programStats.nodes = programStats.depth = programStats.time =
\r
10397 programStats.score = programStats.got_only_move = 0;
\r
10398 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10400 strcpy(bookMove, "move ");
\r
10401 strcat(bookMove, bookHit);
\r
10402 HandleMachineMove(bookMove, &first);
\r
10407 MachineBlackEvent()
\r
10409 char buf[MSG_SIZ];
\r
10410 char *bookHit = NULL;
\r
10412 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10416 if (gameMode == PlayFromGameFile ||
\r
10417 gameMode == TwoMachinesPlay ||
\r
10418 gameMode == Training ||
\r
10419 gameMode == AnalyzeMode ||
\r
10420 gameMode == EndOfGame)
\r
10423 if (gameMode == EditPosition)
\r
10424 EditPositionDone();
\r
10426 if (WhiteOnMove(currentMove)) {
\r
10427 DisplayError(_("It is not Black's turn"), 0);
\r
10431 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10432 ExitAnalyzeMode();
\r
10434 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10435 gameMode == AnalyzeFile)
\r
10438 ResurrectChessProgram(); /* in case it isn't running */
\r
10439 gameMode = MachinePlaysBlack;
\r
10443 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10444 DisplayTitle(buf);
\r
10445 if (first.sendName) {
\r
10446 sprintf(buf, "name %s\n", gameInfo.white);
\r
10447 SendToProgram(buf, &first);
\r
10449 if (first.sendTime) {
\r
10450 if (first.useColors) {
\r
10451 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10453 SendTimeRemaining(&first, FALSE);
\r
10455 if (first.useColors) {
\r
10456 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10458 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10459 SetMachineThinkingEnables();
\r
10460 first.maybeThinking = TRUE;
\r
10463 if (appData.autoFlipView && flipView) {
\r
10464 flipView = !flipView;
\r
10465 DrawPosition(FALSE, NULL);
\r
10466 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10468 if(bookHit) { // [HGM] book: simulate book reply
\r
10469 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10471 programStats.nodes = programStats.depth = programStats.time =
\r
10472 programStats.score = programStats.got_only_move = 0;
\r
10473 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10475 strcpy(bookMove, "move ");
\r
10476 strcat(bookMove, bookHit);
\r
10477 HandleMachineMove(bookMove, &first);
\r
10483 DisplayTwoMachinesTitle()
\r
10485 char buf[MSG_SIZ];
\r
10486 if (appData.matchGames > 0) {
\r
10487 if (first.twoMachinesColor[0] == 'w') {
\r
10488 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10489 gameInfo.white, gameInfo.black,
\r
10490 first.matchWins, second.matchWins,
\r
10491 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10493 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10494 gameInfo.white, gameInfo.black,
\r
10495 second.matchWins, first.matchWins,
\r
10496 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10499 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10501 DisplayTitle(buf);
\r
10505 TwoMachinesEvent P((void))
\r
10508 char buf[MSG_SIZ];
\r
10509 ChessProgramState *onmove;
\r
10510 char *bookHit = NULL;
\r
10512 if (appData.noChessProgram) return;
\r
10514 switch (gameMode) {
\r
10515 case TwoMachinesPlay:
\r
10517 case MachinePlaysWhite:
\r
10518 case MachinePlaysBlack:
\r
10519 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10520 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10523 /* fall through */
\r
10524 case BeginningOfGame:
\r
10525 case PlayFromGameFile:
\r
10528 if (gameMode != EditGame) return;
\r
10530 case EditPosition:
\r
10531 EditPositionDone();
\r
10533 case AnalyzeMode:
\r
10534 case AnalyzeFile:
\r
10535 ExitAnalyzeMode();
\r
10542 forwardMostMove = currentMove;
\r
10543 ResurrectChessProgram(); /* in case first program isn't running */
\r
10545 if (second.pr == NULL) {
\r
10546 StartChessProgram(&second);
\r
10547 if (second.protocolVersion == 1) {
\r
10548 TwoMachinesEventIfReady();
\r
10550 /* kludge: allow timeout for initial "feature" command */
\r
10552 DisplayMessage("", _("Starting second chess program"));
\r
10553 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10557 DisplayMessage("", "");
\r
10558 InitChessProgram(&second, FALSE);
\r
10559 SendToProgram("force\n", &second);
\r
10560 if (startedFromSetupPosition) {
\r
10561 SendBoard(&second, backwardMostMove);
\r
10562 if (appData.debugMode) {
\r
10563 fprintf(debugFP, "Two Machines\n");
\r
10566 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10567 SendMoveToProgram(i, &second);
\r
10570 gameMode = TwoMachinesPlay;
\r
10574 DisplayTwoMachinesTitle();
\r
10575 firstMove = TRUE;
\r
10576 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10579 onmove = &second;
\r
10582 SendToProgram(first.computerString, &first);
\r
10583 if (first.sendName) {
\r
10584 sprintf(buf, "name %s\n", second.tidy);
\r
10585 SendToProgram(buf, &first);
\r
10587 SendToProgram(second.computerString, &second);
\r
10588 if (second.sendName) {
\r
10589 sprintf(buf, "name %s\n", first.tidy);
\r
10590 SendToProgram(buf, &second);
\r
10594 if (!first.sendTime || !second.sendTime) {
\r
10595 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10596 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10598 if (onmove->sendTime) {
\r
10599 if (onmove->useColors) {
\r
10600 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10602 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10604 if (onmove->useColors) {
\r
10605 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10607 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10608 // SendToProgram("go\n", onmove);
\r
10609 onmove->maybeThinking = TRUE;
\r
10610 SetMachineThinkingEnables();
\r
10614 if(bookHit) { // [HGM] book: simulate book reply
\r
10615 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10617 programStats.nodes = programStats.depth = programStats.time =
\r
10618 programStats.score = programStats.got_only_move = 0;
\r
10619 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10621 strcpy(bookMove, "move ");
\r
10622 strcat(bookMove, bookHit);
\r
10623 HandleMachineMove(bookMove, &first);
\r
10630 if (gameMode == Training) {
\r
10631 SetTrainingModeOff();
\r
10632 gameMode = PlayFromGameFile;
\r
10633 DisplayMessage("", _("Training mode off"));
\r
10635 gameMode = Training;
\r
10636 animateTraining = appData.animate;
\r
10638 /* make sure we are not already at the end of the game */
\r
10639 if (currentMove < forwardMostMove) {
\r
10640 SetTrainingModeOn();
\r
10641 DisplayMessage("", _("Training mode on"));
\r
10643 gameMode = PlayFromGameFile;
\r
10644 DisplayError(_("Already at end of game"), 0);
\r
10653 if (!appData.icsActive) return;
\r
10654 switch (gameMode) {
\r
10655 case IcsPlayingWhite:
\r
10656 case IcsPlayingBlack:
\r
10657 case IcsObserving:
\r
10659 case BeginningOfGame:
\r
10660 case IcsExamining:
\r
10666 case EditPosition:
\r
10667 EditPositionDone();
\r
10670 case AnalyzeMode:
\r
10671 case AnalyzeFile:
\r
10672 ExitAnalyzeMode();
\r
10680 gameMode = IcsIdle;
\r
10691 switch (gameMode) {
\r
10693 SetTrainingModeOff();
\r
10695 case MachinePlaysWhite:
\r
10696 case MachinePlaysBlack:
\r
10697 case BeginningOfGame:
\r
10698 SendToProgram("force\n", &first);
\r
10699 SetUserThinkingEnables();
\r
10701 case PlayFromGameFile:
\r
10702 (void) StopLoadGameTimer();
\r
10703 if (gameFileFP != NULL) {
\r
10704 gameFileFP = NULL;
\r
10707 case EditPosition:
\r
10708 EditPositionDone();
\r
10710 case AnalyzeMode:
\r
10711 case AnalyzeFile:
\r
10712 ExitAnalyzeMode();
\r
10713 SendToProgram("force\n", &first);
\r
10715 case TwoMachinesPlay:
\r
10716 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10717 ResurrectChessProgram();
\r
10718 SetUserThinkingEnables();
\r
10721 ResurrectChessProgram();
\r
10723 case IcsPlayingBlack:
\r
10724 case IcsPlayingWhite:
\r
10725 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10727 case IcsObserving:
\r
10728 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10730 case IcsExamining:
\r
10731 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10742 first.offeredDraw = second.offeredDraw = 0;
\r
10744 if (gameMode == PlayFromGameFile) {
\r
10745 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10746 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10747 DisplayTitle("");
\r
10750 if (gameMode == MachinePlaysWhite ||
\r
10751 gameMode == MachinePlaysBlack ||
\r
10752 gameMode == TwoMachinesPlay ||
\r
10753 gameMode == EndOfGame) {
\r
10754 i = forwardMostMove;
\r
10755 while (i > currentMove) {
\r
10756 SendToProgram("undo\n", &first);
\r
10759 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10760 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10761 DisplayBothClocks();
\r
10762 if (whiteFlag || blackFlag) {
\r
10763 whiteFlag = blackFlag = 0;
\r
10765 DisplayTitle("");
\r
10768 gameMode = EditGame;
\r
10775 EditPositionEvent()
\r
10777 if (gameMode == EditPosition) {
\r
10783 if (gameMode != EditGame) return;
\r
10785 gameMode = EditPosition;
\r
10788 if (currentMove > 0)
\r
10789 CopyBoard(boards[0], boards[currentMove]);
\r
10791 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10793 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10794 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10799 ExitAnalyzeMode()
\r
10801 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10802 if (appData.icsEngineAnalyze) {
\r
10803 appData.icsEngineAnalyze = FALSE;
\r
10805 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10807 if (first.analysisSupport && first.analyzing) {
\r
10808 SendToProgram("exit\n", &first);
\r
10809 first.analyzing = FALSE;
\r
10811 AnalysisPopDown();
\r
10812 thinkOutput[0] = NULLCHAR;
\r
10816 EditPositionDone()
\r
10818 startedFromSetupPosition = TRUE;
\r
10819 InitChessProgram(&first, FALSE);
\r
10820 SendToProgram("force\n", &first);
\r
10821 if (blackPlaysFirst) {
\r
10822 strcpy(moveList[0], "");
\r
10823 strcpy(parseList[0], "");
\r
10824 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10825 CopyBoard(boards[1], boards[0]);
\r
10826 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10828 epStatus[1] = epStatus[0];
\r
10829 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10832 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10834 SendBoard(&first, forwardMostMove);
\r
10835 if (appData.debugMode) {
\r
10836 fprintf(debugFP, "EditPosDone\n");
\r
10838 DisplayTitle("");
\r
10839 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10840 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10841 gameMode = EditGame;
\r
10843 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10844 ClearHighlights(); /* [AS] */
\r
10847 /* Pause for `ms' milliseconds */
\r
10848 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10855 GetTimeMark(&m1);
\r
10857 GetTimeMark(&m2);
\r
10858 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10861 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10863 SendMultiLineToICS(buf)
\r
10866 char temp[MSG_SIZ+1], *p;
\r
10869 len = strlen(buf);
\r
10870 if (len > MSG_SIZ)
\r
10873 strncpy(temp, buf, len);
\r
10878 if (*p == '\n' || *p == '\r')
\r
10883 strcat(temp, "\n");
\r
10885 SendToPlayer(temp, strlen(temp));
\r
10889 SetWhiteToPlayEvent()
\r
10891 if (gameMode == EditPosition) {
\r
10892 blackPlaysFirst = FALSE;
\r
10893 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10894 } else if (gameMode == IcsExamining) {
\r
10895 SendToICS(ics_prefix);
\r
10896 SendToICS("tomove white\n");
\r
10901 SetBlackToPlayEvent()
\r
10903 if (gameMode == EditPosition) {
\r
10904 blackPlaysFirst = TRUE;
\r
10905 currentMove = 1; /* kludge */
\r
10906 DisplayBothClocks();
\r
10908 } else if (gameMode == IcsExamining) {
\r
10909 SendToICS(ics_prefix);
\r
10910 SendToICS("tomove black\n");
\r
10915 EditPositionMenuEvent(selection, x, y)
\r
10916 ChessSquare selection;
\r
10919 char buf[MSG_SIZ];
\r
10920 ChessSquare piece = boards[0][y][x];
\r
10922 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10924 switch (selection) {
\r
10926 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10927 SendToICS(ics_prefix);
\r
10928 SendToICS("bsetup clear\n");
\r
10929 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10930 SendToICS(ics_prefix);
\r
10931 SendToICS("clearboard\n");
\r
10933 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10934 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10935 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10936 if (gameMode == IcsExamining) {
\r
10937 if (boards[currentMove][y][x] != EmptySquare) {
\r
10938 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10939 AAA + x, ONE + y);
\r
10943 boards[0][y][x] = p;
\r
10948 if (gameMode == EditPosition) {
\r
10949 DrawPosition(FALSE, boards[0]);
\r
10954 SetWhiteToPlayEvent();
\r
10958 SetBlackToPlayEvent();
\r
10961 case EmptySquare:
\r
10962 if (gameMode == IcsExamining) {
\r
10963 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10966 boards[0][y][x] = EmptySquare;
\r
10967 DrawPosition(FALSE, boards[0]);
\r
10971 case PromotePiece:
\r
10972 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10973 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10974 selection = (ChessSquare) (PROMOTED piece);
\r
10975 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10976 else selection = (ChessSquare)((int)piece - 1);
\r
10977 goto defaultlabel;
\r
10979 case DemotePiece:
\r
10980 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10981 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10982 selection = (ChessSquare) (DEMOTED piece);
\r
10983 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10984 else selection = (ChessSquare)((int)piece + 1);
\r
10985 goto defaultlabel;
\r
10989 if(gameInfo.variant == VariantShatranj ||
\r
10990 gameInfo.variant == VariantXiangqi ||
\r
10991 gameInfo.variant == VariantCourier )
\r
10992 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10993 goto defaultlabel;
\r
10997 if(gameInfo.variant == VariantXiangqi)
\r
10998 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
10999 if(gameInfo.variant == VariantKnightmate)
\r
11000 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11003 if (gameMode == IcsExamining) {
\r
11004 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11005 PieceToChar(selection), AAA + x, ONE + y);
\r
11008 boards[0][y][x] = selection;
\r
11009 DrawPosition(FALSE, boards[0]);
\r
11017 DropMenuEvent(selection, x, y)
\r
11018 ChessSquare selection;
\r
11021 ChessMove moveType;
\r
11023 switch (gameMode) {
\r
11024 case IcsPlayingWhite:
\r
11025 case MachinePlaysBlack:
\r
11026 if (!WhiteOnMove(currentMove)) {
\r
11027 DisplayMoveError(_("It is Black's turn"));
\r
11030 moveType = WhiteDrop;
\r
11032 case IcsPlayingBlack:
\r
11033 case MachinePlaysWhite:
\r
11034 if (WhiteOnMove(currentMove)) {
\r
11035 DisplayMoveError(_("It is White's turn"));
\r
11038 moveType = BlackDrop;
\r
11041 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11047 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11048 selection = (ChessSquare) ((int) selection
\r
11049 + (int) BlackPawn - (int) WhitePawn);
\r
11051 if (boards[currentMove][y][x] != EmptySquare) {
\r
11052 DisplayMoveError(_("That square is occupied"));
\r
11056 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11062 /* Accept a pending offer of any kind from opponent */
\r
11064 if (appData.icsActive) {
\r
11065 SendToICS(ics_prefix);
\r
11066 SendToICS("accept\n");
\r
11067 } else if (cmailMsgLoaded) {
\r
11068 if (currentMove == cmailOldMove &&
\r
11069 commentList[cmailOldMove] != NULL &&
\r
11070 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11071 "Black offers a draw" : "White offers a draw")) {
\r
11073 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11074 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11076 DisplayError(_("There is no pending offer on this move"), 0);
\r
11077 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11080 /* Not used for offers from chess program */
\r
11087 /* Decline a pending offer of any kind from opponent */
\r
11089 if (appData.icsActive) {
\r
11090 SendToICS(ics_prefix);
\r
11091 SendToICS("decline\n");
\r
11092 } else if (cmailMsgLoaded) {
\r
11093 if (currentMove == cmailOldMove &&
\r
11094 commentList[cmailOldMove] != NULL &&
\r
11095 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11096 "Black offers a draw" : "White offers a draw")) {
\r
11098 AppendComment(cmailOldMove, "Draw declined");
\r
11099 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11100 #endif /*NOTDEF*/
\r
11102 DisplayError(_("There is no pending offer on this move"), 0);
\r
11105 /* Not used for offers from chess program */
\r
11112 /* Issue ICS rematch command */
\r
11113 if (appData.icsActive) {
\r
11114 SendToICS(ics_prefix);
\r
11115 SendToICS("rematch\n");
\r
11122 /* Call your opponent's flag (claim a win on time) */
\r
11123 if (appData.icsActive) {
\r
11124 SendToICS(ics_prefix);
\r
11125 SendToICS("flag\n");
\r
11127 switch (gameMode) {
\r
11130 case MachinePlaysWhite:
\r
11133 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11136 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11138 DisplayError(_("Your opponent is not out of time"), 0);
\r
11141 case MachinePlaysBlack:
\r
11144 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11147 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11149 DisplayError(_("Your opponent is not out of time"), 0);
\r
11159 /* Offer draw or accept pending draw offer from opponent */
\r
11161 if (appData.icsActive) {
\r
11162 /* Note: tournament rules require draw offers to be
\r
11163 made after you make your move but before you punch
\r
11164 your clock. Currently ICS doesn't let you do that;
\r
11165 instead, you immediately punch your clock after making
\r
11166 a move, but you can offer a draw at any time. */
\r
11168 SendToICS(ics_prefix);
\r
11169 SendToICS("draw\n");
\r
11170 } else if (cmailMsgLoaded) {
\r
11171 if (currentMove == cmailOldMove &&
\r
11172 commentList[cmailOldMove] != NULL &&
\r
11173 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11174 "Black offers a draw" : "White offers a draw")) {
\r
11175 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11176 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11177 } else if (currentMove == cmailOldMove + 1) {
\r
11178 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11179 "White offers a draw" : "Black offers a draw";
\r
11180 AppendComment(currentMove, offer);
\r
11181 DisplayComment(currentMove - 1, offer);
\r
11182 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11184 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11185 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11187 } else if (first.offeredDraw) {
\r
11188 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11190 if (first.sendDrawOffers) {
\r
11191 SendToProgram("draw\n", &first);
\r
11192 userOfferedDraw = TRUE;
\r
11200 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11202 if (appData.icsActive) {
\r
11203 SendToICS(ics_prefix);
\r
11204 SendToICS("adjourn\n");
\r
11206 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11214 /* Offer Abort or accept pending Abort offer from opponent */
\r
11216 if (appData.icsActive) {
\r
11217 SendToICS(ics_prefix);
\r
11218 SendToICS("abort\n");
\r
11220 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11227 /* Resign. You can do this even if it's not your turn. */
\r
11229 if (appData.icsActive) {
\r
11230 SendToICS(ics_prefix);
\r
11231 SendToICS("resign\n");
\r
11233 switch (gameMode) {
\r
11234 case MachinePlaysWhite:
\r
11235 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11237 case MachinePlaysBlack:
\r
11238 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11241 if (cmailMsgLoaded) {
\r
11243 if (WhiteOnMove(cmailOldMove)) {
\r
11244 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11246 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11248 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11259 StopObservingEvent()
\r
11261 /* Stop observing current games */
\r
11262 SendToICS(ics_prefix);
\r
11263 SendToICS("unobserve\n");
\r
11267 StopExaminingEvent()
\r
11269 /* Stop observing current game */
\r
11270 SendToICS(ics_prefix);
\r
11271 SendToICS("unexamine\n");
\r
11275 ForwardInner(target)
\r
11280 if (appData.debugMode)
\r
11281 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11282 target, currentMove, forwardMostMove);
\r
11284 if (gameMode == EditPosition)
\r
11287 if (gameMode == PlayFromGameFile && !pausing)
\r
11290 if (gameMode == IcsExamining && pausing)
\r
11291 limit = pauseExamForwardMostMove;
\r
11293 limit = forwardMostMove;
\r
11295 if (target > limit) target = limit;
\r
11297 if (target > 0 && moveList[target - 1][0]) {
\r
11298 int fromX, fromY, toX, toY;
\r
11299 toX = moveList[target - 1][2] - AAA;
\r
11300 toY = moveList[target - 1][3] - ONE;
\r
11301 if (moveList[target - 1][1] == '@') {
\r
11302 if (appData.highlightLastMove) {
\r
11303 SetHighlights(-1, -1, toX, toY);
\r
11306 fromX = moveList[target - 1][0] - AAA;
\r
11307 fromY = moveList[target - 1][1] - ONE;
\r
11308 if (target == currentMove + 1) {
\r
11309 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11311 if (appData.highlightLastMove) {
\r
11312 SetHighlights(fromX, fromY, toX, toY);
\r
11316 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11317 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11318 gameMode == AnalyzeFile) {
\r
11319 while (currentMove < target) {
\r
11320 SendMoveToProgram(currentMove++, &first);
\r
11323 currentMove = target;
\r
11326 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11327 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11328 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11330 DisplayBothClocks();
\r
11331 DisplayMove(currentMove - 1);
\r
11332 DrawPosition(FALSE, boards[currentMove]);
\r
11333 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11334 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11335 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11343 if (gameMode == IcsExamining && !pausing) {
\r
11344 SendToICS(ics_prefix);
\r
11345 SendToICS("forward\n");
\r
11347 ForwardInner(currentMove + 1);
\r
11354 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11355 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11356 * the remaining moves to the engine. Otherwise we get analysis output
\r
11357 * after each move.
\r
11359 if (first.analysisSupport) {
\r
11360 SendToProgram("exit\nforce\n", &first);
\r
11361 first.analyzing = FALSE;
\r
11365 if (gameMode == IcsExamining && !pausing) {
\r
11366 SendToICS(ics_prefix);
\r
11367 SendToICS("forward 999999\n");
\r
11369 ForwardInner(forwardMostMove);
\r
11372 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11373 /* we have fed all the moves, so reactivate analysis mode */
\r
11374 SendToProgram("analyze\n", &first);
\r
11375 first.analyzing = TRUE;
\r
11376 /*first.maybeThinking = TRUE;*/
\r
11377 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11382 BackwardInner(target)
\r
11385 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11387 if (appData.debugMode)
\r
11388 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11389 target, currentMove, forwardMostMove);
\r
11391 if (gameMode == EditPosition) return;
\r
11392 if (currentMove <= backwardMostMove) {
\r
11393 ClearHighlights();
\r
11394 DrawPosition(full_redraw, boards[currentMove]);
\r
11397 if (gameMode == PlayFromGameFile && !pausing)
\r
11400 if (moveList[target][0]) {
\r
11401 int fromX, fromY, toX, toY;
\r
11402 toX = moveList[target][2] - AAA;
\r
11403 toY = moveList[target][3] - ONE;
\r
11404 if (moveList[target][1] == '@') {
\r
11405 if (appData.highlightLastMove) {
\r
11406 SetHighlights(-1, -1, toX, toY);
\r
11409 fromX = moveList[target][0] - AAA;
\r
11410 fromY = moveList[target][1] - ONE;
\r
11411 if (target == currentMove - 1) {
\r
11412 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11414 if (appData.highlightLastMove) {
\r
11415 SetHighlights(fromX, fromY, toX, toY);
\r
11419 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11420 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11421 while (currentMove > target) {
\r
11422 SendToProgram("undo\n", &first);
\r
11426 currentMove = target;
\r
11429 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11430 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11431 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11433 DisplayBothClocks();
\r
11434 DisplayMove(currentMove - 1);
\r
11435 DrawPosition(full_redraw, boards[currentMove]);
\r
11436 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11437 // [HGM] PV info: routine tests if comment empty
\r
11438 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11444 if (gameMode == IcsExamining && !pausing) {
\r
11445 SendToICS(ics_prefix);
\r
11446 SendToICS("backward\n");
\r
11448 BackwardInner(currentMove - 1);
\r
11455 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11456 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11457 * all the moves. Otherwise we get analysis output after each undo.
\r
11459 if (first.analysisSupport) {
\r
11460 SendToProgram("exit\nforce\n", &first);
\r
11461 first.analyzing = FALSE;
\r
11465 if (gameMode == IcsExamining && !pausing) {
\r
11466 SendToICS(ics_prefix);
\r
11467 SendToICS("backward 999999\n");
\r
11469 BackwardInner(backwardMostMove);
\r
11472 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11473 /* we have fed all the moves, so reactivate analysis mode */
\r
11474 SendToProgram("analyze\n", &first);
\r
11475 first.analyzing = TRUE;
\r
11476 /*first.maybeThinking = TRUE;*/
\r
11477 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11482 ToNrEvent(int to)
\r
11484 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11485 if (to >= forwardMostMove) to = forwardMostMove;
\r
11486 if (to <= backwardMostMove) to = backwardMostMove;
\r
11487 if (to < currentMove) {
\r
11488 BackwardInner(to);
\r
11490 ForwardInner(to);
\r
11497 if (gameMode != IcsExamining) {
\r
11498 DisplayError(_("You are not examining a game"), 0);
\r
11502 DisplayError(_("You can't revert while pausing"), 0);
\r
11505 SendToICS(ics_prefix);
\r
11506 SendToICS("revert\n");
\r
11510 RetractMoveEvent()
\r
11512 switch (gameMode) {
\r
11513 case MachinePlaysWhite:
\r
11514 case MachinePlaysBlack:
\r
11515 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11516 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11519 if (forwardMostMove < 2) return;
\r
11520 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11521 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11522 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11523 DisplayBothClocks();
\r
11524 DisplayMove(currentMove - 1);
\r
11525 ClearHighlights();/*!! could figure this out*/
\r
11526 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11527 SendToProgram("remove\n", &first);
\r
11528 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11531 case BeginningOfGame:
\r
11535 case IcsPlayingWhite:
\r
11536 case IcsPlayingBlack:
\r
11537 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11538 SendToICS(ics_prefix);
\r
11539 SendToICS("takeback 2\n");
\r
11541 SendToICS(ics_prefix);
\r
11542 SendToICS("takeback 1\n");
\r
11551 ChessProgramState *cps;
\r
11553 switch (gameMode) {
\r
11554 case MachinePlaysWhite:
\r
11555 if (!WhiteOnMove(forwardMostMove)) {
\r
11556 DisplayError(_("It is your turn"), 0);
\r
11561 case MachinePlaysBlack:
\r
11562 if (WhiteOnMove(forwardMostMove)) {
\r
11563 DisplayError(_("It is your turn"), 0);
\r
11568 case TwoMachinesPlay:
\r
11569 if (WhiteOnMove(forwardMostMove) ==
\r
11570 (first.twoMachinesColor[0] == 'w')) {
\r
11576 case BeginningOfGame:
\r
11580 SendToProgram("?\n", cps);
\r
11584 TruncateGameEvent()
\r
11587 if (gameMode != EditGame) return;
\r
11594 if (forwardMostMove > currentMove) {
\r
11595 if (gameInfo.resultDetails != NULL) {
\r
11596 free(gameInfo.resultDetails);
\r
11597 gameInfo.resultDetails = NULL;
\r
11598 gameInfo.result = GameUnfinished;
\r
11600 forwardMostMove = currentMove;
\r
11601 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11609 if (appData.noChessProgram) return;
\r
11610 switch (gameMode) {
\r
11611 case MachinePlaysWhite:
\r
11612 if (WhiteOnMove(forwardMostMove)) {
\r
11613 DisplayError(_("Wait until your turn"), 0);
\r
11617 case BeginningOfGame:
\r
11618 case MachinePlaysBlack:
\r
11619 if (!WhiteOnMove(forwardMostMove)) {
\r
11620 DisplayError(_("Wait until your turn"), 0);
\r
11625 DisplayError(_("No hint available"), 0);
\r
11628 SendToProgram("hint\n", &first);
\r
11629 hintRequested = TRUE;
\r
11635 if (appData.noChessProgram) return;
\r
11636 switch (gameMode) {
\r
11637 case MachinePlaysWhite:
\r
11638 if (WhiteOnMove(forwardMostMove)) {
\r
11639 DisplayError(_("Wait until your turn"), 0);
\r
11643 case BeginningOfGame:
\r
11644 case MachinePlaysBlack:
\r
11645 if (!WhiteOnMove(forwardMostMove)) {
\r
11646 DisplayError(_("Wait until your turn"), 0);
\r
11650 case EditPosition:
\r
11651 EditPositionDone();
\r
11653 case TwoMachinesPlay:
\r
11658 SendToProgram("bk\n", &first);
\r
11659 bookOutput[0] = NULLCHAR;
\r
11660 bookRequested = TRUE;
\r
11666 char *tags = PGNTags(&gameInfo);
\r
11667 TagsPopUp(tags, CmailMsg());
\r
11671 /* end button procedures */
\r
11674 PrintPosition(fp, move)
\r
11680 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11681 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11682 char c = PieceToChar(boards[move][i][j]);
\r
11683 fputc(c == 'x' ? '.' : c, fp);
\r
11684 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11687 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11688 fprintf(fp, "white to play\n");
\r
11690 fprintf(fp, "black to play\n");
\r
11694 PrintOpponents(fp)
\r
11697 if (gameInfo.white != NULL) {
\r
11698 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11700 fprintf(fp, "\n");
\r
11704 /* Find last component of program's own name, using some heuristics */
\r
11706 TidyProgramName(prog, host, buf)
\r
11707 char *prog, *host, buf[MSG_SIZ];
\r
11710 int local = (strcmp(host, "localhost") == 0);
\r
11711 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11713 while (*p == ' ') p++;
\r
11716 if (*prog == '"' || *prog == '\'') {
\r
11717 q = strchr(prog + 1, *prog);
\r
11719 q = strchr(prog, ' ');
\r
11721 if (q == NULL) q = prog + strlen(prog);
\r
11723 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11725 if(p == prog && *p == '"') p++;
\r
11726 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11727 memcpy(buf, p, q - p);
\r
11728 buf[q - p] = NULLCHAR;
\r
11730 strcat(buf, "@");
\r
11731 strcat(buf, host);
\r
11736 TimeControlTagValue()
\r
11738 char buf[MSG_SIZ];
\r
11739 if (!appData.clockMode) {
\r
11740 strcpy(buf, "-");
\r
11741 } else if (movesPerSession > 0) {
\r
11742 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11743 } else if (timeIncrement == 0) {
\r
11744 sprintf(buf, "%ld", timeControl/1000);
\r
11746 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11748 return StrSave(buf);
\r
11754 /* This routine is used only for certain modes */
\r
11755 VariantClass v = gameInfo.variant;
\r
11756 ClearGameInfo(&gameInfo);
\r
11757 gameInfo.variant = v;
\r
11759 switch (gameMode) {
\r
11760 case MachinePlaysWhite:
\r
11761 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11762 gameInfo.site = StrSave(HostName());
\r
11763 gameInfo.date = PGNDate();
\r
11764 gameInfo.round = StrSave("-");
\r
11765 gameInfo.white = StrSave(first.tidy);
\r
11766 gameInfo.black = StrSave(UserName());
\r
11767 gameInfo.timeControl = TimeControlTagValue();
\r
11770 case MachinePlaysBlack:
\r
11771 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11772 gameInfo.site = StrSave(HostName());
\r
11773 gameInfo.date = PGNDate();
\r
11774 gameInfo.round = StrSave("-");
\r
11775 gameInfo.white = StrSave(UserName());
\r
11776 gameInfo.black = StrSave(first.tidy);
\r
11777 gameInfo.timeControl = TimeControlTagValue();
\r
11780 case TwoMachinesPlay:
\r
11781 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11782 gameInfo.site = StrSave(HostName());
\r
11783 gameInfo.date = PGNDate();
\r
11784 if (matchGame > 0) {
\r
11785 char buf[MSG_SIZ];
\r
11786 sprintf(buf, "%d", matchGame);
\r
11787 gameInfo.round = StrSave(buf);
\r
11789 gameInfo.round = StrSave("-");
\r
11791 if (first.twoMachinesColor[0] == 'w') {
\r
11792 gameInfo.white = StrSave(first.tidy);
\r
11793 gameInfo.black = StrSave(second.tidy);
\r
11795 gameInfo.white = StrSave(second.tidy);
\r
11796 gameInfo.black = StrSave(first.tidy);
\r
11798 gameInfo.timeControl = TimeControlTagValue();
\r
11802 gameInfo.event = StrSave("Edited game");
\r
11803 gameInfo.site = StrSave(HostName());
\r
11804 gameInfo.date = PGNDate();
\r
11805 gameInfo.round = StrSave("-");
\r
11806 gameInfo.white = StrSave("-");
\r
11807 gameInfo.black = StrSave("-");
\r
11810 case EditPosition:
\r
11811 gameInfo.event = StrSave("Edited position");
\r
11812 gameInfo.site = StrSave(HostName());
\r
11813 gameInfo.date = PGNDate();
\r
11814 gameInfo.round = StrSave("-");
\r
11815 gameInfo.white = StrSave("-");
\r
11816 gameInfo.black = StrSave("-");
\r
11819 case IcsPlayingWhite:
\r
11820 case IcsPlayingBlack:
\r
11821 case IcsObserving:
\r
11822 case IcsExamining:
\r
11825 case PlayFromGameFile:
\r
11826 gameInfo.event = StrSave("Game from non-PGN file");
\r
11827 gameInfo.site = StrSave(HostName());
\r
11828 gameInfo.date = PGNDate();
\r
11829 gameInfo.round = StrSave("-");
\r
11830 gameInfo.white = StrSave("?");
\r
11831 gameInfo.black = StrSave("?");
\r
11840 ReplaceComment(index, text)
\r
11846 while (*text == '\n') text++;
\r
11847 len = strlen(text);
\r
11848 while (len > 0 && text[len - 1] == '\n') len--;
\r
11850 if (commentList[index] != NULL)
\r
11851 free(commentList[index]);
\r
11854 commentList[index] = NULL;
\r
11857 commentList[index] = (char *) malloc(len + 2);
\r
11858 strncpy(commentList[index], text, len);
\r
11859 commentList[index][len] = '\n';
\r
11860 commentList[index][len + 1] = NULLCHAR;
\r
11873 if (ch == '\r') continue;
\r
11875 } while (ch != '\0');
\r
11879 AppendComment(index, text)
\r
11886 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11889 while (*text == '\n') text++;
\r
11890 len = strlen(text);
\r
11891 while (len > 0 && text[len - 1] == '\n') len--;
\r
11893 if (len == 0) return;
\r
11895 if (commentList[index] != NULL) {
\r
11896 old = commentList[index];
\r
11897 oldlen = strlen(old);
\r
11898 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11899 strcpy(commentList[index], old);
\r
11901 strncpy(&commentList[index][oldlen], text, len);
\r
11902 commentList[index][oldlen + len] = '\n';
\r
11903 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11905 commentList[index] = (char *) malloc(len + 2);
\r
11906 strncpy(commentList[index], text, len);
\r
11907 commentList[index][len] = '\n';
\r
11908 commentList[index][len + 1] = NULLCHAR;
\r
11912 static char * FindStr( char * text, char * sub_text )
\r
11914 char * result = strstr( text, sub_text );
\r
11916 if( result != NULL ) {
\r
11917 result += strlen( sub_text );
\r
11923 /* [AS] Try to extract PV info from PGN comment */
\r
11924 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11925 char *GetInfoFromComment( int index, char * text )
\r
11927 char * sep = text;
\r
11929 if( text != NULL && index > 0 ) {
\r
11932 int time = -1, sec = 0, deci;
\r
11933 char * s_eval = FindStr( text, "[%eval " );
\r
11934 char * s_emt = FindStr( text, "[%emt " );
\r
11936 if( s_eval != NULL || s_emt != NULL ) {
\r
11940 if( s_eval != NULL ) {
\r
11941 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11945 if( delim != ']' ) {
\r
11950 if( s_emt != NULL ) {
\r
11954 /* We expect something like: [+|-]nnn.nn/dd */
\r
11955 int score_lo = 0;
\r
11957 sep = strchr( text, '/' );
\r
11958 if( sep == NULL || sep < (text+4) ) {
\r
11962 time = -1; sec = -1; deci = -1;
\r
11963 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11964 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11965 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11966 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11970 if( score_lo < 0 || score_lo >= 100 ) {
\r
11974 if(sec >= 0) time = 600*time + 10*sec; else
\r
11975 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
11977 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11979 /* [HGM] PV time: now locate end of PV info */
\r
11980 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11982 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11984 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11986 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
11987 while(*sep == ' ') sep++;
\r
11990 if( depth <= 0 ) {
\r
11998 pvInfoList[index-1].depth = depth;
\r
11999 pvInfoList[index-1].score = score;
\r
12000 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12006 SendToProgram(message, cps)
\r
12008 ChessProgramState *cps;
\r
12010 int count, outCount, error;
\r
12011 char buf[MSG_SIZ];
\r
12013 if (cps->pr == NULL) return;
\r
12016 if (appData.debugMode) {
\r
12018 GetTimeMark(&now);
\r
12019 fprintf(debugFP, "%ld >%-6s: %s",
\r
12020 SubtractTimeMarks(&now, &programStartTime),
\r
12021 cps->which, message);
\r
12024 count = strlen(message);
\r
12025 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12026 if (outCount < count && !exiting
\r
12027 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12028 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12029 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12030 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12031 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12032 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12034 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12036 gameInfo.resultDetails = buf;
\r
12038 DisplayFatalError(buf, error, 1);
\r
12043 ReceiveFromProgram(isr, closure, message, count, error)
\r
12044 InputSourceRef isr;
\r
12045 VOIDSTAR closure;
\r
12051 char buf[MSG_SIZ];
\r
12052 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12054 if (isr != cps->isr) return; /* Killed intentionally */
\r
12055 if (count <= 0) {
\r
12056 if (count == 0) {
\r
12058 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12059 cps->which, cps->program);
\r
12060 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12061 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12062 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12063 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12065 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12067 gameInfo.resultDetails = buf;
\r
12069 RemoveInputSource(cps->isr);
\r
12070 DisplayFatalError(buf, 0, 1);
\r
12073 _("Error reading from %s chess program (%s)"),
\r
12074 cps->which, cps->program);
\r
12075 RemoveInputSource(cps->isr);
\r
12077 /* [AS] Program is misbehaving badly... kill it */
\r
12078 if( count == -2 ) {
\r
12079 DestroyChildProcess( cps->pr, 9 );
\r
12080 cps->pr = NoProc;
\r
12083 DisplayFatalError(buf, error, 1);
\r
12088 if ((end_str = strchr(message, '\r')) != NULL)
\r
12089 *end_str = NULLCHAR;
\r
12090 if ((end_str = strchr(message, '\n')) != NULL)
\r
12091 *end_str = NULLCHAR;
\r
12093 if (appData.debugMode) {
\r
12094 TimeMark now; int print = 1;
\r
12095 char *quote = ""; char c; int i;
\r
12097 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12098 char start = message[0];
\r
12099 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12100 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12101 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12102 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12103 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12104 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12105 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12106 { quote = "# "; print = (appData.engineComments == 2); }
\r
12107 message[0] = start; // restore original message
\r
12110 GetTimeMark(&now);
\r
12111 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12112 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12118 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12119 if (appData.icsEngineAnalyze) {
\r
12120 if (strstr(message, "whisper") != NULL ||
\r
12121 strstr(message, "kibitz") != NULL ||
\r
12122 strstr(message, "tellics") != NULL) return;
\r
12125 HandleMachineMove(message, cps);
\r
12130 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12131 ChessProgramState *cps;
\r
12132 int mps, inc, sd, st;
\r
12135 char buf[MSG_SIZ];
\r
12138 if( timeControl_2 > 0 ) {
\r
12139 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12140 tc = timeControl_2;
\r
12143 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12144 inc /= cps->timeOdds;
\r
12145 st /= cps->timeOdds;
\r
12147 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12150 /* Set exact time per move, normally using st command */
\r
12151 if (cps->stKludge) {
\r
12152 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12153 seconds = st % 60;
\r
12154 if (seconds == 0) {
\r
12155 sprintf(buf, "level 1 %d\n", st/60);
\r
12157 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12160 sprintf(buf, "st %d\n", st);
\r
12163 /* Set conventional or incremental time control, using level command */
\r
12164 if (seconds == 0) {
\r
12165 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12166 Fixed in later versions, but still avoid :seconds
\r
12167 when seconds is 0. */
\r
12168 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12170 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12171 seconds, inc/1000);
\r
12174 SendToProgram(buf, cps);
\r
12176 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12177 /* Orthogonally, limit search to given depth */
\r
12179 if (cps->sdKludge) {
\r
12180 sprintf(buf, "depth\n%d\n", sd);
\r
12182 sprintf(buf, "sd %d\n", sd);
\r
12184 SendToProgram(buf, cps);
\r
12187 if(cps->nps > 0) { /* [HGM] nps */
\r
12188 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12190 sprintf(buf, "nps %d\n", cps->nps);
\r
12191 SendToProgram(buf, cps);
\r
12196 ChessProgramState *WhitePlayer()
\r
12197 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12199 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12200 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12206 SendTimeRemaining(cps, machineWhite)
\r
12207 ChessProgramState *cps;
\r
12208 int /*boolean*/ machineWhite;
\r
12210 char message[MSG_SIZ];
\r
12211 long time, otime;
\r
12213 /* Note: this routine must be called when the clocks are stopped
\r
12214 or when they have *just* been set or switched; otherwise
\r
12215 it will be off by the time since the current tick started.
\r
12217 if (machineWhite) {
\r
12218 time = whiteTimeRemaining / 10;
\r
12219 otime = blackTimeRemaining / 10;
\r
12221 time = blackTimeRemaining / 10;
\r
12222 otime = whiteTimeRemaining / 10;
\r
12224 /* [HGM] translate opponent's time by time-odds factor */
\r
12225 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12226 if (appData.debugMode) {
\r
12227 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12230 if (time <= 0) time = 1;
\r
12231 if (otime <= 0) otime = 1;
\r
12233 sprintf(message, "time %ld\n", time);
\r
12234 SendToProgram(message, cps);
\r
12236 sprintf(message, "otim %ld\n", otime);
\r
12237 SendToProgram(message, cps);
\r
12241 BoolFeature(p, name, loc, cps)
\r
12245 ChessProgramState *cps;
\r
12247 char buf[MSG_SIZ];
\r
12248 int len = strlen(name);
\r
12250 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12252 sscanf(*p, "%d", &val);
\r
12253 *loc = (val != 0);
\r
12254 while (**p && **p != ' ') (*p)++;
\r
12255 sprintf(buf, "accepted %s\n", name);
\r
12256 SendToProgram(buf, cps);
\r
12263 IntFeature(p, name, loc, cps)
\r
12267 ChessProgramState *cps;
\r
12269 char buf[MSG_SIZ];
\r
12270 int len = strlen(name);
\r
12271 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12273 sscanf(*p, "%d", loc);
\r
12274 while (**p && **p != ' ') (*p)++;
\r
12275 sprintf(buf, "accepted %s\n", name);
\r
12276 SendToProgram(buf, cps);
\r
12283 StringFeature(p, name, loc, cps)
\r
12287 ChessProgramState *cps;
\r
12289 char buf[MSG_SIZ];
\r
12290 int len = strlen(name);
\r
12291 if (strncmp((*p), name, len) == 0
\r
12292 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12294 sscanf(*p, "%[^\"]", loc);
\r
12295 while (**p && **p != '\"') (*p)++;
\r
12296 if (**p == '\"') (*p)++;
\r
12297 sprintf(buf, "accepted %s\n", name);
\r
12298 SendToProgram(buf, cps);
\r
12305 ParseOption(Option *opt, ChessProgramState *cps)
\r
12306 // [HGM] options: process the string that defines an engine option, and determine
\r
12307 // name, type, default value, and allowed value range
\r
12309 char *p, *q, buf[MSG_SIZ];
\r
12310 int n, min = (-1)<<31, max = 1<<31, def;
\r
12312 if(p = strstr(opt->name, " -spin ")) {
\r
12313 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12314 if(max < min) max = min; // enforce consistency
\r
12315 if(def < min) def = min;
\r
12316 if(def > max) def = max;
\r
12317 opt->value = def;
\r
12320 opt->type = Spin;
\r
12321 } else if(p = strstr(opt->name, " -string ")) {
\r
12322 opt->textValue = p+9;
\r
12323 opt->type = TextBox;
\r
12324 } else if(p = strstr(opt->name, " -check ")) {
\r
12325 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12326 opt->value = (def != 0);
\r
12327 opt->type = CheckBox;
\r
12328 } else if(p = strstr(opt->name, " -combo ")) {
\r
12329 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12330 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12331 opt->value = n = 0;
\r
12332 while(q = StrStr(q, " /// ")) {
\r
12333 n++; *q = 0; // count choices, and null-terminate each of them
\r
12335 if(*q == '*') { // remember default, which is marked with * prefix
\r
12339 cps->comboList[cps->comboCnt++] = q;
\r
12341 cps->comboList[cps->comboCnt++] = NULL;
\r
12342 opt->max = n + 1;
\r
12343 opt->type = ComboBox;
\r
12344 } else if(p = strstr(opt->name, " -button")) {
\r
12345 opt->type = Button;
\r
12346 } else if(p = strstr(opt->name, " -save")) {
\r
12347 opt->type = SaveButton;
\r
12348 } else return FALSE;
\r
12349 *p = 0; // terminate option name
\r
12350 // now look if the command-line options define a setting for this engine option.
\r
12351 if(cps->optionSettings && cps->optionSettings[0])
\r
12352 p = strstr(cps->optionSettings, opt->name); else p = NULL;
\r
12353 if(p && (p == cps->optionSettings || p[-1] == ',')) {
\r
12354 sprintf(buf, "option %s", p);
\r
12355 if(p = strstr(buf, ",")) *p = 0;
\r
12356 strcat(buf, "\n");
\r
12357 SendToProgram(buf, cps);
\r
12363 FeatureDone(cps, val)
\r
12364 ChessProgramState* cps;
\r
12367 DelayedEventCallback cb = GetDelayedEvent();
\r
12368 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12369 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12370 CancelDelayedEvent();
\r
12371 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12373 cps->initDone = val;
\r
12376 /* Parse feature command from engine */
\r
12378 ParseFeatures(args, cps)
\r
12380 ChessProgramState *cps;
\r
12385 char buf[MSG_SIZ];
\r
12388 while (*p == ' ') p++;
\r
12389 if (*p == NULLCHAR) return;
\r
12391 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12392 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12393 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12394 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12395 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12396 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12397 /* Engine can disable reuse, but can't enable it if user said no */
\r
12398 if (!val) cps->reuse = FALSE;
\r
12401 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12402 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12403 if (gameMode == TwoMachinesPlay) {
\r
12404 DisplayTwoMachinesTitle();
\r
12406 DisplayTitle("");
\r
12410 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12411 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12412 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12413 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12414 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12415 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12416 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12417 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12418 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12419 if (IntFeature(&p, "done", &val, cps)) {
\r
12420 FeatureDone(cps, val);
\r
12423 /* Added by Tord: */
\r
12424 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12425 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12426 /* End of additions by Tord */
\r
12428 /* [HGM] added features: */
\r
12429 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12430 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12431 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12432 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12433 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12434 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12435 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12436 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12437 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12438 cps->nrOptions--;
\r
12439 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12440 DisplayError(buf, 0);
\r
12444 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12445 /* End of additions by HGM */
\r
12447 /* unknown feature: complain and skip */
\r
12449 while (*q && *q != '=') q++;
\r
12450 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12451 SendToProgram(buf, cps);
\r
12455 if (*p == '\"') {
\r
12457 while (*p && *p != '\"') p++;
\r
12458 if (*p == '\"') p++;
\r
12460 while (*p && *p != ' ') p++;
\r
12468 PeriodicUpdatesEvent(newState)
\r
12471 if (newState == appData.periodicUpdates)
\r
12474 appData.periodicUpdates=newState;
\r
12476 /* Display type changes, so update it now */
\r
12477 DisplayAnalysis();
\r
12479 /* Get the ball rolling again... */
\r
12481 AnalysisPeriodicEvent(1);
\r
12482 StartAnalysisClock();
\r
12487 PonderNextMoveEvent(newState)
\r
12490 if (newState == appData.ponderNextMove) return;
\r
12491 if (gameMode == EditPosition) EditPositionDone();
\r
12493 SendToProgram("hard\n", &first);
\r
12494 if (gameMode == TwoMachinesPlay) {
\r
12495 SendToProgram("hard\n", &second);
\r
12498 SendToProgram("easy\n", &first);
\r
12499 thinkOutput[0] = NULLCHAR;
\r
12500 if (gameMode == TwoMachinesPlay) {
\r
12501 SendToProgram("easy\n", &second);
\r
12504 appData.ponderNextMove = newState;
\r
12508 NewSettingEvent(option, command, value)
\r
12510 int option, value;
\r
12512 char buf[MSG_SIZ];
\r
12514 if (gameMode == EditPosition) EditPositionDone();
\r
12515 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12516 SendToProgram(buf, &first);
\r
12517 if (gameMode == TwoMachinesPlay) {
\r
12518 SendToProgram(buf, &second);
\r
12523 ShowThinkingEvent()
\r
12524 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12526 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12527 int newState = appData.showThinking
\r
12528 // [HGM] thinking: other features now need thinking output as well
\r
12529 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12531 if (oldState == newState) return;
\r
12532 oldState = newState;
\r
12533 if (gameMode == EditPosition) EditPositionDone();
\r
12535 SendToProgram("post\n", &first);
\r
12536 if (gameMode == TwoMachinesPlay) {
\r
12537 SendToProgram("post\n", &second);
\r
12540 SendToProgram("nopost\n", &first);
\r
12541 thinkOutput[0] = NULLCHAR;
\r
12542 if (gameMode == TwoMachinesPlay) {
\r
12543 SendToProgram("nopost\n", &second);
\r
12546 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12550 AskQuestionEvent(title, question, replyPrefix, which)
\r
12551 char *title; char *question; char *replyPrefix; char *which;
\r
12553 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12554 if (pr == NoProc) return;
\r
12555 AskQuestion(title, question, replyPrefix, pr);
\r
12559 DisplayMove(moveNumber)
\r
12562 char message[MSG_SIZ];
\r
12563 char res[MSG_SIZ];
\r
12564 char cpThinkOutput[MSG_SIZ];
\r
12566 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12568 if (moveNumber == forwardMostMove - 1 ||
\r
12569 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12571 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12573 if (strchr(cpThinkOutput, '\n')) {
\r
12574 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12577 *cpThinkOutput = NULLCHAR;
\r
12580 /* [AS] Hide thinking from human user */
\r
12581 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12582 *cpThinkOutput = NULLCHAR;
\r
12583 if( thinkOutput[0] != NULLCHAR ) {
\r
12586 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12587 cpThinkOutput[i] = '.';
\r
12589 cpThinkOutput[i] = NULLCHAR;
\r
12590 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12594 if (moveNumber == forwardMostMove - 1 &&
\r
12595 gameInfo.resultDetails != NULL) {
\r
12596 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12597 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12599 sprintf(res, " {%s} %s",
\r
12600 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12603 res[0] = NULLCHAR;
\r
12606 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12607 DisplayMessage(res, cpThinkOutput);
\r
12609 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12610 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12611 parseList[moveNumber], res);
\r
12612 DisplayMessage(message, cpThinkOutput);
\r
12617 DisplayAnalysisText(text)
\r
12620 char buf[MSG_SIZ];
\r
12622 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12623 || appData.icsEngineAnalyze) {
\r
12624 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12625 AnalysisPopUp(buf, text);
\r
12630 only_one_move(str)
\r
12633 while (*str && isspace(*str)) ++str;
\r
12634 while (*str && !isspace(*str)) ++str;
\r
12635 if (!*str) return 1;
\r
12636 while (*str && isspace(*str)) ++str;
\r
12637 if (!*str) return 1;
\r
12642 DisplayAnalysis()
\r
12644 char buf[MSG_SIZ];
\r
12645 char lst[MSG_SIZ / 2];
\r
12647 static char *xtra[] = { "", " (--)", " (++)" };
\r
12650 if (programStats.time == 0) {
\r
12651 programStats.time = 1;
\r
12654 if (programStats.got_only_move) {
\r
12655 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12657 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12659 nps = (u64ToDouble(programStats.nodes) /
\r
12660 ((double)programStats.time /100.0));
\r
12662 cs = programStats.time % 100;
\r
12663 s = programStats.time / 100;
\r
12664 h = (s / (60*60));
\r
12669 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12670 if (programStats.move_name[0] != NULLCHAR) {
\r
12671 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12672 programStats.depth,
\r
12673 programStats.nr_moves-programStats.moves_left,
\r
12674 programStats.nr_moves, programStats.move_name,
\r
12675 ((float)programStats.score)/100.0, lst,
\r
12676 only_one_move(lst)?
\r
12677 xtra[programStats.got_fail] : "",
\r
12678 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12680 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12681 programStats.depth,
\r
12682 programStats.nr_moves-programStats.moves_left,
\r
12683 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12685 only_one_move(lst)?
\r
12686 xtra[programStats.got_fail] : "",
\r
12687 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12690 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12691 programStats.depth,
\r
12692 ((float)programStats.score)/100.0,
\r
12694 only_one_move(lst)?
\r
12695 xtra[programStats.got_fail] : "",
\r
12696 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12699 DisplayAnalysisText(buf);
\r
12703 DisplayComment(moveNumber, text)
\r
12707 char title[MSG_SIZ];
\r
12708 char buf[8000]; // comment can be long!
\r
12709 int score, depth;
\r
12711 if( appData.autoDisplayComment ) {
\r
12712 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12713 strcpy(title, "Comment");
\r
12715 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12716 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12717 parseList[moveNumber]);
\r
12719 } else title[0] = 0;
\r
12721 // [HGM] PV info: display PV info together with (or as) comment
\r
12722 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12723 if(text == NULL) text = "";
\r
12724 score = pvInfoList[moveNumber].score;
\r
12725 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12726 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12727 CommentPopUp(title, buf);
\r
12729 if (text != NULL)
\r
12730 CommentPopUp(title, text);
\r
12733 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12734 * might be busy thinking or pondering. It can be omitted if your
\r
12735 * gnuchess is configured to stop thinking immediately on any user
\r
12736 * input. However, that gnuchess feature depends on the FIONREAD
\r
12737 * ioctl, which does not work properly on some flavors of Unix.
\r
12741 ChessProgramState *cps;
\r
12744 if (!cps->useSigint) return;
\r
12745 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12746 switch (gameMode) {
\r
12747 case MachinePlaysWhite:
\r
12748 case MachinePlaysBlack:
\r
12749 case TwoMachinesPlay:
\r
12750 case IcsPlayingWhite:
\r
12751 case IcsPlayingBlack:
\r
12752 case AnalyzeMode:
\r
12753 case AnalyzeFile:
\r
12754 /* Skip if we know it isn't thinking */
\r
12755 if (!cps->maybeThinking) return;
\r
12756 if (appData.debugMode)
\r
12757 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12758 InterruptChildProcess(cps->pr);
\r
12759 cps->maybeThinking = FALSE;
\r
12764 #endif /*ATTENTION*/
\r
12770 if (whiteTimeRemaining <= 0) {
\r
12771 if (!whiteFlag) {
\r
12772 whiteFlag = TRUE;
\r
12773 if (appData.icsActive) {
\r
12774 if (appData.autoCallFlag &&
\r
12775 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12776 SendToICS(ics_prefix);
\r
12777 SendToICS("flag\n");
\r
12781 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12783 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12784 if (appData.autoCallFlag) {
\r
12785 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12792 if (blackTimeRemaining <= 0) {
\r
12793 if (!blackFlag) {
\r
12794 blackFlag = TRUE;
\r
12795 if (appData.icsActive) {
\r
12796 if (appData.autoCallFlag &&
\r
12797 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12798 SendToICS(ics_prefix);
\r
12799 SendToICS("flag\n");
\r
12803 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12805 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12806 if (appData.autoCallFlag) {
\r
12807 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12818 CheckTimeControl()
\r
12820 if (!appData.clockMode || appData.icsActive ||
\r
12821 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12824 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12826 if ( !WhiteOnMove(forwardMostMove) )
\r
12827 /* White made time control */
\r
12828 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12829 /* [HGM] time odds: correct new time quota for time odds! */
\r
12830 / WhitePlayer()->timeOdds;
\r
12832 /* Black made time control */
\r
12833 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12834 / WhitePlayer()->other->timeOdds;
\r
12838 DisplayBothClocks()
\r
12840 int wom = gameMode == EditPosition ?
\r
12841 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12842 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12843 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12847 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12848 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12849 to use other calls if you don't. Clocks will be less accurate if
\r
12850 you have neither ftime nor gettimeofday.
\r
12853 /* VS 2008 requires the #include outside of the function */
\r
12854 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
\r
12855 #include <sys/timeb.h>
\r
12858 /* Get the current time as a TimeMark */
\r
12863 #if HAVE_GETTIMEOFDAY
\r
12865 struct timeval timeVal;
\r
12866 struct timezone timeZone;
\r
12868 gettimeofday(&timeVal, &timeZone);
\r
12869 tm->sec = (long) timeVal.tv_sec;
\r
12870 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12872 #else /*!HAVE_GETTIMEOFDAY*/
\r
12875 // include <sys/timeb.h> / moved to just above start of function
\r
12876 struct timeb timeB;
\r
12879 tm->sec = (long) timeB.time;
\r
12880 tm->ms = (int) timeB.millitm;
\r
12882 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12883 tm->sec = (long) time(NULL);
\r
12889 /* Return the difference in milliseconds between two
\r
12890 time marks. We assume the difference will fit in a long!
\r
12893 SubtractTimeMarks(tm2, tm1)
\r
12894 TimeMark *tm2, *tm1;
\r
12896 return 1000L*(tm2->sec - tm1->sec) +
\r
12897 (long) (tm2->ms - tm1->ms);
\r
12902 * Code to manage the game clocks.
\r
12904 * In tournament play, black starts the clock and then white makes a move.
\r
12905 * We give the human user a slight advantage if he is playing white---the
\r
12906 * clocks don't run until he makes his first move, so it takes zero time.
\r
12907 * Also, we don't account for network lag, so we could get out of sync
\r
12908 * with GNU Chess's clock -- but then, referees are always right.
\r
12911 static TimeMark tickStartTM;
\r
12912 static long intendedTickLength;
\r
12915 NextTickLength(timeRemaining)
\r
12916 long timeRemaining;
\r
12918 long nominalTickLength, nextTickLength;
\r
12920 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12921 nominalTickLength = 100L;
\r
12923 nominalTickLength = 1000L;
\r
12924 nextTickLength = timeRemaining % nominalTickLength;
\r
12925 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12927 return nextTickLength;
\r
12930 /* Adjust clock one minute up or down */
\r
12932 AdjustClock(Boolean which, int dir)
\r
12934 if(which) blackTimeRemaining += 60000*dir;
\r
12935 else whiteTimeRemaining += 60000*dir;
\r
12936 DisplayBothClocks();
\r
12939 /* Stop clocks and reset to a fresh time control */
\r
12943 (void) StopClockTimer();
\r
12944 if (appData.icsActive) {
\r
12945 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12946 } else { /* [HGM] correct new time quote for time odds */
\r
12947 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12948 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12950 if (whiteFlag || blackFlag) {
\r
12951 DisplayTitle("");
\r
12952 whiteFlag = blackFlag = FALSE;
\r
12954 DisplayBothClocks();
\r
12957 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12959 /* Decrement running clock by amount of time that has passed */
\r
12961 DecrementClocks()
\r
12963 long timeRemaining;
\r
12964 long lastTickLength, fudge;
\r
12967 if (!appData.clockMode) return;
\r
12968 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12970 GetTimeMark(&now);
\r
12972 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12974 /* Fudge if we woke up a little too soon */
\r
12975 fudge = intendedTickLength - lastTickLength;
\r
12976 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12978 if (WhiteOnMove(forwardMostMove)) {
\r
12979 if(whiteNPS >= 0) lastTickLength = 0;
\r
12980 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12981 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12982 WhiteOnMove(currentMove));
\r
12984 if(blackNPS >= 0) lastTickLength = 0;
\r
12985 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
12986 DisplayBlackClock(blackTimeRemaining - fudge,
\r
12987 !WhiteOnMove(currentMove));
\r
12990 if (CheckFlags()) return;
\r
12992 tickStartTM = now;
\r
12993 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
12994 StartClockTimer(intendedTickLength);
\r
12996 /* if the time remaining has fallen below the alarm threshold, sound the
\r
12997 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
12998 * with increment) the time remaining has increased to a level above the
\r
12999 * threshold, reset the alarm so it can sound again.
\r
13002 if (appData.icsActive && appData.icsAlarm) {
\r
13004 /* make sure we are dealing with the user's clock */
\r
13005 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13006 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13009 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13010 alarmSounded = FALSE;
\r
13011 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13012 PlayAlarmSound();
\r
13013 alarmSounded = TRUE;
\r
13019 /* A player has just moved, so stop the previously running
\r
13020 clock and (if in clock mode) start the other one.
\r
13021 We redisplay both clocks in case we're in ICS mode, because
\r
13022 ICS gives us an update to both clocks after every move.
\r
13023 Note that this routine is called *after* forwardMostMove
\r
13024 is updated, so the last fractional tick must be subtracted
\r
13025 from the color that is *not* on move now.
\r
13030 long lastTickLength;
\r
13032 int flagged = FALSE;
\r
13034 GetTimeMark(&now);
\r
13036 if (StopClockTimer() && appData.clockMode) {
\r
13037 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13038 if (WhiteOnMove(forwardMostMove)) {
\r
13039 if(blackNPS >= 0) lastTickLength = 0;
\r
13040 blackTimeRemaining -= lastTickLength;
\r
13041 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13042 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13043 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13044 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13046 if(whiteNPS >= 0) lastTickLength = 0;
\r
13047 whiteTimeRemaining -= lastTickLength;
\r
13048 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13049 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13050 pvInfoList[forwardMostMove-1].time =
\r
13051 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13053 flagged = CheckFlags();
\r
13055 CheckTimeControl();
\r
13057 if (flagged || !appData.clockMode) return;
\r
13059 switch (gameMode) {
\r
13060 case MachinePlaysBlack:
\r
13061 case MachinePlaysWhite:
\r
13062 case BeginningOfGame:
\r
13063 if (pausing) return;
\r
13067 case PlayFromGameFile:
\r
13068 case IcsExamining:
\r
13075 tickStartTM = now;
\r
13076 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13077 whiteTimeRemaining : blackTimeRemaining);
\r
13078 StartClockTimer(intendedTickLength);
\r
13082 /* Stop both clocks */
\r
13086 long lastTickLength;
\r
13089 if (!StopClockTimer()) return;
\r
13090 if (!appData.clockMode) return;
\r
13092 GetTimeMark(&now);
\r
13094 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13095 if (WhiteOnMove(forwardMostMove)) {
\r
13096 if(whiteNPS >= 0) lastTickLength = 0;
\r
13097 whiteTimeRemaining -= lastTickLength;
\r
13098 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13100 if(blackNPS >= 0) lastTickLength = 0;
\r
13101 blackTimeRemaining -= lastTickLength;
\r
13102 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13107 /* Start clock of player on move. Time may have been reset, so
\r
13108 if clock is already running, stop and restart it. */
\r
13112 (void) StopClockTimer(); /* in case it was running already */
\r
13113 DisplayBothClocks();
\r
13114 if (CheckFlags()) return;
\r
13116 if (!appData.clockMode) return;
\r
13117 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13119 GetTimeMark(&tickStartTM);
\r
13120 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13121 whiteTimeRemaining : blackTimeRemaining);
\r
13123 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13124 whiteNPS = blackNPS = -1;
\r
13125 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13126 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13127 whiteNPS = first.nps;
\r
13128 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13129 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13130 blackNPS = first.nps;
\r
13131 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13132 whiteNPS = second.nps;
\r
13133 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13134 blackNPS = second.nps;
\r
13135 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13137 StartClockTimer(intendedTickLength);
\r
13144 long second, minute, hour, day;
\r
13146 static char buf[32];
\r
13148 if (ms > 0 && ms <= 9900) {
\r
13149 /* convert milliseconds to tenths, rounding up */
\r
13150 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13152 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13156 /* convert milliseconds to seconds, rounding up */
\r
13157 /* use floating point to avoid strangeness of integer division
\r
13158 with negative dividends on many machines */
\r
13159 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13161 if (second < 0) {
\r
13163 second = -second;
\r
13166 day = second / (60 * 60 * 24);
\r
13167 second = second % (60 * 60 * 24);
\r
13168 hour = second / (60 * 60);
\r
13169 second = second % (60 * 60);
\r
13170 minute = second / 60;
\r
13171 second = second % 60;
\r
13174 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13175 sign, day, hour, minute, second);
\r
13176 else if (hour > 0)
\r
13177 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13179 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13186 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13189 StrStr(string, match)
\r
13190 char *string, *match;
\r
13194 length = strlen(match);
\r
13196 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13197 if (!strncmp(match, string, length))
\r
13204 StrCaseStr(string, match)
\r
13205 char *string, *match;
\r
13207 int i, j, length;
\r
13209 length = strlen(match);
\r
13211 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13212 for (j = 0; j < length; j++) {
\r
13213 if (ToLower(match[j]) != ToLower(string[j]))
\r
13216 if (j == length) return string;
\r
13222 #ifndef _amigados
\r
13224 StrCaseCmp(s1, s2)
\r
13230 c1 = ToLower(*s1++);
\r
13231 c2 = ToLower(*s2++);
\r
13232 if (c1 > c2) return 1;
\r
13233 if (c1 < c2) return -1;
\r
13234 if (c1 == NULLCHAR) return 0;
\r
13243 return isupper(c) ? tolower(c) : c;
\r
13251 return islower(c) ? toupper(c) : c;
\r
13253 #endif /* !_amigados */
\r
13261 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13268 StrSavePtr(s, savePtr)
\r
13269 char *s, **savePtr;
\r
13274 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13275 strcpy(*savePtr, s);
\r
13277 return(*savePtr);
\r
13285 char buf[MSG_SIZ];
\r
13287 clock = time((time_t *)NULL);
\r
13288 tm = localtime(&clock);
\r
13289 sprintf(buf, "%04d.%02d.%02d",
\r
13290 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13291 return StrSave(buf);
\r
13296 PositionToFEN(move, useFEN960)
\r
13300 int i, j, fromX, fromY, toX, toY;
\r
13305 ChessSquare piece;
\r
13307 whiteToPlay = (gameMode == EditPosition) ?
\r
13308 !blackPlaysFirst : (move % 2 == 0);
\r
13311 /* Piece placement data */
\r
13312 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13314 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13315 if (boards[move][i][j] == EmptySquare) {
\r
13317 } else { ChessSquare piece = boards[move][i][j];
\r
13318 if (emptycount > 0) {
\r
13319 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13320 *p++ = '0' + emptycount;
\r
13321 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13324 if(PieceToChar(piece) == '+') {
\r
13325 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13327 piece = (ChessSquare)(DEMOTED piece);
\r
13329 *p++ = PieceToChar(piece);
\r
13330 if(p[-1] == '~') {
\r
13331 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13332 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13337 if (emptycount > 0) {
\r
13338 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13339 *p++ = '0' + emptycount;
\r
13340 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13347 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13348 if( gameInfo.holdingsWidth ) {
\r
13349 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13351 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13352 piece = boards[move][i][BOARD_WIDTH-1];
\r
13353 if( piece != EmptySquare )
\r
13354 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13355 *p++ = PieceToChar(piece);
\r
13357 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13358 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13359 if( piece != EmptySquare )
\r
13360 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13361 *p++ = PieceToChar(piece);
\r
13364 if( q == p ) *p++ = '-';
\r
13369 /* Active color */
\r
13370 *p++ = whiteToPlay ? 'w' : 'b';
\r
13373 if(nrCastlingRights) {
\r
13375 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13376 /* [HGM] write directly from rights */
\r
13377 if(castlingRights[move][2] >= 0 &&
\r
13378 castlingRights[move][0] >= 0 )
\r
13379 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13380 if(castlingRights[move][2] >= 0 &&
\r
13381 castlingRights[move][1] >= 0 )
\r
13382 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13383 if(castlingRights[move][5] >= 0 &&
\r
13384 castlingRights[move][3] >= 0 )
\r
13385 *p++ = castlingRights[move][3] + AAA;
\r
13386 if(castlingRights[move][5] >= 0 &&
\r
13387 castlingRights[move][4] >= 0 )
\r
13388 *p++ = castlingRights[move][4] + AAA;
\r
13391 /* [HGM] write true castling rights */
\r
13392 if( nrCastlingRights == 6 ) {
\r
13393 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13394 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13395 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13396 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13397 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13398 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13399 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13400 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13403 if (q == p) *p++ = '-'; /* No castling rights */
\r
13407 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13408 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13409 /* En passant target square */
\r
13410 if (move > backwardMostMove) {
\r
13411 fromX = moveList[move - 1][0] - AAA;
\r
13412 fromY = moveList[move - 1][1] - ONE;
\r
13413 toX = moveList[move - 1][2] - AAA;
\r
13414 toY = moveList[move - 1][3] - ONE;
\r
13415 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13416 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13417 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13419 /* 2-square pawn move just happened */
\r
13420 *p++ = toX + AAA;
\r
13421 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13431 /* [HGM] find reversible plies */
\r
13432 { int i = 0, j=move;
\r
13434 if (appData.debugMode) { int k;
\r
13435 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13436 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13437 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13441 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13442 if( j == backwardMostMove ) i += initialRulePlies;
\r
13443 sprintf(p, "%d ", i);
\r
13444 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13446 /* Fullmove number */
\r
13447 sprintf(p, "%d", (move / 2) + 1);
\r
13449 return StrSave(buf);
\r
13453 ParseFEN(board, blackPlaysFirst, fen)
\r
13455 int *blackPlaysFirst;
\r
13461 ChessSquare piece;
\r
13465 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13466 if(gameInfo.holdingsWidth) {
\r
13467 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13468 board[i][0] = EmptySquare; /* black holdings */
\r
13469 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13470 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13471 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13475 /* Piece placement data */
\r
13476 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13479 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13480 if (*p == '/') p++;
\r
13481 emptycount = gameInfo.boardWidth - j;
\r
13482 while (emptycount--)
\r
13483 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13485 #if(BOARD_SIZE >= 10)
\r
13486 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13487 p++; emptycount=10;
\r
13488 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13489 while (emptycount--)
\r
13490 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13492 } else if (isdigit(*p)) {
\r
13493 emptycount = *p++ - '0';
\r
13494 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13495 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13496 while (emptycount--)
\r
13497 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13498 } else if (*p == '+' || isalpha(*p)) {
\r
13499 if (j >= gameInfo.boardWidth) return FALSE;
\r
13501 piece = CharToPiece(*++p);
\r
13502 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13503 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13504 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13505 } else piece = CharToPiece(*p++);
\r
13507 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13508 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13509 piece = (ChessSquare) (PROMOTED piece);
\r
13510 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13513 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13519 while (*p == '/' || *p == ' ') p++;
\r
13521 /* [HGM] look for Crazyhouse holdings here */
\r
13522 while(*p==' ') p++;
\r
13523 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13524 if(*p == '[') p++;
\r
13525 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13526 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13527 /* if we would allow FEN reading to set board size, we would */
\r
13528 /* have to add holdings and shift the board read so far here */
\r
13529 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13531 if((int) piece >= (int) BlackPawn ) {
\r
13532 i = (int)piece - (int)BlackPawn;
\r
13533 i = PieceToNumber((ChessSquare)i);
\r
13534 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13535 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13536 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13538 i = (int)piece - (int)WhitePawn;
\r
13539 i = PieceToNumber((ChessSquare)i);
\r
13540 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13541 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13542 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13546 if(*p == ']') *p++;
\r
13549 while(*p == ' ') p++;
\r
13551 /* Active color */
\r
13554 *blackPlaysFirst = FALSE;
\r
13557 *blackPlaysFirst = TRUE;
\r
13563 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13564 /* return the extra info in global variiables */
\r
13566 /* set defaults in case FEN is incomplete */
\r
13567 FENepStatus = EP_UNKNOWN;
\r
13568 for(i=0; i<nrCastlingRights; i++ ) {
\r
13569 FENcastlingRights[i] =
\r
13570 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13571 } /* assume possible unless obviously impossible */
\r
13572 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13573 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13574 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13575 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13576 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13577 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13578 FENrulePlies = 0;
\r
13580 while(*p==' ') p++;
\r
13581 if(nrCastlingRights) {
\r
13582 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13583 /* castling indicator present, so default becomes no castlings */
\r
13584 for(i=0; i<nrCastlingRights; i++ ) {
\r
13585 FENcastlingRights[i] = -1;
\r
13588 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13589 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13590 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13591 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13592 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13594 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13595 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13596 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13600 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13601 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13602 FENcastlingRights[2] = whiteKingFile;
\r
13605 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13606 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13607 FENcastlingRights[2] = whiteKingFile;
\r
13610 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13611 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13612 FENcastlingRights[5] = blackKingFile;
\r
13615 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13616 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13617 FENcastlingRights[5] = blackKingFile;
\r
13620 default: /* FRC castlings */
\r
13621 if(c >= 'a') { /* black rights */
\r
13622 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13623 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13624 if(i == BOARD_RGHT) break;
\r
13625 FENcastlingRights[5] = i;
\r
13627 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13628 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13630 FENcastlingRights[3] = c;
\r
13632 FENcastlingRights[4] = c;
\r
13633 } else { /* white rights */
\r
13634 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13635 if(board[0][i] == WhiteKing) break;
\r
13636 if(i == BOARD_RGHT) break;
\r
13637 FENcastlingRights[2] = i;
\r
13638 c -= AAA - 'a' + 'A';
\r
13639 if(board[0][c] >= WhiteKing) break;
\r
13641 FENcastlingRights[0] = c;
\r
13643 FENcastlingRights[1] = c;
\r
13647 if (appData.debugMode) {
\r
13648 fprintf(debugFP, "FEN castling rights:");
\r
13649 for(i=0; i<nrCastlingRights; i++)
\r
13650 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13651 fprintf(debugFP, "\n");
\r
13654 while(*p==' ') p++;
\r
13657 /* read e.p. field in games that know e.p. capture */
\r
13658 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13659 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13661 p++; FENepStatus = EP_NONE;
\r
13663 char c = *p++ - AAA;
\r
13665 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13666 if(*p >= '0' && *p <='9') *p++;
\r
13672 if(sscanf(p, "%d", &i) == 1) {
\r
13673 FENrulePlies = i; /* 50-move ply counter */
\r
13674 /* (The move number is still ignored) */
\r
13681 EditPositionPasteFEN(char *fen)
\r
13683 if (fen != NULL) {
\r
13684 Board initial_position;
\r
13686 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13687 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13690 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13691 EditPositionEvent();
\r
13692 blackPlaysFirst = savedBlackPlaysFirst;
\r
13693 CopyBoard(boards[0], initial_position);
\r
13694 /* [HGM] copy FEN attributes as well */
\r
13696 initialRulePlies = FENrulePlies;
\r
13697 epStatus[0] = FENepStatus;
\r
13698 for( i=0; i<nrCastlingRights; i++ )
\r
13699 castlingRights[0][i] = FENcastlingRights[i];
\r
13701 EditPositionDone();
\r
13702 DisplayBothClocks();
\r
13703 DrawPosition(FALSE, boards[currentMove]);
\r