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, Massachusetts.
\r
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
\r
8 * The following terms apply to Digital Equipment Corporation's copyright
\r
9 * interest in XBoard:
\r
10 * ------------------------------------------------------------------------
\r
11 * All Rights Reserved
\r
13 * Permission to use, copy, modify, and distribute this software and its
\r
14 * documentation for any purpose and without fee is hereby granted,
\r
15 * provided that the above copyright notice appear in all copies and that
\r
16 * both that copyright notice and this permission notice appear in
\r
17 * supporting documentation, and that the name of Digital not be
\r
18 * used in advertising or publicity pertaining to distribution of the
\r
19 * software without specific, written prior permission.
\r
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
28 * ------------------------------------------------------------------------
\r
30 * The following terms apply to the enhanced version of XBoard distributed
\r
31 * by the Free Software Foundation:
\r
32 * ------------------------------------------------------------------------
\r
33 * This program is free software; you can redistribute it and/or modify
\r
34 * it under the terms of the GNU General Public License as published by
\r
35 * the Free Software Foundation; either version 2 of the License, or
\r
36 * (at your option) any later version.
\r
38 * This program is distributed in the hope that it will be useful,
\r
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
41 * GNU General Public License for more details.
\r
43 * You should have received a copy of the GNU General Public License
\r
44 * along with this program; if not, write to the Free Software
\r
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\r
46 * ------------------------------------------------------------------------
\r
48 * See the file ChangeLog for a revision history. */
\r
50 /* [AS] Also useful here for debugging */
\r
52 #include <windows.h>
\r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
58 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
68 #include <sys/types.h>
\r
69 #include <sys/stat.h>
\r
73 # include <stdlib.h>
\r
74 # include <string.h>
\r
75 #else /* not STDC_HEADERS */
\r
77 # include <string.h>
\r
78 # else /* not HAVE_STRING_H */
\r
79 # include <strings.h>
\r
80 # endif /* not HAVE_STRING_H */
\r
81 #endif /* not STDC_HEADERS */
\r
83 #if HAVE_SYS_FCNTL_H
\r
84 # include <sys/fcntl.h>
\r
85 #else /* not HAVE_SYS_FCNTL_H */
\r
88 # endif /* HAVE_FCNTL_H */
\r
89 #endif /* not HAVE_SYS_FCNTL_H */
\r
91 #if TIME_WITH_SYS_TIME
\r
92 # include <sys/time.h>
\r
95 # if HAVE_SYS_TIME_H
\r
96 # include <sys/time.h>
\r
102 #if defined(_amigados) && !defined(__GNUC__)
\r
104 int tz_minuteswest;
\r
107 extern int gettimeofday(struct timeval *, struct timezone *);
\r
111 # include <unistd.h>
\r
114 #include "common.h"
\r
115 #include "frontend.h"
\r
116 #include "backend.h"
\r
117 #include "parser.h"
\r
120 # include "zippy.h"
\r
122 #include "backendz.h"
\r
123 #include "gettext.h"
\r
126 # define _(s) gettext (s)
\r
127 # define N_(s) gettext_noop (s)
\r
134 /* A point in time */
\r
136 long sec; /* Assuming this is >= 32 bits */
\r
137 int ms; /* Assuming this is >= 16 bits */
\r
140 int establish P((void));
\r
141 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
142 char *buf, int count, int error));
\r
143 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
144 char *buf, int count, int error));
\r
145 void SendToICS P((char *s));
\r
146 void SendToICSDelayed P((char *s, long msdelay));
\r
147 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
148 int toX, int toY));
\r
149 void InitPosition P((int redraw));
\r
150 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
151 int AutoPlayOneMove P((void));
\r
152 int LoadGameOneMove P((ChessMove readAhead));
\r
153 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
154 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
155 int SavePositionToFile P((char *filename));
\r
156 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
158 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
159 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
160 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
161 /*char*/int promoChar));
\r
162 void BackwardInner P((int target));
\r
163 void ForwardInner P((int target));
\r
164 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
165 void EditPositionDone P((void));
\r
166 void PrintOpponents P((FILE *fp));
\r
167 void PrintPosition P((FILE *fp, int move));
\r
168 void StartChessProgram P((ChessProgramState *cps));
\r
169 void SendToProgram P((char *message, ChessProgramState *cps));
\r
170 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
171 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
172 char *buf, int count, int error));
\r
173 void SendTimeControl P((ChessProgramState *cps,
\r
174 int mps, long tc, int inc, int sd, int st));
\r
175 char *TimeControlTagValue P((void));
\r
176 void Attention P((ChessProgramState *cps));
\r
177 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
178 void ResurrectChessProgram P((void));
\r
179 void DisplayComment P((int moveNumber, char *text));
\r
180 void DisplayMove P((int moveNumber));
\r
181 void DisplayAnalysis P((void));
\r
183 void ParseGameHistory P((char *game));
\r
184 void ParseBoard12 P((char *string));
\r
185 void StartClocks P((void));
\r
186 void SwitchClocks P((void));
\r
187 void StopClocks P((void));
\r
188 void ResetClocks P((void));
\r
189 char *PGNDate P((void));
\r
190 void SetGameInfo P((void));
\r
191 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
192 int RegisterMove P((void));
\r
193 void MakeRegisteredMove P((void));
\r
194 void TruncateGame P((void));
\r
195 int looking_at P((char *, int *, char *));
\r
196 void CopyPlayerNameIntoFileName P((char **, char *));
\r
197 char *SavePart P((char *));
\r
198 int SaveGameOldStyle P((FILE *));
\r
199 int SaveGamePGN P((FILE *));
\r
200 void GetTimeMark P((TimeMark *));
\r
201 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
202 int CheckFlags P((void));
\r
203 long NextTickLength P((long));
\r
204 void CheckTimeControl P((void));
\r
205 void show_bytes P((FILE *, char *, int));
\r
206 int string_to_rating P((char *str));
\r
207 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
208 void InitBackEnd3 P((void));
\r
209 void FeatureDone P((ChessProgramState* cps, int val));
\r
210 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
213 extern void ConsoleCreate();
\r
216 ChessProgramState *WhitePlayer();
\r
217 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
218 int VerifyDisplayMode P(());
\r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
221 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
222 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
223 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
224 extern char installDir[MSG_SIZ];
\r
226 extern int tinyLayout, smallLayout;
\r
227 ChessProgramStats programStats;
\r
228 static int exiting = 0; /* [HGM] moved to top */
\r
229 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;
\r
230 extern int startedFromPositionFile;
\r
231 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
232 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
233 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
234 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
235 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
236 int opponentKibitzes;
\r
238 /* States for ics_getting_history */
\r
240 #define H_REQUESTED 1
\r
241 #define H_GOT_REQ_HEADER 2
\r
242 #define H_GOT_UNREQ_HEADER 3
\r
243 #define H_GETTING_MOVES 4
\r
244 #define H_GOT_UNWANTED_HEADER 5
\r
246 /* whosays values for GameEnds */
\r
248 #define GE_ENGINE 1
\r
249 #define GE_PLAYER 2
\r
251 #define GE_XBOARD 4
\r
252 #define GE_ENGINE1 5
\r
253 #define GE_ENGINE2 6
\r
255 /* Maximum number of games in a cmail message */
\r
256 #define CMAIL_MAX_GAMES 20
\r
258 /* Different types of move when calling RegisterMove */
\r
259 #define CMAIL_MOVE 0
\r
260 #define CMAIL_RESIGN 1
\r
261 #define CMAIL_DRAW 2
\r
262 #define CMAIL_ACCEPT 3
\r
264 /* Different types of result to remember for each game */
\r
265 #define CMAIL_NOT_RESULT 0
\r
266 #define CMAIL_OLD_RESULT 1
\r
267 #define CMAIL_NEW_RESULT 2
\r
269 /* Telnet protocol constants */
\r
270 #define TN_WILL 0373
\r
271 #define TN_WONT 0374
\r
273 #define TN_DONT 0376
\r
274 #define TN_IAC 0377
\r
275 #define TN_ECHO 0001
\r
276 #define TN_SGA 0003
\r
280 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
282 assert( dst != NULL );
\r
283 assert( src != NULL );
\r
284 assert( count > 0 );
\r
286 strncpy( dst, src, count );
\r
287 dst[ count-1 ] = '\0';
\r
291 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
295 assert( dst != NULL );
\r
296 assert( src != NULL );
\r
297 assert( count > 0 );
\r
299 dst_len = strlen(dst);
\r
301 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
303 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
308 /* Some compiler can't cast u64 to double
\r
309 * This function do the job for us:
\r
311 * We use the highest bit for cast, this only
\r
312 * works if the highest bit is not
\r
313 * in use (This should not happen)
\r
315 * We used this for all compiler
\r
318 u64ToDouble(u64 value)
\r
321 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
322 r = (double)(s64)tmp;
\r
323 if (value & u64Const(0x8000000000000000))
\r
324 r += 9.2233720368547758080e18; /* 2^63 */
\r
328 /* Fake up flags for now, as we aren't keeping track of castling
\r
329 availability yet. [HGM] Change of logic: the flag now only
\r
330 indicates the type of castlings allowed by the rule of the game.
\r
331 The actual rights themselves are maintained in the array
\r
332 castlingRights, as part of the game history, and are not probed
\r
338 int flags = F_ALL_CASTLE_OK;
\r
339 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
340 switch (gameInfo.variant) {
\r
341 case VariantSuicide:
\r
342 flags &= ~F_ALL_CASTLE_OK;
\r
343 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
344 flags |= F_IGNORE_CHECK;
\r
346 case VariantAtomic:
\r
347 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
349 case VariantKriegspiel:
\r
350 flags |= F_KRIEGSPIEL_CAPTURE;
\r
352 case VariantCapaRandom:
\r
353 case VariantFischeRandom:
\r
354 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
355 case VariantNoCastle:
\r
356 case VariantShatranj:
\r
357 case VariantCourier:
\r
358 flags &= ~F_ALL_CASTLE_OK;
\r
366 FILE *gameFileFP, *debugFP;
\r
369 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
370 into a fixed-size buffer. Because of this, we must be prepared to
\r
371 receive strings as long as the size of the input buffer, which is currently
\r
372 set to 4K for Windows and 8K for the rest.
\r
373 So, we must either allocate sufficiently large buffers here, or
\r
374 reduce the size of the input buffer in the input reading part.
\r
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
379 char thinkOutput1[MSG_SIZ*10];
\r
381 ChessProgramState first, second;
\r
383 /* premove variables */
\r
384 int premoveToX = 0;
\r
385 int premoveToY = 0;
\r
386 int premoveFromX = 0;
\r
387 int premoveFromY = 0;
\r
388 int premovePromoChar = 0;
\r
389 int gotPremove = 0;
\r
390 Boolean alarmSounded;
\r
391 /* end premove variables */
\r
393 char *ics_prefix = "$";
\r
394 int ics_type = ICS_GENERIC;
\r
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
397 int pauseExamForwardMostMove = 0;
\r
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
404 int whiteFlag = FALSE, blackFlag = FALSE;
\r
405 int userOfferedDraw = FALSE;
\r
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
408 int cmailMoveType[CMAIL_MAX_GAMES];
\r
409 long ics_clock_paused = 0;
\r
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
412 GameMode gameMode = BeginningOfGame;
\r
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
416 int hiddenThinkOutputState = 0; /* [AS] */
\r
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
418 int adjudicateLossPlies = 6;
\r
419 char white_holding[64], black_holding[64];
\r
420 TimeMark lastNodeCountTime;
\r
421 long lastNodeCount=0;
\r
422 int have_sent_ICS_logon = 0;
\r
423 int movesPerSession;
\r
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
425 long timeControl_2; /* [AS] Allow separate time controls */
\r
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
427 long timeRemaining[2][MAX_MOVES];
\r
429 TimeMark programStartTime;
\r
430 char ics_handle[MSG_SIZ];
\r
431 int have_set_title = 0;
\r
433 /* animateTraining preserves the state of appData.animate
\r
434 * when Training mode is activated. This allows the
\r
435 * response to be animated when appData.animate == TRUE and
\r
436 * appData.animateDragging == TRUE.
\r
438 Boolean animateTraining;
\r
444 Board boards[MAX_MOVES];
\r
445 /* [HGM] Following 7 needed for accurate legality tests: */
\r
446 char epStatus[MAX_MOVES];
\r
447 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
448 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
449 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
450 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
451 int initialRulePlies, FENrulePlies;
\r
453 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
455 int shuffleOpenings;
\r
457 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
458 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
459 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
460 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
461 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
465 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
466 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
467 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
468 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
471 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
472 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
473 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
474 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
475 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
479 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
480 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
481 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
482 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
486 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
487 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
488 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
489 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
493 #if (BOARD_SIZE>=10)
\r
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
495 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
496 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
497 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
498 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
503 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
504 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
505 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
509 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
510 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
511 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
512 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
516 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
517 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
518 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
519 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
523 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
524 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
525 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
526 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
531 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
532 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
533 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
534 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
537 #define GothicArray CapablancaArray
\r
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
543 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
545 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
548 #define FalconArray CapablancaArray
\r
551 #else // !(BOARD_SIZE>=10)
\r
552 #define XiangqiPosition FIDEArray
\r
553 #define CapablancaArray FIDEArray
\r
554 #define GothicArray FIDEArray
\r
555 #define GreatArray FIDEArray
\r
556 #endif // !(BOARD_SIZE>=10)
\r
558 #if (BOARD_SIZE>=12)
\r
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
561 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
562 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
563 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
565 #else // !(BOARD_SIZE>=12)
\r
566 #define CourierArray CapablancaArray
\r
567 #endif // !(BOARD_SIZE>=12)
\r
570 Board initialPosition;
\r
573 /* Convert str to a rating. Checks for special cases of "----",
\r
575 "++++", etc. Also strips ()'s */
\r
577 string_to_rating(str)
\r
580 while(*str && !isdigit(*str)) ++str;
\r
582 return 0; /* One of the special "no rating" cases */
\r
588 ClearProgramStats()
\r
590 /* Init programStats */
\r
591 programStats.movelist[0] = 0;
\r
592 programStats.depth = 0;
\r
593 programStats.nr_moves = 0;
\r
594 programStats.moves_left = 0;
\r
595 programStats.nodes = 0;
\r
596 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
597 programStats.score = 0;
\r
598 programStats.got_only_move = 0;
\r
599 programStats.got_fail = 0;
\r
600 programStats.line_is_book = 0;
\r
606 int matched, min, sec;
\r
608 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
610 GetTimeMark(&programStartTime);
\r
612 ClearProgramStats();
\r
613 programStats.ok_to_send = 1;
\r
614 programStats.seen_stat = 0;
\r
617 * Initialize game list
\r
619 ListNew(&gameList);
\r
623 * Internet chess server status
\r
625 if (appData.icsActive) {
\r
626 appData.matchMode = FALSE;
\r
627 appData.matchGames = 0;
\r
629 appData.noChessProgram = !appData.zippyPlay;
\r
631 appData.zippyPlay = FALSE;
\r
632 appData.zippyTalk = FALSE;
\r
633 appData.noChessProgram = TRUE;
\r
635 if (*appData.icsHelper != NULLCHAR) {
\r
636 appData.useTelnet = TRUE;
\r
637 appData.telnetProgram = appData.icsHelper;
\r
640 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
643 /* [AS] Initialize pv info list [HGM] and game state */
\r
647 for( i=0; i<MAX_MOVES; i++ ) {
\r
648 pvInfoList[i].depth = -1;
\r
649 epStatus[i]=EP_NONE;
\r
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
655 * Parse timeControl resource
\r
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
658 appData.movesPerSession)) {
\r
660 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
661 DisplayFatalError(buf, 0, 2);
\r
665 * Parse searchTime resource
\r
667 if (*appData.searchTime != NULLCHAR) {
\r
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
669 if (matched == 1) {
\r
670 searchTime = min * 60;
\r
671 } else if (matched == 2) {
\r
672 searchTime = min * 60 + sec;
\r
675 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
676 DisplayFatalError(buf, 0, 2);
\r
680 /* [AS] Adjudication threshold */
\r
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
683 first.which = "first";
\r
684 second.which = "second";
\r
685 first.maybeThinking = second.maybeThinking = FALSE;
\r
686 first.pr = second.pr = NoProc;
\r
687 first.isr = second.isr = NULL;
\r
688 first.sendTime = second.sendTime = 2;
\r
689 first.sendDrawOffers = 1;
\r
690 if (appData.firstPlaysBlack) {
\r
691 first.twoMachinesColor = "black\n";
\r
692 second.twoMachinesColor = "white\n";
\r
694 first.twoMachinesColor = "white\n";
\r
695 second.twoMachinesColor = "black\n";
\r
697 first.program = appData.firstChessProgram;
\r
698 second.program = appData.secondChessProgram;
\r
699 first.host = appData.firstHost;
\r
700 second.host = appData.secondHost;
\r
701 first.dir = appData.firstDirectory;
\r
702 second.dir = appData.secondDirectory;
\r
703 first.other = &second;
\r
704 second.other = &first;
\r
705 first.initString = appData.initString;
\r
706 second.initString = appData.secondInitString;
\r
707 first.computerString = appData.firstComputerString;
\r
708 second.computerString = appData.secondComputerString;
\r
709 first.useSigint = second.useSigint = TRUE;
\r
710 first.useSigterm = second.useSigterm = TRUE;
\r
711 first.reuse = appData.reuseFirst;
\r
712 second.reuse = appData.reuseSecond;
\r
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
714 second.nps = appData.secondNPS;
\r
715 first.useSetboard = second.useSetboard = FALSE;
\r
716 first.useSAN = second.useSAN = FALSE;
\r
717 first.usePing = second.usePing = FALSE;
\r
718 first.lastPing = second.lastPing = 0;
\r
719 first.lastPong = second.lastPong = 0;
\r
720 first.usePlayother = second.usePlayother = FALSE;
\r
721 first.useColors = second.useColors = TRUE;
\r
722 first.useUsermove = second.useUsermove = FALSE;
\r
723 first.sendICS = second.sendICS = FALSE;
\r
724 first.sendName = second.sendName = appData.icsActive;
\r
725 first.sdKludge = second.sdKludge = FALSE;
\r
726 first.stKludge = second.stKludge = FALSE;
\r
727 TidyProgramName(first.program, first.host, first.tidy);
\r
728 TidyProgramName(second.program, second.host, second.tidy);
\r
729 first.matchWins = second.matchWins = 0;
\r
730 strcpy(first.variants, appData.variant);
\r
731 strcpy(second.variants, appData.variant);
\r
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
733 first.analyzing = second.analyzing = FALSE;
\r
734 first.initDone = second.initDone = FALSE;
\r
736 /* New features added by Tord: */
\r
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
739 /* End of new features added by Tord. */
\r
741 /* [HGM] time odds: set factor for each machine */
\r
742 first.timeOdds = appData.firstTimeOdds;
\r
743 second.timeOdds = appData.secondTimeOdds;
\r
745 if(appData.timeOddsMode) {
\r
746 norm = first.timeOdds;
\r
747 if(norm > second.timeOdds) norm = second.timeOdds;
\r
749 first.timeOdds /= norm;
\r
750 second.timeOdds /= norm;
\r
753 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
754 first.accumulateTC = appData.firstAccumulateTC;
\r
755 second.accumulateTC = appData.secondAccumulateTC;
\r
756 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
759 first.debug = second.debug = FALSE;
\r
760 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
762 /* [HGM] options */
\r
763 first.optionSettings = appData.firstOptions;
\r
764 second.optionSettings = appData.secondOptions;
\r
766 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
767 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
768 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
769 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
770 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
771 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
773 if (appData.firstProtocolVersion > PROTOVER ||
\r
774 appData.firstProtocolVersion < 1) {
\r
776 sprintf(buf, _("protocol version %d not supported"),
\r
777 appData.firstProtocolVersion);
\r
778 DisplayFatalError(buf, 0, 2);
\r
780 first.protocolVersion = appData.firstProtocolVersion;
\r
783 if (appData.secondProtocolVersion > PROTOVER ||
\r
784 appData.secondProtocolVersion < 1) {
\r
786 sprintf(buf, _("protocol version %d not supported"),
\r
787 appData.secondProtocolVersion);
\r
788 DisplayFatalError(buf, 0, 2);
\r
790 second.protocolVersion = appData.secondProtocolVersion;
\r
793 if (appData.icsActive) {
\r
794 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
795 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
796 appData.clockMode = FALSE;
\r
797 first.sendTime = second.sendTime = 0;
\r
801 /* Override some settings from environment variables, for backward
\r
802 compatibility. Unfortunately it's not feasible to have the env
\r
803 vars just set defaults, at least in xboard. Ugh.
\r
805 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
810 if (appData.noChessProgram) {
\r
811 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
812 + strlen(PATCHLEVEL));
\r
813 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
818 while (*q != ' ' && *q != NULLCHAR) q++;
\r
820 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
821 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
822 + strlen(PATCHLEVEL) + (q - p));
\r
823 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
824 strncat(programVersion, p, q - p);
\r
826 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
827 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
828 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
829 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
833 if (!appData.icsActive) {
\r
835 /* Check for variants that are supported only in ICS mode,
\r
836 or not at all. Some that are accepted here nevertheless
\r
837 have bugs; see comments below.
\r
839 VariantClass variant = StringToVariant(appData.variant);
\r
841 case VariantBughouse: /* need four players and two boards */
\r
842 case VariantKriegspiel: /* need to hide pieces and move details */
\r
843 /* case VariantFischeRandom: (Fabien: moved below) */
\r
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
845 DisplayFatalError(buf, 0, 2);
\r
848 case VariantUnknown:
\r
849 case VariantLoadable:
\r
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
860 DisplayFatalError(buf, 0, 2);
\r
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
865 case VariantGothic: /* [HGM] should work */
\r
866 case VariantCapablanca: /* [HGM] should work */
\r
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
868 case VariantShogi: /* [HGM] drops not tested for legality */
\r
869 case VariantKnightmate: /* [HGM] should work */
\r
870 case VariantCylinder: /* [HGM] untested */
\r
871 case VariantFalcon: /* [HGM] untested */
\r
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
873 offboard interposition not understood */
\r
874 case VariantNormal: /* definitely works! */
\r
875 case VariantWildCastle: /* pieces not automatically shuffled */
\r
876 case VariantNoCastle: /* pieces not automatically shuffled */
\r
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
878 case VariantLosers: /* should work except for win condition,
\r
879 and doesn't know captures are mandatory */
\r
880 case VariantSuicide: /* should work except for win condition,
\r
881 and doesn't know captures are mandatory */
\r
882 case VariantGiveaway: /* should work except for win condition,
\r
883 and doesn't know captures are mandatory */
\r
884 case VariantTwoKings: /* should work */
\r
885 case VariantAtomic: /* should work except for win condition */
\r
886 case Variant3Check: /* should work except for win condition */
\r
887 case VariantShatranj: /* should work except for all win conditions */
\r
888 case VariantBerolina: /* might work if TestLegality is off */
\r
889 case VariantCapaRandom: /* should work */
\r
890 case VariantJanus: /* should work */
\r
891 case VariantSuper: /* experimental */
\r
892 case VariantGreat: /* experimental, requires legality testing to be off */
\r
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
898 InitEngineUCI( installDir, &second );
\r
901 int NextIntegerFromString( char ** str, long * value )
\r
906 while( *s == ' ' || *s == '\t' ) {
\r
912 if( *s >= '0' && *s <= '9' ) {
\r
913 while( *s >= '0' && *s <= '9' ) {
\r
914 *value = *value * 10 + (*s - '0');
\r
926 int NextTimeControlFromString( char ** str, long * value )
\r
929 int result = NextIntegerFromString( str, &temp );
\r
931 if( result == 0 ) {
\r
932 *value = temp * 60; /* Minutes */
\r
933 if( **str == ':' ) {
\r
935 result = NextIntegerFromString( str, &temp );
\r
936 *value += temp; /* Seconds */
\r
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
945 int result = -1; long temp, temp2;
\r
947 if(**str != '+') return -1; // old params remain in force!
\r
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
952 /* time only: incremental or sudden-death time control */
\r
953 if(**str == '+') { /* increment follows; read it */
\r
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
956 *inc = temp2 * 1000;
\r
958 *moves = 0; *tc = temp * 1000;
\r
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
962 (*str)++; /* classical time control */
\r
963 result = NextTimeControlFromString( str, &temp2);
\r
966 *tc = temp2 * 1000;
\r
972 int GetTimeQuota(int movenr)
\r
973 { /* [HGM] get time to add from the multi-session time-control string */
\r
974 int moves=1; /* kludge to force reading of first session */
\r
975 long time, increment;
\r
976 char *s = fullTimeControlString;
\r
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
982 if(movenr == -1) return time; /* last move before new session */
\r
983 if(!moves) return increment; /* current session is incremental */
\r
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
985 } while(movenr >= -1); /* try again for next session */
\r
987 return 0; // no new time quota on this move
\r
991 ParseTimeControl(tc, ti, mps)
\r
997 int matched, min, sec;
\r
999 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1000 if (matched == 1) {
\r
1001 timeControl = min * 60 * 1000;
\r
1002 } else if (matched == 2) {
\r
1003 timeControl = (min * 60 + sec) * 1000;
\r
1010 char buf[MSG_SIZ];
\r
1012 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1015 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1016 else sprintf(buf, "+%s+%d", tc, ti);
\r
1019 sprintf(buf, "+%d/%s", mps, tc);
\r
1020 else sprintf(buf, "+%s", tc);
\r
1022 fullTimeControlString = StrSave(buf);
\r
1024 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1028 if( *tc == '/' ) {
\r
1029 /* Parse second time control */
\r
1032 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1040 timeControl_2 = tc2 * 1000;
\r
1043 timeControl_2 = 0;
\r
1050 timeControl = tc1 * 1000;
\r
1054 timeIncrement = ti * 1000; /* convert to ms */
\r
1055 movesPerSession = 0;
\r
1057 timeIncrement = 0;
\r
1058 movesPerSession = mps;
\r
1066 if (appData.debugMode) {
\r
1067 fprintf(debugFP, "%s\n", programVersion);
\r
1070 if (appData.matchGames > 0) {
\r
1071 appData.matchMode = TRUE;
\r
1072 } else if (appData.matchMode) {
\r
1073 appData.matchGames = 1;
\r
1075 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1076 appData.matchGames = appData.sameColorGames;
\r
1077 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1078 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1079 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1081 Reset(TRUE, FALSE);
\r
1082 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1085 /* kludge: allow timeout for initial "feature" commands */
\r
1087 DisplayMessage("", _("Starting chess program"));
\r
1088 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1093 InitBackEnd3 P((void))
\r
1095 GameMode initialMode;
\r
1096 char buf[MSG_SIZ];
\r
1099 InitChessProgram(&first, startedFromSetupPosition);
\r
1102 if (appData.icsActive) {
\r
1104 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1107 err = establish();
\r
1109 if (*appData.icsCommPort != NULLCHAR) {
\r
1110 sprintf(buf, _("Could not open comm port %s"),
\r
1111 appData.icsCommPort);
\r
1113 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1114 appData.icsHost, appData.icsPort);
\r
1116 DisplayFatalError(buf, err, 1);
\r
1121 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1123 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1124 } else if (appData.noChessProgram) {
\r
1130 if (*appData.cmailGameName != NULLCHAR) {
\r
1132 OpenLoopback(&cmailPR);
\r
1134 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1138 DisplayMessage("", "");
\r
1139 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1140 initialMode = BeginningOfGame;
\r
1141 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1142 initialMode = TwoMachinesPlay;
\r
1143 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1144 initialMode = AnalyzeFile;
\r
1145 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1146 initialMode = AnalyzeMode;
\r
1147 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1148 initialMode = MachinePlaysWhite;
\r
1149 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1150 initialMode = MachinePlaysBlack;
\r
1151 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1152 initialMode = EditGame;
\r
1153 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1154 initialMode = EditPosition;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1156 initialMode = Training;
\r
1158 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1159 DisplayFatalError(buf, 0, 2);
\r
1163 if (appData.matchMode) {
\r
1164 /* Set up machine vs. machine match */
\r
1165 if (appData.noChessProgram) {
\r
1166 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1172 if (*appData.loadGameFile != NULLCHAR) {
\r
1173 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1174 if(index<0) lastIndex = index = 1;
\r
1175 if (!LoadGameFromFile(appData.loadGameFile,
\r
1177 appData.loadGameFile, FALSE)) {
\r
1178 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1181 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1182 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1183 if(index<0) lastIndex = index = 1;
\r
1184 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1186 appData.loadPositionFile)) {
\r
1187 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1191 TwoMachinesEvent();
\r
1192 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1193 /* Set up cmail mode */
\r
1194 ReloadCmailMsgEvent(TRUE);
\r
1196 /* Set up other modes */
\r
1197 if (initialMode == AnalyzeFile) {
\r
1198 if (*appData.loadGameFile == NULLCHAR) {
\r
1199 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1203 if (*appData.loadGameFile != NULLCHAR) {
\r
1204 (void) LoadGameFromFile(appData.loadGameFile,
\r
1205 appData.loadGameIndex,
\r
1206 appData.loadGameFile, TRUE);
\r
1207 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1208 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1209 appData.loadPositionIndex,
\r
1210 appData.loadPositionFile);
\r
1211 /* [HGM] try to make self-starting even after FEN load */
\r
1212 /* to allow automatic setup of fairy variants with wtm */
\r
1213 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1214 gameMode = BeginningOfGame;
\r
1215 setboardSpoiledMachineBlack = 1;
\r
1217 /* [HGM] loadPos: make that every new game uses the setup */
\r
1218 /* from file as long as we do not switch variant */
\r
1219 if(!blackPlaysFirst) { int i;
\r
1220 startedFromPositionFile = TRUE;
\r
1221 CopyBoard(filePosition, boards[0]);
\r
1222 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1225 if (initialMode == AnalyzeMode) {
\r
1226 if (appData.noChessProgram) {
\r
1227 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1230 if (appData.icsActive) {
\r
1231 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1234 AnalyzeModeEvent();
\r
1235 } else if (initialMode == AnalyzeFile) {
\r
1236 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1237 ShowThinkingEvent();
\r
1238 AnalyzeFileEvent();
\r
1239 AnalysisPeriodicEvent(1);
\r
1240 } else if (initialMode == MachinePlaysWhite) {
\r
1241 if (appData.noChessProgram) {
\r
1242 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1246 if (appData.icsActive) {
\r
1247 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1251 MachineWhiteEvent();
\r
1252 } else if (initialMode == MachinePlaysBlack) {
\r
1253 if (appData.noChessProgram) {
\r
1254 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1258 if (appData.icsActive) {
\r
1259 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1263 MachineBlackEvent();
\r
1264 } else if (initialMode == TwoMachinesPlay) {
\r
1265 if (appData.noChessProgram) {
\r
1266 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1270 if (appData.icsActive) {
\r
1271 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1275 TwoMachinesEvent();
\r
1276 } else if (initialMode == EditGame) {
\r
1278 } else if (initialMode == EditPosition) {
\r
1279 EditPositionEvent();
\r
1280 } else if (initialMode == Training) {
\r
1281 if (*appData.loadGameFile == NULLCHAR) {
\r
1282 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1291 * Establish will establish a contact to a remote host.port.
\r
1292 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1293 * used to talk to the host.
\r
1294 * Returns 0 if okay, error code if not.
\r
1299 char buf[MSG_SIZ];
\r
1301 if (*appData.icsCommPort != NULLCHAR) {
\r
1302 /* Talk to the host through a serial comm port */
\r
1303 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1305 } else if (*appData.gateway != NULLCHAR) {
\r
1306 if (*appData.remoteShell == NULLCHAR) {
\r
1307 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1308 sprintf(buf, "%s %s %s",
\r
1309 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1310 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1313 /* Use the rsh program to run telnet program on a gateway host */
\r
1314 if (*appData.remoteUser == NULLCHAR) {
\r
1315 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1316 appData.gateway, appData.telnetProgram,
\r
1317 appData.icsHost, appData.icsPort);
\r
1319 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1320 appData.remoteShell, appData.gateway,
\r
1321 appData.remoteUser, appData.telnetProgram,
\r
1322 appData.icsHost, appData.icsPort);
\r
1324 return StartChildProcess(buf, "", &icsPR);
\r
1327 } else if (appData.useTelnet) {
\r
1328 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1331 /* TCP socket interface differs somewhat between
\r
1332 Unix and NT; handle details in the front end.
\r
1334 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1339 show_bytes(fp, buf, count)
\r
1345 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1346 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1355 /* Returns an errno value */
\r
1357 OutputMaybeTelnet(pr, message, count, outError)
\r
1363 char buf[8192], *p, *q, *buflim;
\r
1364 int left, newcount, outcount;
\r
1366 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1367 *appData.gateway != NULLCHAR) {
\r
1368 if (appData.debugMode) {
\r
1369 fprintf(debugFP, ">ICS: ");
\r
1370 show_bytes(debugFP, message, count);
\r
1371 fprintf(debugFP, "\n");
\r
1373 return OutputToProcess(pr, message, count, outError);
\r
1376 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1382 if (q >= buflim) {
\r
1383 if (appData.debugMode) {
\r
1384 fprintf(debugFP, ">ICS: ");
\r
1385 show_bytes(debugFP, buf, newcount);
\r
1386 fprintf(debugFP, "\n");
\r
1388 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1389 if (outcount < newcount) return -1; /* to be sure */
\r
1396 } else if (((unsigned char) *p) == TN_IAC) {
\r
1397 *q++ = (char) TN_IAC;
\r
1404 if (appData.debugMode) {
\r
1405 fprintf(debugFP, ">ICS: ");
\r
1406 show_bytes(debugFP, buf, newcount);
\r
1407 fprintf(debugFP, "\n");
\r
1409 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1410 if (outcount < newcount) return -1; /* to be sure */
\r
1415 read_from_player(isr, closure, message, count, error)
\r
1416 InputSourceRef isr;
\r
1422 int outError, outCount;
\r
1423 static int gotEof = 0;
\r
1425 /* Pass data read from player on to ICS */
\r
1428 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1429 if (outCount < count) {
\r
1430 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1432 } else if (count < 0) {
\r
1433 RemoveInputSource(isr);
\r
1434 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1435 } else if (gotEof++ > 0) {
\r
1436 RemoveInputSource(isr);
\r
1437 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1445 int count, outCount, outError;
\r
1447 if (icsPR == NULL) return;
\r
1449 count = strlen(s);
\r
1450 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1451 if (outCount < count) {
\r
1452 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1456 /* This is used for sending logon scripts to the ICS. Sending
\r
1457 without a delay causes problems when using timestamp on ICC
\r
1458 (at least on my machine). */
\r
1460 SendToICSDelayed(s,msdelay)
\r
1464 int count, outCount, outError;
\r
1466 if (icsPR == NULL) return;
\r
1468 count = strlen(s);
\r
1469 if (appData.debugMode) {
\r
1470 fprintf(debugFP, ">ICS: ");
\r
1471 show_bytes(debugFP, s, count);
\r
1472 fprintf(debugFP, "\n");
\r
1474 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1476 if (outCount < count) {
\r
1477 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1482 /* Remove all highlighting escape sequences in s
\r
1483 Also deletes any suffix starting with '('
\r
1486 StripHighlightAndTitle(s)
\r
1489 static char retbuf[MSG_SIZ];
\r
1492 while (*s != NULLCHAR) {
\r
1493 while (*s == '\033') {
\r
1494 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1495 if (*s != NULLCHAR) s++;
\r
1497 while (*s != NULLCHAR && *s != '\033') {
\r
1498 if (*s == '(' || *s == '[') {
\r
1509 /* Remove all highlighting escape sequences in s */
\r
1514 static char retbuf[MSG_SIZ];
\r
1517 while (*s != NULLCHAR) {
\r
1518 while (*s == '\033') {
\r
1519 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1520 if (*s != NULLCHAR) s++;
\r
1522 while (*s != NULLCHAR && *s != '\033') {
\r
1530 char *variantNames[] = VARIANT_NAMES;
\r
1535 return variantNames[v];
\r
1539 /* Identify a variant from the strings the chess servers use or the
\r
1540 PGN Variant tag names we use. */
\r
1542 StringToVariant(e)
\r
1547 VariantClass v = VariantNormal;
\r
1548 int i, found = FALSE;
\r
1549 char buf[MSG_SIZ];
\r
1553 /* [HGM] skip over optional board-size prefixes */
\r
1554 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1555 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1556 while( *e++ != '_');
\r
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1560 if (StrCaseStr(e, variantNames[i])) {
\r
1561 v = (VariantClass) i;
\r
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1569 || StrCaseStr(e, "wild/fr")
\r
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1571 v = VariantFischeRandom;
\r
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1573 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1576 if (isdigit(*p)) {
\r
1582 case 0: /* FICS only, actually */
\r
1584 /* Castling legal even if K starts on d-file */
\r
1585 v = VariantWildCastle;
\r
1590 /* Castling illegal even if K & R happen to start in
\r
1591 normal positions. */
\r
1592 v = VariantNoCastle;
\r
1605 /* Castling legal iff K & R start in normal positions */
\r
1606 v = VariantNormal;
\r
1611 /* Special wilds for position setup; unclear what to do here */
\r
1612 v = VariantLoadable;
\r
1615 /* Bizarre ICC game */
\r
1616 v = VariantTwoKings;
\r
1619 v = VariantKriegspiel;
\r
1622 v = VariantLosers;
\r
1625 v = VariantFischeRandom;
\r
1628 v = VariantCrazyhouse;
\r
1631 v = VariantBughouse;
\r
1634 v = Variant3Check;
\r
1637 /* Not quite the same as FICS suicide! */
\r
1638 v = VariantGiveaway;
\r
1641 v = VariantAtomic;
\r
1644 v = VariantShatranj;
\r
1647 /* Temporary names for future ICC types. The name *will* change in
\r
1648 the next xboard/WinBoard release after ICC defines it. */
\r
1677 v = VariantXiangqi;
\r
1680 v = VariantCourier;
\r
1683 v = VariantGothic;
\r
1686 v = VariantCapablanca;
\r
1689 v = VariantKnightmate;
\r
1695 v = VariantCylinder;
\r
1698 v = VariantFalcon;
\r
1701 v = VariantCapaRandom;
\r
1704 v = VariantBerolina;
\r
1716 /* Found "wild" or "w" in the string but no number;
\r
1717 must assume it's normal chess. */
\r
1718 v = VariantNormal;
\r
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1722 DisplayError(buf, 0);
\r
1723 v = VariantUnknown;
\r
1728 if (appData.debugMode) {
\r
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1730 e, wnum, VariantName(v));
\r
1735 static int leftover_start = 0, leftover_len = 0;
\r
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1739 advance *index beyond it, and set leftover_start to the new value of
\r
1740 *index; else return FALSE. If pattern contains the character '*', it
\r
1741 matches any sequence of characters not containing '\r', '\n', or the
\r
1742 character following the '*' (if any), and the matched sequence(s) are
\r
1743 copied into star_match.
\r
1746 looking_at(buf, index, pattern)
\r
1751 char *bufp = &buf[*index], *patternp = pattern;
\r
1752 int star_count = 0;
\r
1753 char *matchp = star_match[0];
\r
1756 if (*patternp == NULLCHAR) {
\r
1757 *index = leftover_start = bufp - buf;
\r
1758 *matchp = NULLCHAR;
\r
1761 if (*bufp == NULLCHAR) return FALSE;
\r
1762 if (*patternp == '*') {
\r
1763 if (*bufp == *(patternp + 1)) {
\r
1764 *matchp = NULLCHAR;
\r
1765 matchp = star_match[++star_count];
\r
1769 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1771 if (*patternp == NULLCHAR)
\r
1776 *matchp++ = *bufp++;
\r
1780 if (*patternp != *bufp) return FALSE;
\r
1787 SendToPlayer(data, length)
\r
1791 int error, outCount;
\r
1792 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1793 if (outCount < length) {
\r
1794 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1799 PackHolding(packed, holding)
\r
1803 char *p = holding;
\r
1805 int runlength = 0;
\r
1811 switch (runlength) {
\r
1822 sprintf(q, "%d", runlength);
\r
1834 /* Telnet protocol requests from the front end */
\r
1836 TelnetRequest(ddww, option)
\r
1837 unsigned char ddww, option;
\r
1839 unsigned char msg[3];
\r
1840 int outCount, outError;
\r
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1844 if (appData.debugMode) {
\r
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1861 sprintf(buf1, "%d", ddww);
\r
1866 optionStr = "ECHO";
\r
1870 sprintf(buf2, "%d", option);
\r
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1879 if (outCount < 3) {
\r
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1887 if (!appData.icsActive) return;
\r
1888 TelnetRequest(TN_DO, TN_ECHO);
\r
1894 if (!appData.icsActive) return;
\r
1895 TelnetRequest(TN_DONT, TN_ECHO);
\r
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1901 /* put the holdings sent to us by the server on the board holdings area */
\r
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1904 ChessSquare piece;
\r
1906 if(gameInfo.holdingsWidth < 2) return;
\r
1908 if( (int)lowestPiece >= BlackPawn ) {
\r
1909 holdingsColumn = 0;
\r
1911 holdingsStartRow = BOARD_HEIGHT-1;
\r
1914 holdingsColumn = BOARD_WIDTH-1;
\r
1915 countsColumn = BOARD_WIDTH-2;
\r
1916 holdingsStartRow = 0;
\r
1920 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1921 board[i][holdingsColumn] = EmptySquare;
\r
1922 board[i][countsColumn] = (ChessSquare) 0;
\r
1924 while( (p=*holdings++) != NULLCHAR ) {
\r
1925 piece = CharToPiece( ToUpper(p) );
\r
1926 if(piece == EmptySquare) continue;
\r
1927 /*j = (int) piece - (int) WhitePawn;*/
\r
1928 j = PieceToNumber(piece);
\r
1929 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1930 if(j < 0) continue; /* should not happen */
\r
1931 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1932 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1933 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1940 VariantSwitch(Board board, VariantClass newVariant)
\r
1942 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1943 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1944 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1946 startedFromPositionFile = FALSE;
\r
1947 if(gameInfo.variant == newVariant) return;
\r
1949 /* [HGM] This routine is called each time an assignment is made to
\r
1950 * gameInfo.variant during a game, to make sure the board sizes
\r
1951 * are set to match the new variant. If that means adding or deleting
\r
1952 * holdings, we shift the playing board accordingly
\r
1953 * This kludge is needed because in ICS observe mode, we get boards
\r
1954 * of an ongoing game without knowing the variant, and learn about the
\r
1955 * latter only later. This can be because of the move list we requested,
\r
1956 * in which case the game history is refilled from the beginning anyway,
\r
1957 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1958 * case we want to add those holdings to the already received position.
\r
1962 if (appData.debugMode) {
\r
1963 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1964 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1965 setbuf(debugFP, NULL);
\r
1967 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1969 switch(newVariant) {
\r
1970 case VariantShogi:
\r
1971 newWidth = 9; newHeight = 9;
\r
1972 gameInfo.holdingsSize = 7;
\r
1973 case VariantBughouse:
\r
1974 case VariantCrazyhouse:
\r
1975 newHoldingsWidth = 2; break;
\r
1977 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1980 if(newWidth != gameInfo.boardWidth ||
\r
1981 newHeight != gameInfo.boardHeight ||
\r
1982 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1984 /* shift position to new playing area, if needed */
\r
1985 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1986 for(i=0; i<BOARD_HEIGHT; i++)
\r
1987 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1988 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1990 for(i=0; i<newHeight; i++) {
\r
1991 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1992 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1994 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1995 for(i=0; i<BOARD_HEIGHT; i++)
\r
1996 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2001 gameInfo.boardWidth = newWidth;
\r
2002 gameInfo.boardHeight = newHeight;
\r
2003 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2004 gameInfo.variant = newVariant;
\r
2005 InitDrawingSizes(-2, 0);
\r
2007 /* [HGM] The following should definitely be solved in a better way */
\r
2009 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2010 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2011 saveEP = epStatus[0];
\r
2013 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2015 epStatus[0] = saveEP;
\r
2016 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2017 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2019 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2021 forwardMostMove = oldForwardMostMove;
\r
2022 backwardMostMove = oldBackwardMostMove;
\r
2023 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2026 static int loggedOn = FALSE;
\r
2028 /*-- Game start info cache: --*/
\r
2030 char gs_kind[MSG_SIZ];
\r
2031 static char player1Name[128] = "";
\r
2032 static char player2Name[128] = "";
\r
2033 static int player1Rating = -1;
\r
2034 static int player2Rating = -1;
\r
2035 /*----------------------------*/
\r
2037 ColorClass curColor = ColorNormal;
\r
2038 int suppressKibitz = 0;
\r
2041 read_from_ics(isr, closure, data, count, error)
\r
2042 InputSourceRef isr;
\r
2048 #define BUF_SIZE 8192
\r
2049 #define STARTED_NONE 0
\r
2050 #define STARTED_MOVES 1
\r
2051 #define STARTED_BOARD 2
\r
2052 #define STARTED_OBSERVE 3
\r
2053 #define STARTED_HOLDINGS 4
\r
2054 #define STARTED_CHATTER 5
\r
2055 #define STARTED_COMMENT 6
\r
2056 #define STARTED_MOVES_NOHIDE 7
\r
2058 static int started = STARTED_NONE;
\r
2059 static char parse[20000];
\r
2060 static int parse_pos = 0;
\r
2061 static char buf[BUF_SIZE + 1];
\r
2062 static int firstTime = TRUE, intfSet = FALSE;
\r
2063 static ColorClass prevColor = ColorNormal;
\r
2064 static int savingComment = FALSE;
\r
2070 int backup; /* [DM] For zippy color lines */
\r
2073 if (appData.debugMode) {
\r
2075 fprintf(debugFP, "<ICS: ");
\r
2076 show_bytes(debugFP, data, count);
\r
2077 fprintf(debugFP, "\n");
\r
2081 if (appData.debugMode) { int f = forwardMostMove;
\r
2082 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2083 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2086 /* If last read ended with a partial line that we couldn't parse,
\r
2087 prepend it to the new read and try again. */
\r
2088 if (leftover_len > 0) {
\r
2089 for (i=0; i<leftover_len; i++)
\r
2090 buf[i] = buf[leftover_start + i];
\r
2093 /* Copy in new characters, removing nulls and \r's */
\r
2094 buf_len = leftover_len;
\r
2095 for (i = 0; i < count; i++) {
\r
2096 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2097 buf[buf_len++] = data[i];
\r
2098 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2099 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2100 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2103 buf[buf_len] = NULLCHAR;
\r
2104 next_out = leftover_len;
\r
2105 leftover_start = 0;
\r
2108 while (i < buf_len) {
\r
2109 /* Deal with part of the TELNET option negotiation
\r
2110 protocol. We refuse to do anything beyond the
\r
2111 defaults, except that we allow the WILL ECHO option,
\r
2112 which ICS uses to turn off password echoing when we are
\r
2113 directly connected to it. We reject this option
\r
2114 if localLineEditing mode is on (always on in xboard)
\r
2115 and we are talking to port 23, which might be a real
\r
2116 telnet server that will try to keep WILL ECHO on permanently.
\r
2118 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2119 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2120 unsigned char option;
\r
2122 switch ((unsigned char) buf[++i]) {
\r
2124 if (appData.debugMode)
\r
2125 fprintf(debugFP, "\n<WILL ");
\r
2126 switch (option = (unsigned char) buf[++i]) {
\r
2128 if (appData.debugMode)
\r
2129 fprintf(debugFP, "ECHO ");
\r
2130 /* Reply only if this is a change, according
\r
2131 to the protocol rules. */
\r
2132 if (remoteEchoOption) break;
\r
2133 if (appData.localLineEditing &&
\r
2134 atoi(appData.icsPort) == TN_PORT) {
\r
2135 TelnetRequest(TN_DONT, TN_ECHO);
\r
2138 TelnetRequest(TN_DO, TN_ECHO);
\r
2139 remoteEchoOption = TRUE;
\r
2143 if (appData.debugMode)
\r
2144 fprintf(debugFP, "%d ", option);
\r
2145 /* Whatever this is, we don't want it. */
\r
2146 TelnetRequest(TN_DONT, option);
\r
2151 if (appData.debugMode)
\r
2152 fprintf(debugFP, "\n<WONT ");
\r
2153 switch (option = (unsigned char) buf[++i]) {
\r
2155 if (appData.debugMode)
\r
2156 fprintf(debugFP, "ECHO ");
\r
2157 /* Reply only if this is a change, according
\r
2158 to the protocol rules. */
\r
2159 if (!remoteEchoOption) break;
\r
2161 TelnetRequest(TN_DONT, TN_ECHO);
\r
2162 remoteEchoOption = FALSE;
\r
2165 if (appData.debugMode)
\r
2166 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2167 /* Whatever this is, it must already be turned
\r
2168 off, because we never agree to turn on
\r
2169 anything non-default, so according to the
\r
2170 protocol rules, we don't reply. */
\r
2175 if (appData.debugMode)
\r
2176 fprintf(debugFP, "\n<DO ");
\r
2177 switch (option = (unsigned char) buf[++i]) {
\r
2179 /* Whatever this is, we refuse to do it. */
\r
2180 if (appData.debugMode)
\r
2181 fprintf(debugFP, "%d ", option);
\r
2182 TelnetRequest(TN_WONT, option);
\r
2187 if (appData.debugMode)
\r
2188 fprintf(debugFP, "\n<DONT ");
\r
2189 switch (option = (unsigned char) buf[++i]) {
\r
2191 if (appData.debugMode)
\r
2192 fprintf(debugFP, "%d ", option);
\r
2193 /* Whatever this is, we are already not doing
\r
2194 it, because we never agree to do anything
\r
2195 non-default, so according to the protocol
\r
2196 rules, we don't reply. */
\r
2201 if (appData.debugMode)
\r
2202 fprintf(debugFP, "\n<IAC ");
\r
2203 /* Doubled IAC; pass it through */
\r
2207 if (appData.debugMode)
\r
2208 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2209 /* Drop all other telnet commands on the floor */
\r
2212 if (oldi > next_out)
\r
2213 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2214 if (++i > next_out)
\r
2219 /* OK, this at least will *usually* work */
\r
2220 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2224 if (loggedOn && !intfSet) {
\r
2225 if (ics_type == ICS_ICC) {
\r
2227 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2230 } else if (ics_type == ICS_CHESSNET) {
\r
2231 sprintf(str, "/style 12\n");
\r
2233 strcpy(str, "alias $ @\n$set interface ");
\r
2234 strcat(str, programVersion);
\r
2235 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2237 strcat(str, "$iset nohighlight 1\n");
\r
2239 strcat(str, "$iset lock 1\n$style 12\n");
\r
2245 if (started == STARTED_COMMENT) {
\r
2246 /* Accumulate characters in comment */
\r
2247 parse[parse_pos++] = buf[i];
\r
2248 if (buf[i] == '\n') {
\r
2249 parse[parse_pos] = NULLCHAR;
\r
2250 if(!suppressKibitz) // [HGM] kibitz
\r
2251 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2252 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2253 int nrDigit = 0, nrAlph = 0, i;
\r
2254 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2255 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2256 parse[parse_pos] = NULLCHAR;
\r
2257 // try to be smart: if it does not look like search info, it should go to
\r
2258 // ICS interaction window after all, not to engine-output window.
\r
2259 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2260 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2261 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2262 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2264 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2265 OutputKibitz(suppressKibitz, parse);
\r
2267 char tmp[MSG_SIZ];
\r
2268 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2269 SendToPlayer(tmp, strlen(tmp));
\r
2272 started = STARTED_NONE;
\r
2274 /* Don't match patterns against characters in chatter */
\r
2279 if (started == STARTED_CHATTER) {
\r
2280 if (buf[i] != '\n') {
\r
2281 /* Don't match patterns against characters in chatter */
\r
2285 started = STARTED_NONE;
\r
2288 /* Kludge to deal with rcmd protocol */
\r
2289 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2290 DisplayFatalError(&buf[1], 0, 1);
\r
2293 firstTime = FALSE;
\r
2296 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2297 ics_type = ICS_ICC;
\r
2299 if (appData.debugMode)
\r
2300 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2303 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2304 ics_type = ICS_FICS;
\r
2306 if (appData.debugMode)
\r
2307 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2310 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2311 ics_type = ICS_CHESSNET;
\r
2313 if (appData.debugMode)
\r
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2319 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2320 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2321 looking_at(buf, &i, "will be \"*\""))) {
\r
2322 strcpy(ics_handle, star_match[0]);
\r
2326 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2327 char buf[MSG_SIZ];
\r
2328 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2329 DisplayIcsInteractionTitle(buf);
\r
2330 have_set_title = TRUE;
\r
2333 /* skip finger notes */
\r
2334 if (started == STARTED_NONE &&
\r
2335 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2336 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2337 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2338 started = STARTED_CHATTER;
\r
2343 /* skip formula vars */
\r
2344 if (started == STARTED_NONE &&
\r
2345 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2346 started = STARTED_CHATTER;
\r
2352 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2353 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2354 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2355 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2356 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2357 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2358 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2359 suppressKibitz = TRUE;
\r
2360 if((StrStr(star_match[0], gameInfo.white) == star_match[0])
\r
2361 && (gameMode == IcsPlayingWhite) ||
\r
2362 (StrStr(star_match[0], gameInfo.black) == star_match[0])
\r
2363 && (gameMode == IcsPlayingBlack) ) // opponent kibitz
\r
2364 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2366 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2367 parse_pos = 0; parse[0] = NULLCHAR;
\r
2368 savingComment = TRUE;
\r
2369 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2370 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2374 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2375 started = STARTED_CHATTER;
\r
2376 suppressKibitz = TRUE;
\r
2378 } // [HGM] kibitz: end of patch
\r
2380 if (appData.zippyTalk || appData.zippyPlay) {
\r
2381 /* [DM] Backup address for color zippy lines */
\r
2385 if (loggedOn == TRUE)
\r
2386 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2387 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2389 if (ZippyControl(buf, &i) ||
\r
2390 ZippyConverse(buf, &i) ||
\r
2391 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2393 if (!appData.colorize) continue;
\r
2397 } // [DM] 'else { ' deleted
\r
2398 if (/* Don't color "message" or "messages" output */
\r
2399 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2400 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2401 looking_at(buf, &i, "--* (*:*): ") ||
\r
2402 /* Regular tells and says */
\r
2403 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2404 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2405 looking_at(buf, &i, "* says: ") ||
\r
2406 /* Message notifications (same color as tells) */
\r
2407 looking_at(buf, &i, "* has left a message ") ||
\r
2408 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2409 /* Whispers and kibitzes */
\r
2410 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2411 looking_at(buf, &i, "* kibitzes: ") ||
\r
2412 /* Channel tells */
\r
2413 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2415 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2416 /* Avoid "tells you:" spoofs in channels */
\r
2419 if (star_match[0][0] == NULLCHAR ||
\r
2420 strchr(star_match[0], ' ') ||
\r
2421 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2422 /* Reject bogus matches */
\r
2425 if (appData.colorize) {
\r
2426 if (oldi > next_out) {
\r
2427 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2432 Colorize(ColorTell, FALSE);
\r
2433 curColor = ColorTell;
\r
2436 Colorize(ColorKibitz, FALSE);
\r
2437 curColor = ColorKibitz;
\r
2440 p = strrchr(star_match[1], '(');
\r
2442 p = star_match[1];
\r
2446 if (atoi(p) == 1) {
\r
2447 Colorize(ColorChannel1, FALSE);
\r
2448 curColor = ColorChannel1;
\r
2450 Colorize(ColorChannel, FALSE);
\r
2451 curColor = ColorChannel;
\r
2455 curColor = ColorNormal;
\r
2459 if (started == STARTED_NONE && appData.autoComment &&
\r
2460 (gameMode == IcsObserving ||
\r
2461 gameMode == IcsPlayingWhite ||
\r
2462 gameMode == IcsPlayingBlack)) {
\r
2463 parse_pos = i - oldi;
\r
2464 memcpy(parse, &buf[oldi], parse_pos);
\r
2465 parse[parse_pos] = NULLCHAR;
\r
2466 started = STARTED_COMMENT;
\r
2467 savingComment = TRUE;
\r
2469 started = STARTED_CHATTER;
\r
2470 savingComment = FALSE;
\r
2477 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2478 looking_at(buf, &i, "* c-shouts: ")) {
\r
2479 if (appData.colorize) {
\r
2480 if (oldi > next_out) {
\r
2481 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2484 Colorize(ColorSShout, FALSE);
\r
2485 curColor = ColorSShout;
\r
2488 started = STARTED_CHATTER;
\r
2492 if (looking_at(buf, &i, "--->")) {
\r
2497 if (looking_at(buf, &i, "* shouts: ") ||
\r
2498 looking_at(buf, &i, "--> ")) {
\r
2499 if (appData.colorize) {
\r
2500 if (oldi > next_out) {
\r
2501 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2504 Colorize(ColorShout, FALSE);
\r
2505 curColor = ColorShout;
\r
2508 started = STARTED_CHATTER;
\r
2512 if (looking_at( buf, &i, "Challenge:")) {
\r
2513 if (appData.colorize) {
\r
2514 if (oldi > next_out) {
\r
2515 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2518 Colorize(ColorChallenge, FALSE);
\r
2519 curColor = ColorChallenge;
\r
2525 if (looking_at(buf, &i, "* offers you") ||
\r
2526 looking_at(buf, &i, "* offers to be") ||
\r
2527 looking_at(buf, &i, "* would like to") ||
\r
2528 looking_at(buf, &i, "* requests to") ||
\r
2529 looking_at(buf, &i, "Your opponent offers") ||
\r
2530 looking_at(buf, &i, "Your opponent requests")) {
\r
2532 if (appData.colorize) {
\r
2533 if (oldi > next_out) {
\r
2534 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2537 Colorize(ColorRequest, FALSE);
\r
2538 curColor = ColorRequest;
\r
2543 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2544 if (appData.colorize) {
\r
2545 if (oldi > next_out) {
\r
2546 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2549 Colorize(ColorSeek, FALSE);
\r
2550 curColor = ColorSeek;
\r
2555 if (looking_at(buf, &i, "\\ ")) {
\r
2556 if (prevColor != ColorNormal) {
\r
2557 if (oldi > next_out) {
\r
2558 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2561 Colorize(prevColor, TRUE);
\r
2562 curColor = prevColor;
\r
2564 if (savingComment) {
\r
2565 parse_pos = i - oldi;
\r
2566 memcpy(parse, &buf[oldi], parse_pos);
\r
2567 parse[parse_pos] = NULLCHAR;
\r
2568 started = STARTED_COMMENT;
\r
2570 started = STARTED_CHATTER;
\r
2575 if (looking_at(buf, &i, "Black Strength :") ||
\r
2576 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2577 looking_at(buf, &i, "<10>") ||
\r
2578 looking_at(buf, &i, "#@#")) {
\r
2579 /* Wrong board style */
\r
2581 SendToICS(ics_prefix);
\r
2582 SendToICS("set style 12\n");
\r
2583 SendToICS(ics_prefix);
\r
2584 SendToICS("refresh\n");
\r
2588 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2590 have_sent_ICS_logon = 1;
\r
2594 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2595 (looking_at(buf, &i, "\n<12> ") ||
\r
2596 looking_at(buf, &i, "<12> "))) {
\r
2598 if (oldi > next_out) {
\r
2599 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2602 started = STARTED_BOARD;
\r
2607 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2608 looking_at(buf, &i, "<b1> ")) {
\r
2609 if (oldi > next_out) {
\r
2610 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2613 started = STARTED_HOLDINGS;
\r
2618 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2620 /* Header for a move list -- first line */
\r
2622 switch (ics_getting_history) {
\r
2624 switch (gameMode) {
\r
2626 case BeginningOfGame:
\r
2627 /* User typed "moves" or "oldmoves" while we
\r
2628 were idle. Pretend we asked for these
\r
2629 moves and soak them up so user can step
\r
2630 through them and/or save them.
\r
2632 Reset(FALSE, TRUE);
\r
2633 gameMode = IcsObserving;
\r
2636 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2638 case EditGame: /*?*/
\r
2639 case EditPosition: /*?*/
\r
2640 /* Should above feature work in these modes too? */
\r
2641 /* For now it doesn't */
\r
2642 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2645 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2650 /* Is this the right one? */
\r
2651 if (gameInfo.white && gameInfo.black &&
\r
2652 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2653 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2655 ics_getting_history = H_GOT_REQ_HEADER;
\r
2658 case H_GOT_REQ_HEADER:
\r
2659 case H_GOT_UNREQ_HEADER:
\r
2660 case H_GOT_UNWANTED_HEADER:
\r
2661 case H_GETTING_MOVES:
\r
2662 /* Should not happen */
\r
2663 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2664 ics_getting_history = H_FALSE;
\r
2668 /* Save player ratings into gameInfo if needed */
\r
2669 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2670 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2671 (gameInfo.whiteRating == -1 ||
\r
2672 gameInfo.blackRating == -1)) {
\r
2674 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2675 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2676 if (appData.debugMode)
\r
2677 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2678 gameInfo.whiteRating, gameInfo.blackRating);
\r
2683 if (looking_at(buf, &i,
\r
2684 "* * match, initial time: * minute*, increment: * second")) {
\r
2685 /* Header for a move list -- second line */
\r
2686 /* Initial board will follow if this is a wild game */
\r
2687 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2688 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2689 gameInfo.event = StrSave(str);
\r
2690 /* [HGM] we switched variant. Translate boards if needed. */
\r
2691 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2695 if (looking_at(buf, &i, "Move ")) {
\r
2696 /* Beginning of a move list */
\r
2697 switch (ics_getting_history) {
\r
2699 /* Normally should not happen */
\r
2700 /* Maybe user hit reset while we were parsing */
\r
2703 /* Happens if we are ignoring a move list that is not
\r
2704 * the one we just requested. Common if the user
\r
2705 * tries to observe two games without turning off
\r
2708 case H_GETTING_MOVES:
\r
2709 /* Should not happen */
\r
2710 DisplayError(_("Error gathering move list: nested"), 0);
\r
2711 ics_getting_history = H_FALSE;
\r
2713 case H_GOT_REQ_HEADER:
\r
2714 ics_getting_history = H_GETTING_MOVES;
\r
2715 started = STARTED_MOVES;
\r
2717 if (oldi > next_out) {
\r
2718 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2721 case H_GOT_UNREQ_HEADER:
\r
2722 ics_getting_history = H_GETTING_MOVES;
\r
2723 started = STARTED_MOVES_NOHIDE;
\r
2726 case H_GOT_UNWANTED_HEADER:
\r
2727 ics_getting_history = H_FALSE;
\r
2733 if (looking_at(buf, &i, "% ") ||
\r
2734 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2735 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2736 savingComment = FALSE;
\r
2737 switch (started) {
\r
2738 case STARTED_MOVES:
\r
2739 case STARTED_MOVES_NOHIDE:
\r
2740 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2741 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2742 ParseGameHistory(parse);
\r
2744 if (appData.zippyPlay && first.initDone) {
\r
2745 FeedMovesToProgram(&first, forwardMostMove);
\r
2746 if (gameMode == IcsPlayingWhite) {
\r
2747 if (WhiteOnMove(forwardMostMove)) {
\r
2748 if (first.sendTime) {
\r
2749 if (first.useColors) {
\r
2750 SendToProgram("black\n", &first);
\r
2752 SendTimeRemaining(&first, TRUE);
\r
2755 if (first.useColors) {
\r
2756 SendToProgram("white\ngo\n", &first);
\r
2758 SendToProgram("go\n", &first);
\r
2761 if (first.useColors) {
\r
2762 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2764 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2766 first.maybeThinking = TRUE;
\r
2768 if (first.usePlayother) {
\r
2769 if (first.sendTime) {
\r
2770 SendTimeRemaining(&first, TRUE);
\r
2772 SendToProgram("playother\n", &first);
\r
2773 firstMove = FALSE;
\r
2778 } else if (gameMode == IcsPlayingBlack) {
\r
2779 if (!WhiteOnMove(forwardMostMove)) {
\r
2780 if (first.sendTime) {
\r
2781 if (first.useColors) {
\r
2782 SendToProgram("white\n", &first);
\r
2784 SendTimeRemaining(&first, FALSE);
\r
2787 if (first.useColors) {
\r
2788 SendToProgram("black\ngo\n", &first);
\r
2790 SendToProgram("go\n", &first);
\r
2793 if (first.useColors) {
\r
2794 SendToProgram("black\n", &first);
\r
2796 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2798 first.maybeThinking = TRUE;
\r
2800 if (first.usePlayother) {
\r
2801 if (first.sendTime) {
\r
2802 SendTimeRemaining(&first, FALSE);
\r
2804 SendToProgram("playother\n", &first);
\r
2805 firstMove = FALSE;
\r
2813 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2814 /* Moves came from oldmoves or moves command
\r
2815 while we weren't doing anything else.
\r
2817 currentMove = forwardMostMove;
\r
2818 ClearHighlights();/*!!could figure this out*/
\r
2819 flipView = appData.flipView;
\r
2820 DrawPosition(FALSE, boards[currentMove]);
\r
2821 DisplayBothClocks();
\r
2822 sprintf(str, "%s vs. %s",
\r
2823 gameInfo.white, gameInfo.black);
\r
2824 DisplayTitle(str);
\r
2825 gameMode = IcsIdle;
\r
2827 /* Moves were history of an active game */
\r
2828 if (gameInfo.resultDetails != NULL) {
\r
2829 free(gameInfo.resultDetails);
\r
2830 gameInfo.resultDetails = NULL;
\r
2833 HistorySet(parseList, backwardMostMove,
\r
2834 forwardMostMove, currentMove-1);
\r
2835 DisplayMove(currentMove - 1);
\r
2836 if (started == STARTED_MOVES) next_out = i;
\r
2837 started = STARTED_NONE;
\r
2838 ics_getting_history = H_FALSE;
\r
2841 case STARTED_OBSERVE:
\r
2842 started = STARTED_NONE;
\r
2843 SendToICS(ics_prefix);
\r
2844 SendToICS("refresh\n");
\r
2850 if(bookHit) { // [HGM] book: simulate book reply
\r
2851 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2853 programStats.depth = programStats.nodes = programStats.time =
\r
2854 programStats.score = programStats.got_only_move = 0;
\r
2855 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2857 strcpy(bookMove, "move ");
\r
2858 strcat(bookMove, bookHit);
\r
2859 HandleMachineMove(bookMove, &first);
\r
2864 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2865 started == STARTED_HOLDINGS ||
\r
2866 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2867 /* Accumulate characters in move list or board */
\r
2868 parse[parse_pos++] = buf[i];
\r
2871 /* Start of game messages. Mostly we detect start of game
\r
2872 when the first board image arrives. On some versions
\r
2873 of the ICS, though, we need to do a "refresh" after starting
\r
2874 to observe in order to get the current board right away. */
\r
2875 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2876 started = STARTED_OBSERVE;
\r
2880 /* Handle auto-observe */
\r
2881 if (appData.autoObserve &&
\r
2882 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2883 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2885 /* Choose the player that was highlighted, if any. */
\r
2886 if (star_match[0][0] == '\033' ||
\r
2887 star_match[1][0] != '\033') {
\r
2888 player = star_match[0];
\r
2890 player = star_match[2];
\r
2892 sprintf(str, "%sobserve %s\n",
\r
2893 ics_prefix, StripHighlightAndTitle(player));
\r
2896 /* Save ratings from notify string */
\r
2897 strcpy(player1Name, star_match[0]);
\r
2898 player1Rating = string_to_rating(star_match[1]);
\r
2899 strcpy(player2Name, star_match[2]);
\r
2900 player2Rating = string_to_rating(star_match[3]);
\r
2902 if (appData.debugMode)
\r
2904 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2905 player1Name, player1Rating,
\r
2906 player2Name, player2Rating);
\r
2911 /* Deal with automatic examine mode after a game,
\r
2912 and with IcsObserving -> IcsExamining transition */
\r
2913 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2914 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2916 int gamenum = atoi(star_match[0]);
\r
2917 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2918 gamenum == ics_gamenum) {
\r
2919 /* We were already playing or observing this game;
\r
2920 no need to refetch history */
\r
2921 gameMode = IcsExamining;
\r
2923 pauseExamForwardMostMove = forwardMostMove;
\r
2924 } else if (currentMove < forwardMostMove) {
\r
2925 ForwardInner(forwardMostMove);
\r
2928 /* I don't think this case really can happen */
\r
2929 SendToICS(ics_prefix);
\r
2930 SendToICS("refresh\n");
\r
2935 /* Error messages */
\r
2936 if (ics_user_moved) {
\r
2937 if (looking_at(buf, &i, "Illegal move") ||
\r
2938 looking_at(buf, &i, "Not a legal move") ||
\r
2939 looking_at(buf, &i, "Your king is in check") ||
\r
2940 looking_at(buf, &i, "It isn't your turn") ||
\r
2941 looking_at(buf, &i, "It is not your move")) {
\r
2942 /* Illegal move */
\r
2943 ics_user_moved = 0;
\r
2944 if (forwardMostMove > backwardMostMove) {
\r
2945 currentMove = --forwardMostMove;
\r
2946 DisplayMove(currentMove - 1); /* before DMError */
\r
2947 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2948 DrawPosition(FALSE, boards[currentMove]);
\r
2950 DisplayBothClocks();
\r
2956 if (looking_at(buf, &i, "still have time") ||
\r
2957 looking_at(buf, &i, "not out of time") ||
\r
2958 looking_at(buf, &i, "either player is out of time") ||
\r
2959 looking_at(buf, &i, "has timeseal; checking")) {
\r
2960 /* We must have called his flag a little too soon */
\r
2961 whiteFlag = blackFlag = FALSE;
\r
2965 if (looking_at(buf, &i, "added * seconds to") ||
\r
2966 looking_at(buf, &i, "seconds were added to")) {
\r
2967 /* Update the clocks */
\r
2968 SendToICS(ics_prefix);
\r
2969 SendToICS("refresh\n");
\r
2973 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2974 ics_clock_paused = TRUE;
\r
2979 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2980 ics_clock_paused = FALSE;
\r
2985 /* Grab player ratings from the Creating: message.
\r
2986 Note we have to check for the special case when
\r
2987 the ICS inserts things like [white] or [black]. */
\r
2988 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2989 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2991 0 player 1 name (not necessarily white)
\r
2993 2 empty, white, or black (IGNORED)
\r
2994 3 player 2 name (not necessarily black)
\r
2997 The names/ratings are sorted out when the game
\r
2998 actually starts (below).
\r
3000 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3001 player1Rating = string_to_rating(star_match[1]);
\r
3002 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3003 player2Rating = string_to_rating(star_match[4]);
\r
3005 if (appData.debugMode)
\r
3007 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3008 player1Name, player1Rating,
\r
3009 player2Name, player2Rating);
\r
3014 /* Improved generic start/end-of-game messages */
\r
3015 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3016 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3017 /* If tkind == 0: */
\r
3018 /* star_match[0] is the game number */
\r
3019 /* [1] is the white player's name */
\r
3020 /* [2] is the black player's name */
\r
3021 /* For end-of-game: */
\r
3022 /* [3] is the reason for the game end */
\r
3023 /* [4] is a PGN end game-token, preceded by " " */
\r
3024 /* For start-of-game: */
\r
3025 /* [3] begins with "Creating" or "Continuing" */
\r
3026 /* [4] is " *" or empty (don't care). */
\r
3027 int gamenum = atoi(star_match[0]);
\r
3028 char *whitename, *blackname, *why, *endtoken;
\r
3029 ChessMove endtype = (ChessMove) 0;
\r
3032 whitename = star_match[1];
\r
3033 blackname = star_match[2];
\r
3034 why = star_match[3];
\r
3035 endtoken = star_match[4];
\r
3037 whitename = star_match[1];
\r
3038 blackname = star_match[3];
\r
3039 why = star_match[5];
\r
3040 endtoken = star_match[6];
\r
3043 /* Game start messages */
\r
3044 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3045 strncmp(why, "Continuing ", 11) == 0) {
\r
3046 gs_gamenum = gamenum;
\r
3047 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3049 if (appData.zippyPlay) {
\r
3050 ZippyGameStart(whitename, blackname);
\r
3056 /* Game end messages */
\r
3057 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3058 ics_gamenum != gamenum) {
\r
3061 while (endtoken[0] == ' ') endtoken++;
\r
3062 switch (endtoken[0]) {
\r
3065 endtype = GameUnfinished;
\r
3068 endtype = BlackWins;
\r
3071 if (endtoken[1] == '/')
\r
3072 endtype = GameIsDrawn;
\r
3074 endtype = WhiteWins;
\r
3077 GameEnds(endtype, why, GE_ICS);
\r
3079 if (appData.zippyPlay && first.initDone) {
\r
3080 ZippyGameEnd(endtype, why);
\r
3081 if (first.pr == NULL) {
\r
3082 /* Start the next process early so that we'll
\r
3083 be ready for the next challenge */
\r
3084 StartChessProgram(&first);
\r
3086 /* Send "new" early, in case this command takes
\r
3087 a long time to finish, so that we'll be ready
\r
3088 for the next challenge. */
\r
3089 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3090 Reset(TRUE, TRUE);
\r
3096 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3097 looking_at(buf, &i, "no longer observing game *") ||
\r
3098 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3099 if (gameMode == IcsObserving &&
\r
3100 atoi(star_match[0]) == ics_gamenum)
\r
3102 /* icsEngineAnalyze */
\r
3103 if (appData.icsEngineAnalyze) {
\r
3104 ExitAnalyzeMode();
\r
3108 gameMode = IcsIdle;
\r
3110 ics_user_moved = FALSE;
\r
3115 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3116 if (gameMode == IcsExamining &&
\r
3117 atoi(star_match[0]) == ics_gamenum)
\r
3119 gameMode = IcsIdle;
\r
3121 ics_user_moved = FALSE;
\r
3126 /* Advance leftover_start past any newlines we find,
\r
3127 so only partial lines can get reparsed */
\r
3128 if (looking_at(buf, &i, "\n")) {
\r
3129 prevColor = curColor;
\r
3130 if (curColor != ColorNormal) {
\r
3131 if (oldi > next_out) {
\r
3132 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3135 Colorize(ColorNormal, FALSE);
\r
3136 curColor = ColorNormal;
\r
3138 if (started == STARTED_BOARD) {
\r
3139 started = STARTED_NONE;
\r
3140 parse[parse_pos] = NULLCHAR;
\r
3141 ParseBoard12(parse);
\r
3142 ics_user_moved = 0;
\r
3144 /* Send premove here */
\r
3145 if (appData.premove) {
\r
3146 char str[MSG_SIZ];
\r
3147 if (currentMove == 0 &&
\r
3148 gameMode == IcsPlayingWhite &&
\r
3149 appData.premoveWhite) {
\r
3150 sprintf(str, "%s%s\n", ics_prefix,
\r
3151 appData.premoveWhiteText);
\r
3152 if (appData.debugMode)
\r
3153 fprintf(debugFP, "Sending premove:\n");
\r
3155 } else if (currentMove == 1 &&
\r
3156 gameMode == IcsPlayingBlack &&
\r
3157 appData.premoveBlack) {
\r
3158 sprintf(str, "%s%s\n", ics_prefix,
\r
3159 appData.premoveBlackText);
\r
3160 if (appData.debugMode)
\r
3161 fprintf(debugFP, "Sending premove:\n");
\r
3163 } else if (gotPremove) {
\r
3165 ClearPremoveHighlights();
\r
3166 if (appData.debugMode)
\r
3167 fprintf(debugFP, "Sending premove:\n");
\r
3168 UserMoveEvent(premoveFromX, premoveFromY,
\r
3169 premoveToX, premoveToY,
\r
3170 premovePromoChar);
\r
3174 /* Usually suppress following prompt */
\r
3175 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3176 if (looking_at(buf, &i, "*% ")) {
\r
3177 savingComment = FALSE;
\r
3181 } else if (started == STARTED_HOLDINGS) {
\r
3183 char new_piece[MSG_SIZ];
\r
3184 started = STARTED_NONE;
\r
3185 parse[parse_pos] = NULLCHAR;
\r
3186 if (appData.debugMode)
\r
3187 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3188 parse, currentMove);
\r
3189 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3190 gamenum == ics_gamenum) {
\r
3191 if (gameInfo.variant == VariantNormal) {
\r
3192 /* [HGM] We seem to switch variant during a game!
\r
3193 * Presumably no holdings were displayed, so we have
\r
3194 * to move the position two files to the right to
\r
3195 * create room for them!
\r
3197 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3198 /* Get a move list just to see the header, which
\r
3199 will tell us whether this is really bug or zh */
\r
3200 if (ics_getting_history == H_FALSE) {
\r
3201 ics_getting_history = H_REQUESTED;
\r
3202 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3206 new_piece[0] = NULLCHAR;
\r
3207 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3208 &gamenum, white_holding, black_holding,
\r
3210 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3211 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3212 /* [HGM] copy holdings to board holdings area */
\r
3213 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3214 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3216 if (appData.zippyPlay && first.initDone) {
\r
3217 ZippyHoldings(white_holding, black_holding,
\r
3221 if (tinyLayout || smallLayout) {
\r
3222 char wh[16], bh[16];
\r
3223 PackHolding(wh, white_holding);
\r
3224 PackHolding(bh, black_holding);
\r
3225 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3226 gameInfo.white, gameInfo.black);
\r
3228 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3229 gameInfo.white, white_holding,
\r
3230 gameInfo.black, black_holding);
\r
3233 DrawPosition(FALSE, boards[currentMove]);
\r
3234 DisplayTitle(str);
\r
3236 /* Suppress following prompt */
\r
3237 if (looking_at(buf, &i, "*% ")) {
\r
3238 savingComment = FALSE;
\r
3245 i++; /* skip unparsed character and loop back */
\r
3248 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3249 started != STARTED_HOLDINGS && i > next_out) {
\r
3250 SendToPlayer(&buf[next_out], i - next_out);
\r
3253 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3255 leftover_len = buf_len - leftover_start;
\r
3256 /* if buffer ends with something we couldn't parse,
\r
3257 reparse it after appending the next read */
\r
3259 } else if (count == 0) {
\r
3260 RemoveInputSource(isr);
\r
3261 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3263 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3268 /* Board style 12 looks like this:
\r
3270 <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
3272 * The "<12> " is stripped before it gets to this routine. The two
\r
3273 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3274 * some chess servers may not have them, or may have only the first.
\r
3275 * Additional trailing fields may be added in the future.
\r
3278 #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
3280 #define RELATION_OBSERVING_PLAYED 0
\r
3281 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3282 #define RELATION_PLAYING_MYMOVE 1
\r
3283 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3284 #define RELATION_EXAMINING 2
\r
3285 #define RELATION_ISOLATED_BOARD -3
\r
3286 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3289 ParseBoard12(string)
\r
3292 GameMode newGameMode;
\r
3293 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3294 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3295 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3296 char to_play, board_chars[200];
\r
3297 char move_str[500], str[500], elapsed_time[500];
\r
3298 char black[32], white[32];
\r
3300 int prevMove = currentMove;
\r
3302 ChessMove moveType;
\r
3303 int fromX, fromY, toX, toY;
\r
3305 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3306 char *bookHit = NULL; // [HGM] book
\r
3308 fromX = fromY = toX = toY = -1;
\r
3312 if (appData.debugMode)
\r
3313 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3315 move_str[0] = NULLCHAR;
\r
3316 elapsed_time[0] = NULLCHAR;
\r
3317 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3319 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3320 if(string[i] == ' ') { ranks++; files = 0; }
\r
3324 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3325 board_chars[i] = '\0';
\r
3328 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3329 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3330 &gamenum, white, black, &relation, &basetime, &increment,
\r
3331 &white_stren, &black_stren, &white_time, &black_time,
\r
3332 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3336 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3337 DisplayError(str, 0);
\r
3341 /* Convert the move number to internal form */
\r
3342 moveNum = (moveNum - 1) * 2;
\r
3343 if (to_play == 'B') moveNum++;
\r
3344 if (moveNum >= MAX_MOVES) {
\r
3345 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3350 switch (relation) {
\r
3351 case RELATION_OBSERVING_PLAYED:
\r
3352 case RELATION_OBSERVING_STATIC:
\r
3353 if (gamenum == -1) {
\r
3354 /* Old ICC buglet */
\r
3355 relation = RELATION_OBSERVING_STATIC;
\r
3357 newGameMode = IcsObserving;
\r
3359 case RELATION_PLAYING_MYMOVE:
\r
3360 case RELATION_PLAYING_NOTMYMOVE:
\r
3362 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3363 IcsPlayingWhite : IcsPlayingBlack;
\r
3365 case RELATION_EXAMINING:
\r
3366 newGameMode = IcsExamining;
\r
3368 case RELATION_ISOLATED_BOARD:
\r
3370 /* Just display this board. If user was doing something else,
\r
3371 we will forget about it until the next board comes. */
\r
3372 newGameMode = IcsIdle;
\r
3374 case RELATION_STARTING_POSITION:
\r
3375 newGameMode = gameMode;
\r
3379 /* Modify behavior for initial board display on move listing
\r
3382 switch (ics_getting_history) {
\r
3386 case H_GOT_REQ_HEADER:
\r
3387 case H_GOT_UNREQ_HEADER:
\r
3388 /* This is the initial position of the current game */
\r
3389 gamenum = ics_gamenum;
\r
3390 moveNum = 0; /* old ICS bug workaround */
\r
3391 if (to_play == 'B') {
\r
3392 startedFromSetupPosition = TRUE;
\r
3393 blackPlaysFirst = TRUE;
\r
3395 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3396 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3397 if (currentMove == 0) currentMove = 1;
\r
3399 newGameMode = gameMode;
\r
3400 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3402 case H_GOT_UNWANTED_HEADER:
\r
3403 /* This is an initial board that we don't want */
\r
3405 case H_GETTING_MOVES:
\r
3406 /* Should not happen */
\r
3407 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3408 ics_getting_history = H_FALSE;
\r
3412 /* Take action if this is the first board of a new game, or of a
\r
3413 different game than is currently being displayed. */
\r
3414 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3415 relation == RELATION_ISOLATED_BOARD) {
\r
3417 /* Forget the old game and get the history (if any) of the new one */
\r
3418 if (gameMode != BeginningOfGame) {
\r
3419 Reset(FALSE, TRUE);
\r
3422 if (appData.autoRaiseBoard) BoardToTop();
\r
3424 if (gamenum == -1) {
\r
3425 newGameMode = IcsIdle;
\r
3426 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3427 appData.getMoveList) {
\r
3428 /* Need to get game history */
\r
3429 ics_getting_history = H_REQUESTED;
\r
3430 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3434 /* Initially flip the board to have black on the bottom if playing
\r
3435 black or if the ICS flip flag is set, but let the user change
\r
3436 it with the Flip View button. */
\r
3437 flipView = appData.autoFlipView ?
\r
3438 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3441 /* Done with values from previous mode; copy in new ones */
\r
3442 gameMode = newGameMode;
\r
3444 ics_gamenum = gamenum;
\r
3445 if (gamenum == gs_gamenum) {
\r
3446 int klen = strlen(gs_kind);
\r
3447 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3448 sprintf(str, "ICS %s", gs_kind);
\r
3449 gameInfo.event = StrSave(str);
\r
3451 gameInfo.event = StrSave("ICS game");
\r
3453 gameInfo.site = StrSave(appData.icsHost);
\r
3454 gameInfo.date = PGNDate();
\r
3455 gameInfo.round = StrSave("-");
\r
3456 gameInfo.white = StrSave(white);
\r
3457 gameInfo.black = StrSave(black);
\r
3458 timeControl = basetime * 60 * 1000;
\r
3459 timeControl_2 = 0;
\r
3460 timeIncrement = increment * 1000;
\r
3461 movesPerSession = 0;
\r
3462 gameInfo.timeControl = TimeControlTagValue();
\r
3463 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3464 if (appData.debugMode) {
\r
3465 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3466 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3467 setbuf(debugFP, NULL);
\r
3470 gameInfo.outOfBook = NULL;
\r
3472 /* Do we have the ratings? */
\r
3473 if (strcmp(player1Name, white) == 0 &&
\r
3474 strcmp(player2Name, black) == 0) {
\r
3475 if (appData.debugMode)
\r
3476 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3477 player1Rating, player2Rating);
\r
3478 gameInfo.whiteRating = player1Rating;
\r
3479 gameInfo.blackRating = player2Rating;
\r
3480 } else if (strcmp(player2Name, white) == 0 &&
\r
3481 strcmp(player1Name, black) == 0) {
\r
3482 if (appData.debugMode)
\r
3483 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3484 player2Rating, player1Rating);
\r
3485 gameInfo.whiteRating = player2Rating;
\r
3486 gameInfo.blackRating = player1Rating;
\r
3488 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3490 /* Silence shouts if requested */
\r
3491 if (appData.quietPlay &&
\r
3492 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3493 SendToICS(ics_prefix);
\r
3494 SendToICS("set shout 0\n");
\r
3498 /* Deal with midgame name changes */
\r
3500 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3501 if (gameInfo.white) free(gameInfo.white);
\r
3502 gameInfo.white = StrSave(white);
\r
3504 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3505 if (gameInfo.black) free(gameInfo.black);
\r
3506 gameInfo.black = StrSave(black);
\r
3510 /* Throw away game result if anything actually changes in examine mode */
\r
3511 if (gameMode == IcsExamining && !newGame) {
\r
3512 gameInfo.result = GameUnfinished;
\r
3513 if (gameInfo.resultDetails != NULL) {
\r
3514 free(gameInfo.resultDetails);
\r
3515 gameInfo.resultDetails = NULL;
\r
3519 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3520 in if they are in a different variation than we are. */
\r
3521 if (pauseExamInvalid) return;
\r
3522 if (pausing && gameMode == IcsExamining) {
\r
3523 if (moveNum <= pauseExamForwardMostMove) {
\r
3524 pauseExamInvalid = TRUE;
\r
3525 forwardMostMove = pauseExamForwardMostMove;
\r
3530 if (appData.debugMode) {
\r
3531 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3533 /* Parse the board */
\r
3534 for (k = 0; k < ranks; k++) {
\r
3535 for (j = 0; j < files; j++)
\r
3536 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3537 if(gameInfo.holdingsWidth > 1) {
\r
3538 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3539 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3542 CopyBoard(boards[moveNum], board);
\r
3543 if (moveNum == 0) {
\r
3544 startedFromSetupPosition =
\r
3545 !CompareBoards(board, initialPosition);
\r
3546 if(startedFromSetupPosition)
\r
3547 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3550 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3551 to make it also work for FRC opening positions. Note that board12
\r
3552 is really defective for later FRC positions, as it has no way to
\r
3553 indicate which Rook can castle if they are on the same side of King.
\r
3554 For the initial position we grant rights to the outermost Rooks,
\r
3555 and remember thos rights, and we then copy them on positions
\r
3556 later in an FRC game. This means WB might not recognize castlings with
\r
3557 Rooks that have moved back to their original position as illegal,
\r
3558 but in ICS mode that is not its job anyway.
\r
3560 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3561 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3563 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3564 if(board[0][i] == WhiteRook) j = i;
\r
3565 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3566 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3567 if(board[0][i] == WhiteRook) j = i;
\r
3568 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3569 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3570 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3571 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3572 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3573 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3574 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3576 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3577 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3578 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3579 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3580 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3581 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3583 r = castlingRights[moveNum][0] = initialRights[0];
\r
3584 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3585 r = castlingRights[moveNum][1] = initialRights[1];
\r
3586 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3587 r = castlingRights[moveNum][3] = initialRights[3];
\r
3588 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3589 r = castlingRights[moveNum][4] = initialRights[4];
\r
3590 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3591 /* wildcastle kludge: always assume King has rights */
\r
3592 r = castlingRights[moveNum][2] = initialRights[2];
\r
3593 r = castlingRights[moveNum][5] = initialRights[5];
\r
3595 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3596 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3599 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3600 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3601 /* This was an initial position from a move list, not
\r
3602 the current position */
\r
3606 /* Update currentMove and known move number limits */
\r
3607 newMove = newGame || moveNum > forwardMostMove;
\r
3609 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3610 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3611 takeback = forwardMostMove - moveNum;
\r
3612 for (i = 0; i < takeback; i++) {
\r
3613 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3614 SendToProgram("undo\n", &first);
\r
3619 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3620 if (gameMode == IcsExamining && moveNum == 0) {
\r
3621 /* Workaround for ICS limitation: we are not told the wild
\r
3622 type when starting to examine a game. But if we ask for
\r
3623 the move list, the move list header will tell us */
\r
3624 ics_getting_history = H_REQUESTED;
\r
3625 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3628 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3629 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3630 forwardMostMove = moveNum;
\r
3631 if (!pausing || currentMove > forwardMostMove)
\r
3632 currentMove = forwardMostMove;
\r
3634 /* New part of history that is not contiguous with old part */
\r
3635 if (pausing && gameMode == IcsExamining) {
\r
3636 pauseExamInvalid = TRUE;
\r
3637 forwardMostMove = pauseExamForwardMostMove;
\r
3640 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3641 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3642 ics_getting_history = H_REQUESTED;
\r
3643 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3648 /* Update the clocks */
\r
3649 if (strchr(elapsed_time, '.')) {
\r
3650 /* Time is in ms */
\r
3651 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3652 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3654 /* Time is in seconds */
\r
3655 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3656 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3661 if (appData.zippyPlay && newGame &&
\r
3662 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3663 gameMode != IcsExamining)
\r
3664 ZippyFirstBoard(moveNum, basetime, increment);
\r
3667 /* Put the move on the move list, first converting
\r
3668 to canonical algebraic form. */
\r
3669 if (moveNum > 0) {
\r
3670 if (appData.debugMode) {
\r
3671 if (appData.debugMode) { int f = forwardMostMove;
\r
3672 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3673 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3675 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3676 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3677 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3678 setbuf(debugFP, NULL);
\r
3680 if (moveNum <= backwardMostMove) {
\r
3681 /* We don't know what the board looked like before
\r
3682 this move. Punt. */
\r
3683 strcpy(parseList[moveNum - 1], move_str);
\r
3684 strcat(parseList[moveNum - 1], " ");
\r
3685 strcat(parseList[moveNum - 1], elapsed_time);
\r
3686 moveList[moveNum - 1][0] = NULLCHAR;
\r
3687 } else if (strcmp(move_str, "none") == 0) {
\r
3688 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3689 /* Again, we don't know what the board looked like;
\r
3690 this is really the start of the game. */
\r
3691 parseList[moveNum - 1][0] = NULLCHAR;
\r
3692 moveList[moveNum - 1][0] = NULLCHAR;
\r
3693 backwardMostMove = moveNum;
\r
3694 startedFromSetupPosition = TRUE;
\r
3695 fromX = fromY = toX = toY = -1;
\r
3697 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3698 // So we parse the long-algebraic move string in stead of the SAN move
\r
3699 int valid; char buf[MSG_SIZ], *prom;
\r
3701 // str looks something like "Q/a1-a2"; kill the slash
\r
3702 if(str[1] == '/')
\r
3703 sprintf(buf, "%c%s", str[0], str+2);
\r
3704 else strcpy(buf, str); // might be castling
\r
3705 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3706 strcat(buf, prom); // long move lacks promo specification!
\r
3707 if(!appData.testLegality) {
\r
3708 if(appData.debugMode)
\r
3709 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3710 strcpy(move_str, buf);
\r
3712 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3713 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3714 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3715 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3716 // end of long SAN patch
\r
3718 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3719 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3720 fromY, fromX, toY, toX, promoChar,
\r
3721 parseList[moveNum-1]);
\r
3722 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3723 castlingRights[moveNum]) ) {
\r
3725 case MT_STALEMATE:
\r
3729 if(gameInfo.variant != VariantShogi)
\r
3730 strcat(parseList[moveNum - 1], "+");
\r
3732 case MT_CHECKMATE:
\r
3733 strcat(parseList[moveNum - 1], "#");
\r
3736 strcat(parseList[moveNum - 1], " ");
\r
3737 strcat(parseList[moveNum - 1], elapsed_time);
\r
3738 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3739 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3740 strcat(moveList[moveNum - 1], "\n");
\r
3742 /* Move from ICS was illegal!? Punt. */
\r
3743 if (appData.debugMode) {
\r
3744 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3745 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3748 if (appData.testLegality && appData.debugMode) {
\r
3749 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3750 DisplayError(str, 0);
\r
3753 strcpy(parseList[moveNum - 1], move_str);
\r
3754 strcat(parseList[moveNum - 1], " ");
\r
3755 strcat(parseList[moveNum - 1], elapsed_time);
\r
3756 moveList[moveNum - 1][0] = NULLCHAR;
\r
3757 fromX = fromY = toX = toY = -1;
\r
3760 if (appData.debugMode) {
\r
3761 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3762 setbuf(debugFP, NULL);
\r
3766 /* Send move to chess program (BEFORE animating it). */
\r
3767 if (appData.zippyPlay && !newGame && newMove &&
\r
3768 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3770 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3771 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3772 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3773 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3775 DisplayError(str, 0);
\r
3777 if (first.sendTime) {
\r
3778 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3780 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3781 if (firstMove && !bookHit) {
\r
3782 firstMove = FALSE;
\r
3783 if (first.useColors) {
\r
3784 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3786 "black\ngo\n", &first);
\r
3788 SendToProgram("go\n", &first);
\r
3790 first.maybeThinking = TRUE;
\r
3793 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3794 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3795 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3796 DisplayError(str, 0);
\r
3798 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3799 SendMoveToProgram(moveNum - 1, &first);
\r
3806 if (moveNum > 0 && !gotPremove) {
\r
3807 /* If move comes from a remote source, animate it. If it
\r
3808 isn't remote, it will have already been animated. */
\r
3809 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3810 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3812 if (!pausing && appData.highlightLastMove) {
\r
3813 SetHighlights(fromX, fromY, toX, toY);
\r
3817 /* Start the clocks */
\r
3818 whiteFlag = blackFlag = FALSE;
\r
3819 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3820 if (ticking == 0) {
\r
3821 ics_clock_paused = TRUE;
\r
3823 } else if (ticking == 1) {
\r
3824 ics_clock_paused = FALSE;
\r
3826 if (gameMode == IcsIdle ||
\r
3827 relation == RELATION_OBSERVING_STATIC ||
\r
3828 relation == RELATION_EXAMINING ||
\r
3830 DisplayBothClocks();
\r
3834 /* Display opponents and material strengths */
\r
3835 if (gameInfo.variant != VariantBughouse &&
\r
3836 gameInfo.variant != VariantCrazyhouse) {
\r
3837 if (tinyLayout || smallLayout) {
\r
3838 if(gameInfo.variant == VariantNormal)
\r
3839 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3840 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3841 basetime, increment);
\r
3843 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3844 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3845 basetime, increment, (int) gameInfo.variant);
\r
3847 if(gameInfo.variant == VariantNormal)
\r
3848 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3849 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3850 basetime, increment);
\r
3852 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3853 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3854 basetime, increment, VariantName(gameInfo.variant));
\r
3856 DisplayTitle(str);
\r
3857 if (appData.debugMode) {
\r
3858 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3863 /* Display the board */
\r
3866 if (appData.premove)
\r
3867 if (!gotPremove ||
\r
3868 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3869 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3870 ClearPremoveHighlights();
\r
3872 DrawPosition(FALSE, boards[currentMove]);
\r
3873 DisplayMove(moveNum - 1);
\r
3874 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3878 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3880 if(bookHit) { // [HGM] book: simulate book reply
\r
3881 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3883 programStats.depth = programStats.nodes = programStats.time =
\r
3884 programStats.score = programStats.got_only_move = 0;
\r
3885 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3887 strcpy(bookMove, "move ");
\r
3888 strcat(bookMove, bookHit);
\r
3889 HandleMachineMove(bookMove, &first);
\r
3895 GetMoveListEvent()
\r
3897 char buf[MSG_SIZ];
\r
3898 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3899 ics_getting_history = H_REQUESTED;
\r
3900 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3906 AnalysisPeriodicEvent(force)
\r
3909 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3910 && !force) || !appData.periodicUpdates)
\r
3913 /* Send . command to Crafty to collect stats */
\r
3914 SendToProgram(".\n", &first);
\r
3916 /* Don't send another until we get a response (this makes
\r
3917 us stop sending to old Crafty's which don't understand
\r
3918 the "." command (sending illegal cmds resets node count & time,
\r
3919 which looks bad)) */
\r
3920 programStats.ok_to_send = 0;
\r
3924 SendMoveToProgram(moveNum, cps)
\r
3926 ChessProgramState *cps;
\r
3928 char buf[MSG_SIZ];
\r
3930 if (cps->useUsermove) {
\r
3931 SendToProgram("usermove ", cps);
\r
3933 if (cps->useSAN) {
\r
3935 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3936 int len = space - parseList[moveNum];
\r
3937 memcpy(buf, parseList[moveNum], len);
\r
3938 buf[len++] = '\n';
\r
3939 buf[len] = NULLCHAR;
\r
3941 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3943 SendToProgram(buf, cps);
\r
3945 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3946 AlphaRank(moveList[moveNum], 4);
\r
3947 SendToProgram(moveList[moveNum], cps);
\r
3948 AlphaRank(moveList[moveNum], 4); // and back
\r
3950 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3951 * the engine. It would be nice to have a better way to identify castle
\r
3953 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3954 && cps->useOOCastle) {
\r
3955 int fromX = moveList[moveNum][0] - AAA;
\r
3956 int fromY = moveList[moveNum][1] - ONE;
\r
3957 int toX = moveList[moveNum][2] - AAA;
\r
3958 int toY = moveList[moveNum][3] - ONE;
\r
3959 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3960 && boards[moveNum][toY][toX] == WhiteRook)
\r
3961 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3962 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3963 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3964 else SendToProgram("O-O-O\n", cps);
\r
3966 else SendToProgram(moveList[moveNum], cps);
\r
3968 else SendToProgram(moveList[moveNum], cps);
\r
3969 /* End of additions by Tord */
\r
3972 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3973 /* Send 'go' if we are in a mode where machine should play. */
\r
3974 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3975 (gameMode == TwoMachinesPlay ||
\r
3977 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3979 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3980 SendToProgram("go\n", cps);
\r
3981 if (appData.debugMode) {
\r
3982 fprintf(debugFP, "(extra)\n");
\r
3985 setboardSpoiledMachineBlack = 0;
\r
3989 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3990 ChessMove moveType;
\r
3991 int fromX, fromY, toX, toY;
\r
3993 char user_move[MSG_SIZ];
\r
3995 switch (moveType) {
\r
3997 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
3998 (int)moveType, fromX, fromY, toX, toY);
\r
3999 DisplayError(user_move + strlen("say "), 0);
\r
4001 case WhiteKingSideCastle:
\r
4002 case BlackKingSideCastle:
\r
4003 case WhiteQueenSideCastleWild:
\r
4004 case BlackQueenSideCastleWild:
\r
4006 case WhiteHSideCastleFR:
\r
4007 case BlackHSideCastleFR:
\r
4009 sprintf(user_move, "o-o\n");
\r
4011 case WhiteQueenSideCastle:
\r
4012 case BlackQueenSideCastle:
\r
4013 case WhiteKingSideCastleWild:
\r
4014 case BlackKingSideCastleWild:
\r
4016 case WhiteASideCastleFR:
\r
4017 case BlackASideCastleFR:
\r
4019 sprintf(user_move, "o-o-o\n");
\r
4021 case WhitePromotionQueen:
\r
4022 case BlackPromotionQueen:
\r
4023 case WhitePromotionRook:
\r
4024 case BlackPromotionRook:
\r
4025 case WhitePromotionBishop:
\r
4026 case BlackPromotionBishop:
\r
4027 case WhitePromotionKnight:
\r
4028 case BlackPromotionKnight:
\r
4029 case WhitePromotionKing:
\r
4030 case BlackPromotionKing:
\r
4031 case WhitePromotionChancellor:
\r
4032 case BlackPromotionChancellor:
\r
4033 case WhitePromotionArchbishop:
\r
4034 case BlackPromotionArchbishop:
\r
4035 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4036 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4037 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4038 PieceToChar(WhiteFerz));
\r
4039 else if(gameInfo.variant == VariantGreat)
\r
4040 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4041 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4042 PieceToChar(WhiteMan));
\r
4044 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4045 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4046 PieceToChar(PromoPiece(moveType)));
\r
4050 sprintf(user_move, "%c@%c%c\n",
\r
4051 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4052 AAA + toX, ONE + toY);
\r
4055 case WhiteCapturesEnPassant:
\r
4056 case BlackCapturesEnPassant:
\r
4057 case IllegalMove: /* could be a variant we don't quite understand */
\r
4058 sprintf(user_move, "%c%c%c%c\n",
\r
4059 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4062 SendToICS(user_move);
\r
4066 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4067 int rf, ff, rt, ft;
\r
4071 if (rf == DROP_RANK) {
\r
4072 sprintf(move, "%c@%c%c\n",
\r
4073 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4075 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4076 sprintf(move, "%c%c%c%c\n",
\r
4077 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4079 sprintf(move, "%c%c%c%c%c\n",
\r
4080 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4086 ProcessICSInitScript(f)
\r
4089 char buf[MSG_SIZ];
\r
4091 while (fgets(buf, MSG_SIZ, f)) {
\r
4092 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4099 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4101 AlphaRank(char *move, int n)
\r
4103 char *p = move, c; int x, y;
\r
4105 if (appData.debugMode) {
\r
4106 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4109 if(move[1]=='*' &&
\r
4110 move[2]>='0' && move[2]<='9' &&
\r
4111 move[3]>='a' && move[3]<='x' ) {
\r
4113 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4114 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4116 if(move[0]>='0' && move[0]<='9' &&
\r
4117 move[1]>='a' && move[1]<='x' &&
\r
4118 move[2]>='0' && move[2]<='9' &&
\r
4119 move[3]>='a' && move[3]<='x' ) {
\r
4120 /* input move, Shogi -> normal */
\r
4121 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4122 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4123 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4124 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4126 if(move[1]=='@' &&
\r
4127 move[3]>='0' && move[3]<='9' &&
\r
4128 move[2]>='a' && move[2]<='x' ) {
\r
4130 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4131 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4134 move[0]>='a' && move[0]<='x' &&
\r
4135 move[3]>='0' && move[3]<='9' &&
\r
4136 move[2]>='a' && move[2]<='x' ) {
\r
4137 /* output move, normal -> Shogi */
\r
4138 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4139 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4140 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4141 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4142 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4144 if (appData.debugMode) {
\r
4145 fprintf(debugFP, " out = '%s'\n", move);
\r
4149 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4151 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4154 ChessMove *moveType;
\r
4155 int *fromX, *fromY, *toX, *toY;
\r
4158 if (appData.debugMode) {
\r
4159 fprintf(debugFP, "move to parse: %s\n", move);
\r
4161 *moveType = yylexstr(moveNum, move);
\r
4163 switch (*moveType) {
\r
4164 case WhitePromotionChancellor:
\r
4165 case BlackPromotionChancellor:
\r
4166 case WhitePromotionArchbishop:
\r
4167 case BlackPromotionArchbishop:
\r
4168 case WhitePromotionQueen:
\r
4169 case BlackPromotionQueen:
\r
4170 case WhitePromotionRook:
\r
4171 case BlackPromotionRook:
\r
4172 case WhitePromotionBishop:
\r
4173 case BlackPromotionBishop:
\r
4174 case WhitePromotionKnight:
\r
4175 case BlackPromotionKnight:
\r
4176 case WhitePromotionKing:
\r
4177 case BlackPromotionKing:
\r
4179 case WhiteCapturesEnPassant:
\r
4180 case BlackCapturesEnPassant:
\r
4181 case WhiteKingSideCastle:
\r
4182 case WhiteQueenSideCastle:
\r
4183 case BlackKingSideCastle:
\r
4184 case BlackQueenSideCastle:
\r
4185 case WhiteKingSideCastleWild:
\r
4186 case WhiteQueenSideCastleWild:
\r
4187 case BlackKingSideCastleWild:
\r
4188 case BlackQueenSideCastleWild:
\r
4189 /* Code added by Tord: */
\r
4190 case WhiteHSideCastleFR:
\r
4191 case WhiteASideCastleFR:
\r
4192 case BlackHSideCastleFR:
\r
4193 case BlackASideCastleFR:
\r
4194 /* End of code added by Tord */
\r
4195 case IllegalMove: /* bug or odd chess variant */
\r
4196 *fromX = currentMoveString[0] - AAA;
\r
4197 *fromY = currentMoveString[1] - ONE;
\r
4198 *toX = currentMoveString[2] - AAA;
\r
4199 *toY = currentMoveString[3] - ONE;
\r
4200 *promoChar = currentMoveString[4];
\r
4201 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4202 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4203 if (appData.debugMode) {
\r
4204 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4206 *fromX = *fromY = *toX = *toY = 0;
\r
4209 if (appData.testLegality) {
\r
4210 return (*moveType != IllegalMove);
\r
4212 return !(fromX == fromY && toX == toY);
\r
4217 *fromX = *moveType == WhiteDrop ?
\r
4218 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4219 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4220 *fromY = DROP_RANK;
\r
4221 *toX = currentMoveString[2] - AAA;
\r
4222 *toY = currentMoveString[3] - ONE;
\r
4223 *promoChar = NULLCHAR;
\r
4226 case AmbiguousMove:
\r
4227 case ImpossibleMove:
\r
4228 case (ChessMove) 0: /* end of file */
\r
4237 if (appData.debugMode) {
\r
4238 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4241 *fromX = *fromY = *toX = *toY = 0;
\r
4242 *promoChar = NULLCHAR;
\r
4247 /* [AS] FRC game initialization */
\r
4248 static int FindEmptySquare( Board board, int n )
\r
4253 while( board[0][i] != EmptySquare ) i++;
\r
4264 static void ShuffleFRC( Board board )
\r
4270 for( i=0; i<8; i++ ) {
\r
4271 board[0][i] = EmptySquare;
\r
4274 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4275 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4276 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4277 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4278 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4279 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4280 initialRights[1] = initialRights[4] =
\r
4281 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4282 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4283 initialRights[2] = initialRights[5] =
\r
4284 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4285 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4286 initialRights[0] = initialRights[3] =
\r
4287 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4289 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4290 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4294 static unsigned char FRC_KnightTable[10] = {
\r
4295 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4298 static void SetupFRC( Board board, int pos_index )
\r
4301 unsigned char knights;
\r
4303 /* Bring the position index into a safe range (just in case...) */
\r
4304 if( pos_index < 0 ) pos_index = 0;
\r
4308 /* Clear the board */
\r
4309 for( i=0; i<8; i++ ) {
\r
4310 board[0][i] = EmptySquare;
\r
4313 /* Place bishops and queen */
\r
4314 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4317 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4320 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4323 /* Place knigths */
\r
4324 knights = FRC_KnightTable[ pos_index ];
\r
4326 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4327 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4329 /* Place rooks and king */
\r
4330 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4331 initialRights[1] = initialRights[4] =
\r
4332 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4333 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4334 initialRights[2] = initialRights[5] =
\r
4335 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4336 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4337 initialRights[0] = initialRights[3] =
\r
4338 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4340 /* Mirror piece placement for black */
\r
4341 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4342 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4346 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4347 // All positions will have equal probability, but the current method will not provide a unique
\r
4348 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4353 int squaresLeft[4];
\r
4354 int piecesLeft[(int)BlackPawn];
\r
4355 long long int seed, nrOfShuffles;
\r
4357 void GetPositionNumber()
\r
4358 { // sets global variable seed
\r
4361 seed = appData.defaultFrcPosition;
\r
4362 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4363 srandom(time(0));
\r
4364 for(i=0; i<50; i++) seed += random();
\r
4365 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4366 if(seed<0) seed = -seed;
\r
4370 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4371 // put the piece on the (n-1)-th empty squares of the given shade
\r
4375 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4376 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4377 board[rank][i] = (ChessSquare) pieceType;
\r
4378 squaresLeft[(i-BOARD_LEFT&1) + 1]--;
\r
4379 squaresLeft[ANY]--;
\r
4380 piecesLeft[pieceType]--;
\r
4388 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4389 // calculate where the next piece goes, (any empty square), and put it there
\r
4393 i = seed % squaresLeft[shade];
\r
4394 nrOfShuffles *= squaresLeft[shade];
\r
4395 seed /= squaresLeft[shade];
\r
4396 put(board, pieceType, rank, i, shade);
\r
4399 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4400 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4402 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4404 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4405 i = seed % k; // pick one
\r
4406 nrOfShuffles *= k;
\r
4408 while(i >= j) i -= j--;
\r
4409 j = n - 1 - j; i += j;
\r
4410 put(board, pieceType, rank, j, ANY);
\r
4411 put(board, pieceType, rank, i, ANY);
\r
4414 void SetUpShuffle(Board board, int number)
\r
4416 int i, p, first=1;
\r
4418 GetPositionNumber(); nrOfShuffles = 1;
\r
4420 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4421 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4422 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4424 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4426 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4427 p = (int) board[0][i];
\r
4428 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4429 board[0][i] = EmptySquare;
\r
4432 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4433 // shuffles restricted to allow normal castling put KRR first
\r
4434 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4435 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4436 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4437 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4438 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4439 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4440 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4441 put(board, WhiteRook, 0, 0, ANY);
\r
4442 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4445 if((BOARD_RGHT-BOARD_LEFT & 1) == 0)
\r
4446 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4447 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4448 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4449 while(piecesLeft[p] >= 2) {
\r
4450 AddOnePiece(board, p, 0, LITE);
\r
4451 AddOnePiece(board, p, 0, DARK);
\r
4453 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4456 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4457 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4458 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4459 if(p == (int)WhiteRook) continue;
\r
4460 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4461 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4464 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4466 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4467 // Last King gets castling rights
\r
4468 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4469 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4470 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4473 while(piecesLeft[(int)WhiteKing]) {
\r
4474 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4475 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4480 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4481 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4484 // Only Rooks can be left; simply place them all
\r
4485 while(piecesLeft[(int)WhiteRook]) {
\r
4486 i = put(board, WhiteRook, 0, 0, ANY);
\r
4487 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4490 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4492 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4495 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4496 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4499 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4504 int SetCharTable( char *table, const char * map )
\r
4505 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4506 /* Basically a safe strcpy that uses the last character as King */
\r
4508 int result = FALSE; int NrPieces;
\r
4510 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4511 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4512 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4514 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4515 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4516 table[i] = map[i];
\r
4517 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4519 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4520 table[(int) BlackKing] = map[NrPieces-1];
\r
4528 void Prelude(Board board)
\r
4529 { // [HGM] superchess: random selection of exo-pieces
\r
4530 int i, j, k; ChessSquare p;
\r
4531 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4533 GetPositionNumber(); // use FRC position number
\r
4535 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4536 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4537 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4538 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4541 j = seed%4; seed /= 4;
\r
4542 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4543 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4544 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4545 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4546 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4547 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4548 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4549 j = seed%3; seed /= 3;
\r
4550 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4551 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4552 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4553 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4554 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4555 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4556 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4557 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4558 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4559 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4560 put(board, exoPieces[0], 0, 0, ANY);
\r
4561 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4565 InitPosition(redraw)
\r
4568 ChessSquare (* pieces)[BOARD_SIZE];
\r
4569 int i, j, pawnRow, overrule,
\r
4570 oldx = gameInfo.boardWidth,
\r
4571 oldy = gameInfo.boardHeight,
\r
4572 oldh = gameInfo.holdingsWidth,
\r
4573 oldv = gameInfo.variant;
\r
4575 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4576 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4578 /* [AS] Initialize pv info list [HGM] and game status */
\r
4580 for( i=0; i<MAX_MOVES; i++ ) {
\r
4581 pvInfoList[i].depth = 0;
\r
4582 epStatus[i]=EP_NONE;
\r
4583 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4586 initialRulePlies = 0; /* 50-move counter start */
\r
4588 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4589 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4593 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4594 /* the initialized data only consist of the two backranks. The switch */
\r
4595 /* selects which one we will use, which is than copied to the Board */
\r
4596 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4597 /* empty squares. This initial position is then copied to boards[0], */
\r
4598 /* possibly after shuffling, so that it remains available. */
\r
4600 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4601 gameInfo.boardWidth = 8;
\r
4602 gameInfo.boardHeight = 8;
\r
4603 gameInfo.holdingsSize = 0;
\r
4604 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4605 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4606 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4608 switch (gameInfo.variant) {
\r
4609 case VariantFischeRandom:
\r
4610 shuffleOpenings = TRUE;
\r
4612 pieces = FIDEArray;
\r
4614 case VariantShatranj:
\r
4615 pieces = ShatranjArray;
\r
4616 nrCastlingRights = 0;
\r
4617 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4619 case VariantTwoKings:
\r
4620 pieces = twoKingsArray;
\r
4621 nrCastlingRights = 8; /* add rights for second King */
\r
4622 castlingRights[0][6] = initialRights[2] = 5;
\r
4623 castlingRights[0][7] = initialRights[5] = 5;
\r
4624 castlingRank[6] = 0;
\r
4625 castlingRank[7] = BOARD_HEIGHT-1;
\r
4627 case VariantCapaRandom:
\r
4628 shuffleOpenings = TRUE;
\r
4629 case VariantCapablanca:
\r
4630 pieces = CapablancaArray;
\r
4631 gameInfo.boardWidth = 10;
\r
4632 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4634 case VariantGothic:
\r
4635 pieces = GothicArray;
\r
4636 gameInfo.boardWidth = 10;
\r
4637 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4639 case VariantJanus:
\r
4640 pieces = JanusArray;
\r
4641 gameInfo.boardWidth = 10;
\r
4642 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4643 nrCastlingRights = 6;
\r
4644 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4645 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4646 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;
\r
4647 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4648 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4649 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;
\r
4651 case VariantFalcon:
\r
4652 pieces = FalconArray;
\r
4653 gameInfo.boardWidth = 10;
\r
4654 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4656 case VariantXiangqi:
\r
4657 pieces = XiangqiArray;
\r
4658 gameInfo.boardWidth = 9;
\r
4659 gameInfo.boardHeight = 10;
\r
4660 nrCastlingRights = 0;
\r
4661 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4663 case VariantShogi:
\r
4664 pieces = ShogiArray;
\r
4665 gameInfo.boardWidth = 9;
\r
4666 gameInfo.boardHeight = 9;
\r
4667 gameInfo.holdingsSize = 7;
\r
4668 nrCastlingRights = 0;
\r
4669 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4671 case VariantCourier:
\r
4672 pieces = CourierArray;
\r
4673 gameInfo.boardWidth = 12;
\r
4674 nrCastlingRights = 0;
\r
4675 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4676 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4678 case VariantKnightmate:
\r
4679 pieces = KnightmateArray;
\r
4680 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4682 case VariantFairy:
\r
4683 pieces = fairyArray;
\r
4684 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4686 case VariantGreat:
\r
4687 pieces = GreatArray;
\r
4688 gameInfo.boardWidth = 10;
\r
4689 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4690 gameInfo.holdingsSize = 8;
\r
4692 case VariantSuper:
\r
4693 pieces = FIDEArray;
\r
4694 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4695 gameInfo.holdingsSize = 8;
\r
4696 startedFromSetupPosition = TRUE;
\r
4698 case VariantCrazyhouse:
\r
4699 case VariantBughouse:
\r
4700 pieces = FIDEArray;
\r
4701 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4702 gameInfo.holdingsSize = 5;
\r
4704 case VariantWildCastle:
\r
4705 pieces = FIDEArray;
\r
4706 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4707 shuffleOpenings = 1;
\r
4709 case VariantNoCastle:
\r
4710 pieces = FIDEArray;
\r
4711 nrCastlingRights = 0;
\r
4712 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4713 /* !!?unconstrained back-rank shuffle */
\r
4714 shuffleOpenings = 1;
\r
4719 if(appData.NrFiles >= 0) {
\r
4720 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4721 gameInfo.boardWidth = appData.NrFiles;
\r
4723 if(appData.NrRanks >= 0) {
\r
4724 gameInfo.boardHeight = appData.NrRanks;
\r
4726 if(appData.holdingsSize >= 0) {
\r
4727 i = appData.holdingsSize;
\r
4728 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4729 gameInfo.holdingsSize = i;
\r
4731 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4732 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4733 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4735 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4736 if(pawnRow < 1) pawnRow = 1;
\r
4738 /* User pieceToChar list overrules defaults */
\r
4739 if(appData.pieceToCharTable != NULL)
\r
4740 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4742 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4744 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4745 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4746 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4747 initialPosition[i][j] = s;
\r
4749 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4750 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4751 initialPosition[pawnRow][j] = WhitePawn;
\r
4752 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4753 if(gameInfo.variant == VariantXiangqi) {
\r
4755 initialPosition[pawnRow][j] =
\r
4756 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4757 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4758 initialPosition[2][j] = WhiteCannon;
\r
4759 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4763 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4765 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4768 initialPosition[1][j] = WhiteBishop;
\r
4769 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4771 initialPosition[1][j] = WhiteRook;
\r
4772 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4775 if( nrCastlingRights == -1) {
\r
4776 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4777 /* This sets default castling rights from none to normal corners */
\r
4778 /* Variants with other castling rights must set them themselves above */
\r
4779 nrCastlingRights = 6;
\r
4781 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4782 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4783 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4784 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4785 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4786 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4789 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4790 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4791 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;
\r
4792 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;
\r
4793 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4794 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4797 if(gameInfo.variant == VariantFischeRandom) {
\r
4798 if( appData.defaultFrcPosition < 0 ) {
\r
4799 ShuffleFRC( initialPosition );
\r
4802 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4804 startedFromSetupPosition = TRUE;
\r
4807 if (appData.debugMode) {
\r
4808 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4810 if(shuffleOpenings) {
\r
4811 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4812 startedFromSetupPosition = TRUE;
\r
4815 if(startedFromPositionFile) {
\r
4816 /* [HGM] loadPos: use PositionFile for every new game */
\r
4817 CopyBoard(initialPosition, filePosition);
\r
4818 for(i=0; i<nrCastlingRights; i++)
\r
4819 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4820 startedFromSetupPosition = TRUE;
\r
4823 CopyBoard(boards[0], initialPosition);
\r
4825 if(oldx != gameInfo.boardWidth ||
\r
4826 oldy != gameInfo.boardHeight ||
\r
4827 oldh != gameInfo.holdingsWidth
\r
4829 || oldv == VariantGothic || // For licensing popups
\r
4830 gameInfo.variant == VariantGothic
\r
4833 || oldv == VariantFalcon ||
\r
4834 gameInfo.variant == VariantFalcon
\r
4837 InitDrawingSizes(-2 ,0);
\r
4840 DrawPosition(TRUE, boards[currentMove]);
\r
4844 SendBoard(cps, moveNum)
\r
4845 ChessProgramState *cps;
\r
4848 char message[MSG_SIZ];
\r
4850 if (cps->useSetboard) {
\r
4851 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4852 sprintf(message, "setboard %s\n", fen);
\r
4853 SendToProgram(message, cps);
\r
4859 /* Kludge to set black to move, avoiding the troublesome and now
\r
4860 * deprecated "black" command.
\r
4862 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4864 SendToProgram("edit\n", cps);
\r
4865 SendToProgram("#\n", cps);
\r
4866 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4867 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4868 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4869 if ((int) *bp < (int) BlackPawn) {
\r
4870 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4871 AAA + j, ONE + i);
\r
4872 if(message[0] == '+' || message[0] == '~') {
\r
4873 sprintf(message, "%c%c%c+\n",
\r
4874 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4875 AAA + j, ONE + i);
\r
4877 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4878 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4879 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4881 SendToProgram(message, cps);
\r
4886 SendToProgram("c\n", cps);
\r
4887 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4888 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4889 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4890 if (((int) *bp != (int) EmptySquare)
\r
4891 && ((int) *bp >= (int) BlackPawn)) {
\r
4892 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4893 AAA + j, ONE + i);
\r
4894 if(message[0] == '+' || message[0] == '~') {
\r
4895 sprintf(message, "%c%c%c+\n",
\r
4896 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4897 AAA + j, ONE + i);
\r
4899 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4900 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4901 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4903 SendToProgram(message, cps);
\r
4908 SendToProgram(".\n", cps);
\r
4910 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4914 IsPromotion(fromX, fromY, toX, toY)
\r
4915 int fromX, fromY, toX, toY;
\r
4917 /* [HGM] add Shogi promotions */
\r
4918 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4919 ChessSquare piece;
\r
4921 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4922 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4923 /* [HGM] Note to self: line above also weeds out drops */
\r
4924 piece = boards[currentMove][fromY][fromX];
\r
4925 if(gameInfo.variant == VariantShogi) {
\r
4926 promotionZoneSize = 3;
\r
4927 highestPromotingPiece = (int)WhiteKing;
\r
4928 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4929 and if in normal chess we then allow promotion to King, why not
\r
4930 allow promotion of other piece in Shogi? */
\r
4932 if((int)piece >= BlackPawn) {
\r
4933 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4935 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4937 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4938 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4940 return ( (int)piece <= highestPromotingPiece );
\r
4944 InPalace(row, column)
\r
4946 { /* [HGM] for Xiangqi */
\r
4947 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4948 column < (BOARD_WIDTH + 4)/2 &&
\r
4949 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4954 PieceForSquare (x, y)
\r
4958 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4961 return boards[currentMove][y][x];
\r
4965 OKToStartUserMove(x, y)
\r
4968 ChessSquare from_piece;
\r
4971 if (matchMode) return FALSE;
\r
4972 if (gameMode == EditPosition) return TRUE;
\r
4974 if (x >= 0 && y >= 0)
\r
4975 from_piece = boards[currentMove][y][x];
\r
4977 from_piece = EmptySquare;
\r
4979 if (from_piece == EmptySquare) return FALSE;
\r
4981 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4982 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4984 switch (gameMode) {
\r
4985 case PlayFromGameFile:
\r
4987 case TwoMachinesPlay:
\r
4991 case IcsObserving:
\r
4995 case MachinePlaysWhite:
\r
4996 case IcsPlayingBlack:
\r
4997 if (appData.zippyPlay) return FALSE;
\r
4998 if (white_piece) {
\r
4999 DisplayMoveError(_("You are playing Black"));
\r
5004 case MachinePlaysBlack:
\r
5005 case IcsPlayingWhite:
\r
5006 if (appData.zippyPlay) return FALSE;
\r
5007 if (!white_piece) {
\r
5008 DisplayMoveError(_("You are playing White"));
\r
5014 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5015 DisplayMoveError(_("It is White's turn"));
\r
5018 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5019 DisplayMoveError(_("It is Black's turn"));
\r
5022 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
5023 /* Editing correspondence game history */
\r
5024 /* Could disallow this or prompt for confirmation */
\r
5025 cmailOldMove = -1;
\r
5027 if (currentMove < forwardMostMove) {
\r
5028 /* Discarding moves */
\r
5029 /* Could prompt for confirmation here,
\r
5030 but I don't think that's such a good idea */
\r
5031 forwardMostMove = currentMove;
\r
5035 case BeginningOfGame:
\r
5036 if (appData.icsActive) return FALSE;
\r
5037 if (!appData.noChessProgram) {
\r
5038 if (!white_piece) {
\r
5039 DisplayMoveError(_("You are playing White"));
\r
5046 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5047 DisplayMoveError(_("It is White's turn"));
\r
5050 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5051 DisplayMoveError(_("It is Black's turn"));
\r
5057 case IcsExamining:
\r
5060 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
5061 && gameMode != AnalyzeFile && gameMode != Training) {
\r
5062 DisplayMoveError(_("Displayed position is not current"));
\r
5068 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
5069 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
5070 int lastLoadGameUseList = FALSE;
\r
5071 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
5072 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
5076 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
5077 int fromX, fromY, toX, toY;
\r
5080 ChessMove moveType;
\r
5081 ChessSquare pdown, pup;
\r
5083 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
5084 if ((fromX == toX) && (fromY == toY)) {
\r
5085 return ImpossibleMove;
\r
5088 /* [HGM] suppress all moves into holdings area and guard band */
\r
5089 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5090 return ImpossibleMove;
\r
5092 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5093 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5094 pdown = boards[currentMove][fromY][fromX];
\r
5095 pup = boards[currentMove][toY][toX];
\r
5096 if ( gameMode != EditPosition &&
\r
5097 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5098 WhitePawn <= pup && pup < BlackPawn ||
\r
5099 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5100 BlackPawn <= pup && pup < EmptySquare
\r
5101 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5102 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5103 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5105 return ImpossibleMove;
\r
5107 /* Check if the user is playing in turn. This is complicated because we
\r
5108 let the user "pick up" a piece before it is his turn. So the piece he
\r
5109 tried to pick up may have been captured by the time he puts it down!
\r
5110 Therefore we use the color the user is supposed to be playing in this
\r
5111 test, not the color of the piece that is currently on the starting
\r
5112 square---except in EditGame mode, where the user is playing both
\r
5113 sides; fortunately there the capture race can't happen. (It can
\r
5114 now happen in IcsExamining mode, but that's just too bad. The user
\r
5115 will get a somewhat confusing message in that case.)
\r
5118 switch (gameMode) {
\r
5119 case PlayFromGameFile:
\r
5121 case TwoMachinesPlay:
\r
5123 case IcsObserving:
\r
5125 /* We switched into a game mode where moves are not accepted,
\r
5126 perhaps while the mouse button was down. */
\r
5127 return ImpossibleMove;
\r
5129 case MachinePlaysWhite:
\r
5130 /* User is moving for Black */
\r
5131 if (WhiteOnMove(currentMove)) {
\r
5132 DisplayMoveError(_("It is White's turn"));
\r
5133 return ImpossibleMove;
\r
5137 case MachinePlaysBlack:
\r
5138 /* User is moving for White */
\r
5139 if (!WhiteOnMove(currentMove)) {
\r
5140 DisplayMoveError(_("It is Black's turn"));
\r
5141 return ImpossibleMove;
\r
5146 case IcsExamining:
\r
5147 case BeginningOfGame:
\r
5150 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5151 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5152 /* User is moving for Black */
\r
5153 if (WhiteOnMove(currentMove)) {
\r
5154 DisplayMoveError(_("It is White's turn"));
\r
5155 return ImpossibleMove;
\r
5158 /* User is moving for White */
\r
5159 if (!WhiteOnMove(currentMove)) {
\r
5160 DisplayMoveError(_("It is Black's turn"));
\r
5161 return ImpossibleMove;
\r
5166 case IcsPlayingBlack:
\r
5167 /* User is moving for Black */
\r
5168 if (WhiteOnMove(currentMove)) {
\r
5169 if (!appData.premove) {
\r
5170 DisplayMoveError(_("It is White's turn"));
\r
5171 } else if (toX >= 0 && toY >= 0) {
\r
5174 premoveFromX = fromX;
\r
5175 premoveFromY = fromY;
\r
5176 premovePromoChar = promoChar;
\r
5178 if (appData.debugMode)
\r
5179 fprintf(debugFP, "Got premove: fromX %d,"
\r
5180 "fromY %d, toX %d, toY %d\n",
\r
5181 fromX, fromY, toX, toY);
\r
5183 return ImpossibleMove;
\r
5187 case IcsPlayingWhite:
\r
5188 /* User is moving for White */
\r
5189 if (!WhiteOnMove(currentMove)) {
\r
5190 if (!appData.premove) {
\r
5191 DisplayMoveError(_("It is Black's turn"));
\r
5192 } else if (toX >= 0 && toY >= 0) {
\r
5195 premoveFromX = fromX;
\r
5196 premoveFromY = fromY;
\r
5197 premovePromoChar = promoChar;
\r
5199 if (appData.debugMode)
\r
5200 fprintf(debugFP, "Got premove: fromX %d,"
\r
5201 "fromY %d, toX %d, toY %d\n",
\r
5202 fromX, fromY, toX, toY);
\r
5204 return ImpossibleMove;
\r
5211 case EditPosition:
\r
5212 /* EditPosition, empty square, or different color piece;
\r
5213 click-click move is possible */
\r
5214 if (toX == -2 || toY == -2) {
\r
5215 boards[0][fromY][fromX] = EmptySquare;
\r
5216 return AmbiguousMove;
\r
5217 } else if (toX >= 0 && toY >= 0) {
\r
5218 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5219 boards[0][fromY][fromX] = EmptySquare;
\r
5220 return AmbiguousMove;
\r
5222 return ImpossibleMove;
\r
5225 /* [HGM] If move started in holdings, it means a drop */
\r
5226 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5227 if( pup != EmptySquare ) return ImpossibleMove;
\r
5228 if(appData.testLegality) {
\r
5229 /* it would be more logical if LegalityTest() also figured out
\r
5230 * which drops are legal. For now we forbid pawns on back rank.
\r
5231 * Shogi is on its own here...
\r
5233 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5234 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5235 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5237 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5240 userOfferedDraw = FALSE;
\r
5242 /* [HGM] always test for legality, to get promotion info */
\r
5243 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5244 epStatus[currentMove], castlingRights[currentMove],
\r
5245 fromY, fromX, toY, toX, promoChar);
\r
5247 /* [HGM] but possibly ignore an IllegalMove result */
\r
5248 if (appData.testLegality) {
\r
5249 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5250 DisplayMoveError(_("Illegal move"));
\r
5251 return ImpossibleMove;
\r
5254 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5256 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5257 function is made into one that returns an OK move type if FinishMove
\r
5258 should be called. This to give the calling driver routine the
\r
5259 opportunity to finish the userMove input with a promotion popup,
\r
5260 without bothering the user with this for invalid or illegal moves */
\r
5262 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5265 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5267 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5268 ChessMove moveType;
\r
5269 int fromX, fromY, toX, toY;
\r
5270 /*char*/int promoChar;
\r
5272 char *bookHit = 0;
\r
5273 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5274 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5275 // [HGM] superchess: suppress promotions to non-available piece
\r
5276 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5277 if(WhiteOnMove(currentMove)) {
\r
5278 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5280 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5284 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5285 move type in caller when we know the move is a legal promotion */
\r
5286 if(moveType == NormalMove && promoChar)
\r
5287 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5288 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5289 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5290 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5291 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5292 fromX = boards[currentMove][fromY][fromX];
\r
5293 fromY = DROP_RANK;
\r
5296 /* [HGM] <popupFix> The following if has been moved here from
\r
5297 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5298 piece drops in training games?), and because it can only be
\r
5299 performed after it is known to what we promote. */
\r
5300 if (gameMode == Training) {
\r
5301 /* compare the move played on the board to the next move in the
\r
5302 * game. If they match, display the move and the opponent's response.
\r
5303 * If they don't match, display an error message.
\r
5307 CopyBoard(testBoard, boards[currentMove]);
\r
5308 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5310 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5311 ForwardInner(currentMove+1);
\r
5313 /* Autoplay the opponent's response.
\r
5314 * if appData.animate was TRUE when Training mode was entered,
\r
5315 * the response will be animated.
\r
5317 saveAnimate = appData.animate;
\r
5318 appData.animate = animateTraining;
\r
5319 ForwardInner(currentMove+1);
\r
5320 appData.animate = saveAnimate;
\r
5322 /* check for the end of the game */
\r
5323 if (currentMove >= forwardMostMove) {
\r
5324 gameMode = PlayFromGameFile;
\r
5326 SetTrainingModeOff();
\r
5327 DisplayInformation(_("End of game"));
\r
5330 DisplayError(_("Incorrect move"), 0);
\r
5335 /* Ok, now we know that the move is good, so we can kill
\r
5336 the previous line in Analysis Mode */
\r
5337 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5338 forwardMostMove = currentMove;
\r
5341 /* If we need the chess program but it's dead, restart it */
\r
5342 ResurrectChessProgram();
\r
5344 /* A user move restarts a paused game*/
\r
5348 thinkOutput[0] = NULLCHAR;
\r
5350 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5352 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5353 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5354 // [HGM] superchess: take promotion piece out of holdings
\r
5355 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5356 if(WhiteOnMove(forwardMostMove-1)) {
\r
5357 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5358 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5360 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5361 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5365 if (gameMode == BeginningOfGame) {
\r
5366 if (appData.noChessProgram) {
\r
5367 gameMode = EditGame;
\r
5370 char buf[MSG_SIZ];
\r
5371 gameMode = MachinePlaysBlack;
\r
5374 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5375 DisplayTitle(buf);
\r
5376 if (first.sendName) {
\r
5377 sprintf(buf, "name %s\n", gameInfo.white);
\r
5378 SendToProgram(buf, &first);
\r
5384 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5385 /* Relay move to ICS or chess engine */
\r
5386 if (appData.icsActive) {
\r
5387 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5388 gameMode == IcsExamining) {
\r
5389 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5390 ics_user_moved = 1;
\r
5393 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5394 gameMode == MachinePlaysWhite ||
\r
5395 gameMode == MachinePlaysBlack)) {
\r
5396 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5398 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5399 // [HGM] book: if program might be playing, let it use book
\r
5400 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5401 first.maybeThinking = TRUE;
\r
5402 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5403 if (currentMove == cmailOldMove + 1) {
\r
5404 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5408 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5410 switch (gameMode) {
\r
5412 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5413 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5417 case MT_CHECKMATE:
\r
5418 if (WhiteOnMove(currentMove)) {
\r
5419 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5421 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5424 case MT_STALEMATE:
\r
5425 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5430 case MachinePlaysBlack:
\r
5431 case MachinePlaysWhite:
\r
5432 /* disable certain menu options while machine is thinking */
\r
5433 SetMachineThinkingEnables();
\r
5440 if(bookHit) { // [HGM] book: simulate book reply
\r
5441 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5443 programStats.depth = programStats.nodes = programStats.time =
\r
5444 programStats.score = programStats.got_only_move = 0;
\r
5445 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5447 strcpy(bookMove, "move ");
\r
5448 strcat(bookMove, bookHit);
\r
5449 HandleMachineMove(bookMove, &first);
\r
5455 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5456 int fromX, fromY, toX, toY;
\r
5459 /* [HGM] This routine was added to allow calling of its two logical
\r
5460 parts from other modules in the old way. Before, UserMoveEvent()
\r
5461 automatically called FinishMove() if the move was OK, and returned
\r
5462 otherwise. I separated the two, in order to make it possible to
\r
5463 slip a promotion popup in between. But that it always needs two
\r
5464 calls, to the first part, (now called UserMoveTest() ), and to
\r
5465 FinishMove if the first part succeeded. Calls that do not need
\r
5466 to do anything in between, can call this routine the old way.
\r
5468 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5469 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5470 if(moveType != ImpossibleMove)
\r
5471 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5474 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5476 char * hint = lastHint;
\r
5477 FrontEndProgramStats stats;
\r
5479 stats.which = cps == &first ? 0 : 1;
\r
5480 stats.depth = cpstats->depth;
\r
5481 stats.nodes = cpstats->nodes;
\r
5482 stats.score = cpstats->score;
\r
5483 stats.time = cpstats->time;
\r
5484 stats.pv = cpstats->movelist;
\r
5485 stats.hint = lastHint;
\r
5486 stats.an_move_index = 0;
\r
5487 stats.an_move_count = 0;
\r
5489 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5490 stats.hint = cpstats->move_name;
\r
5491 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5492 stats.an_move_count = cpstats->nr_moves;
\r
5495 SetProgramStats( &stats );
\r
5498 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5499 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5500 char *bookHit = NULL;
\r
5502 //first determine if the incoming move brings opponent into his book
\r
5503 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5504 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5505 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5506 if(bookHit != NULL && !cps->bookSuspend) {
\r
5507 // make sure opponent is not going to reply after receiving move to book position
\r
5508 SendToProgram("force\n", cps);
\r
5509 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5511 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5512 // now arrange restart after book miss
\r
5514 // after a book hit we never send 'go', and the code after the call to this routine
\r
5515 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5516 char buf[MSG_SIZ];
\r
5517 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5518 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5519 SendToProgram(buf, cps);
\r
5520 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5521 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5522 SendToProgram("go\n", cps);
\r
5523 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5524 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5525 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5526 SendToProgram("go\n", cps);
\r
5527 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5529 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5532 char *savedMessage;
\r
5533 ChessProgramState *savedState;
\r
5534 void DeferredBookMove(void)
\r
5536 if(savedState->lastPing != savedState->lastPong)
\r
5537 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5539 HandleMachineMove(savedMessage, savedState);
\r
5543 HandleMachineMove(message, cps)
\r
5545 ChessProgramState *cps;
\r
5547 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5548 char realname[MSG_SIZ];
\r
5549 int fromX, fromY, toX, toY;
\r
5550 ChessMove moveType;
\r
5556 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5558 * Kludge to ignore BEL characters
\r
5560 while (*message == '\007') message++;
\r
5563 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5565 if(cps->debug && *message == '#') return;
\r
5568 * Look for book output
\r
5570 if (cps == &first && bookRequested) {
\r
5571 if (message[0] == '\t' || message[0] == ' ') {
\r
5572 /* Part of the book output is here; append it */
\r
5573 strcat(bookOutput, message);
\r
5574 strcat(bookOutput, " \n");
\r
5576 } else if (bookOutput[0] != NULLCHAR) {
\r
5577 /* All of book output has arrived; display it */
\r
5578 char *p = bookOutput;
\r
5579 while (*p != NULLCHAR) {
\r
5580 if (*p == '\t') *p = ' ';
\r
5583 DisplayInformation(bookOutput);
\r
5584 bookRequested = FALSE;
\r
5585 /* Fall through to parse the current output */
\r
5590 * Look for machine move.
\r
5592 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5593 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5595 /* This method is only useful on engines that support ping */
\r
5596 if (cps->lastPing != cps->lastPong) {
\r
5597 if (gameMode == BeginningOfGame) {
\r
5598 /* Extra move from before last new; ignore */
\r
5599 if (appData.debugMode) {
\r
5600 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5603 if (appData.debugMode) {
\r
5604 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5605 cps->which, gameMode);
\r
5608 SendToProgram("undo\n", cps);
\r
5613 switch (gameMode) {
\r
5614 case BeginningOfGame:
\r
5615 /* Extra move from before last reset; ignore */
\r
5616 if (appData.debugMode) {
\r
5617 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5624 /* Extra move after we tried to stop. The mode test is
\r
5625 not a reliable way of detecting this problem, but it's
\r
5626 the best we can do on engines that don't support ping.
\r
5628 if (appData.debugMode) {
\r
5629 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5630 cps->which, gameMode);
\r
5632 SendToProgram("undo\n", cps);
\r
5635 case MachinePlaysWhite:
\r
5636 case IcsPlayingWhite:
\r
5637 machineWhite = TRUE;
\r
5640 case MachinePlaysBlack:
\r
5641 case IcsPlayingBlack:
\r
5642 machineWhite = FALSE;
\r
5645 case TwoMachinesPlay:
\r
5646 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5649 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5650 if (appData.debugMode) {
\r
5652 "Ignoring move out of turn by %s, gameMode %d"
\r
5653 ", forwardMost %d\n",
\r
5654 cps->which, gameMode, forwardMostMove);
\r
5659 if (appData.debugMode) { int f = forwardMostMove;
\r
5660 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5661 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5663 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5664 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5665 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5666 /* Machine move could not be parsed; ignore it. */
\r
5667 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5668 machineMove, cps->which);
\r
5669 DisplayError(buf1, 0);
\r
5670 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5671 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5672 if (gameMode == TwoMachinesPlay) {
\r
5673 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5679 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5680 /* So we have to redo legality test with true e.p. status here, */
\r
5681 /* to make sure an illegal e.p. capture does not slip through, */
\r
5682 /* to cause a forfeit on a justified illegal-move complaint */
\r
5683 /* of the opponent. */
\r
5684 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5685 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5687 ChessMove moveType;
\r
5688 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5689 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5690 fromY, fromX, toY, toX, promoChar);
\r
5691 if (appData.debugMode) {
\r
5693 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5694 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5695 fprintf(debugFP, "castling rights\n");
\r
5697 if(moveType == IllegalMove) {
\r
5698 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5699 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5700 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5702 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5703 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5704 when they shouldn't (like TSCP-Gothic) */
\r
5705 switch(moveType) {
\r
5706 case WhiteASideCastleFR:
\r
5707 case BlackASideCastleFR:
\r
5709 currentMoveString[2]++;
\r
5711 case WhiteHSideCastleFR:
\r
5712 case BlackHSideCastleFR:
\r
5714 currentMoveString[2]--;
\r
5718 hintRequested = FALSE;
\r
5719 lastHint[0] = NULLCHAR;
\r
5720 bookRequested = FALSE;
\r
5721 /* Program may be pondering now */
\r
5722 cps->maybeThinking = TRUE;
\r
5723 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5724 if (cps->offeredDraw) cps->offeredDraw--;
\r
5727 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5729 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5730 ics_user_moved = 1;
\r
5731 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5732 char buf[3*MSG_SIZ];
\r
5734 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5735 programStats.depth,
\r
5736 programStats.score / 100.,
\r
5737 programStats.time / 100.,
\r
5738 (double) programStats.nodes,
\r
5739 programStats.nodes / (10*abs(programStats.time) + 1.),
\r
5740 programStats.movelist);
\r
5745 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5746 strcpy(machineMove, currentMoveString);
\r
5747 strcat(machineMove, "\n");
\r
5748 strcpy(moveList[forwardMostMove], machineMove);
\r
5750 /* [AS] Save move info and clear stats for next move */
\r
5751 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5752 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5753 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5754 ClearProgramStats();
\r
5755 thinkOutput[0] = NULLCHAR;
\r
5756 hiddenThinkOutputState = 0;
\r
5758 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5760 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5761 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5764 while( count < adjudicateLossPlies ) {
\r
5765 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5768 score = -score; /* Flip score for winning side */
\r
5771 if( score > adjudicateLossThreshold ) {
\r
5778 if( count >= adjudicateLossPlies ) {
\r
5779 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5781 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5782 "Xboard adjudication",
\r
5789 if( gameMode == TwoMachinesPlay ) {
\r
5790 // [HGM] some adjudications useful with buggy engines
\r
5791 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5792 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5794 if(appData.testLegality)
\r
5795 // don't wait for engine to announce game end if we can judge ourselves
\r
5796 switch (MateTest(boards[forwardMostMove],
\r
5797 PosFlags(forwardMostMove), epFile,
\r
5798 castlingRights[forwardMostMove]) ) {
\r
5803 case MT_STALEMATE:
\r
5804 epStatus[forwardMostMove] = EP_STALEMATE;
\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( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5812 case MT_CHECKMATE:
\r
5813 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5814 if(appData.checkMates) {
\r
5815 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5816 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5817 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5818 "Xboard adjudication: Checkmate",
\r
5824 if( appData.testLegality )
\r
5825 { /* [HGM] Some more adjudications for obstinate engines */
\r
5826 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5827 NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,
\r
5828 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5829 static int moveCount = 6;
\r
5831 /* First absolutely insufficient mating material. Count what is on board. */
\r
5832 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5833 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5837 { /* count B,N,R and other of each side */
\r
5841 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5842 bishopsColor |= 1 << ((i^j)&1);
\r
5847 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5848 bishopsColor |= 1 << ((i^j)&1);
\r
5858 case EmptySquare:
\r
5863 PawnAdvance += m; NrPawns++;
\r
5865 NrPieces += (p != EmptySquare);
\r
5866 NrW += ((int)p < (int)BlackPawn);
\r
5867 if(gameInfo.variant == VariantXiangqi &&
\r
5868 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5869 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5870 NrW -= ((int)p < (int)BlackPawn);
\r
5874 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5875 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5876 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5877 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5879 /* always flag draws, for judging claims */
\r
5880 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5882 if(appData.materialDraws) {
\r
5883 /* but only adjudicate them if adjudication enabled */
\r
5884 SendToProgram("force\n", cps->other); // suppress reply
\r
5885 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5887 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5892 /* Shatranj baring rule */
\r
5893 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5896 if(--bare < 0 && appData.checkMates) {
\r
5897 /* but only adjudicate them if adjudication enabled */
\r
5898 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5899 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5900 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5901 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5906 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5907 if(NrPieces == 4 &&
\r
5908 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5909 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5910 || NrWN==2 || NrBN==2 /* KNNK */
\r
5911 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5913 if(--moveCount < 0 && appData.trivialDraws)
\r
5914 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5915 SendToProgram("force\n", cps->other); // suppress reply
\r
5916 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5917 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5918 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5921 } else moveCount = 6;
\r
5925 if (appData.debugMode) { int i;
\r
5926 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5927 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5928 appData.drawRepeats);
\r
5929 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5930 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5934 /* Check for rep-draws */
\r
5936 for(k = forwardMostMove-2;
\r
5937 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5938 epStatus[k] < EP_UNKNOWN &&
\r
5939 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5943 if (appData.debugMode) {
\r
5944 fprintf(debugFP, " loop\n");
\r
5947 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5949 if (appData.debugMode) {
\r
5950 fprintf(debugFP, "match\n");
\r
5953 /* compare castling rights */
\r
5954 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5955 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5956 rights++; /* King lost rights, while rook still had them */
\r
5957 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5958 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5959 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5960 rights++; /* but at least one rook lost them */
\r
5962 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5963 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5965 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5966 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5967 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5971 if (appData.debugMode) {
\r
5972 for(i=0; i<nrCastlingRights; i++)
\r
5973 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5976 if (appData.debugMode) {
\r
5977 fprintf(debugFP, " %d %d\n", rights, k);
\r
5980 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5981 && appData.drawRepeats > 1) {
\r
5982 /* adjudicate after user-specified nr of repeats */
\r
5983 SendToProgram("force\n", cps->other); // suppress reply
\r
5984 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5985 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5986 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5987 // [HGM] xiangqi: check for forbidden perpetuals
\r
5988 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5989 for(m=forwardMostMove; m>k; m-=2) {
\r
5990 if(MateTest(boards[m], PosFlags(m),
\r
5991 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5992 ourPerpetual = 0; // the current mover did not always check
\r
5993 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5994 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5995 hisPerpetual = 0; // the opponent did not always check
\r
5997 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5998 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5999 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
6002 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
6003 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6004 // if neither of us is checking all the time, or both are, it is draw
\r
6005 // (illegal-chase forfeits not implemented yet!)
\r
6007 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6010 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6011 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6015 /* Now we test for 50-move draws. Determine ply count */
\r
6016 count = forwardMostMove;
\r
6017 /* look for last irreversble move */
\r
6018 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6020 /* if we hit starting position, add initial plies */
\r
6021 if( count == backwardMostMove )
\r
6022 count -= initialRulePlies;
\r
6023 count = forwardMostMove - count;
\r
6025 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6026 /* this is used to judge if draw claims are legal */
\r
6027 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6028 SendToProgram("force\n", cps->other); // suppress reply
\r
6029 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6030 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6031 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6035 /* if draw offer is pending, treat it as a draw claim
\r
6036 * when draw condition present, to allow engines a way to
\r
6037 * claim draws before making their move to avoid a race
\r
6038 * condition occurring after their move
\r
6040 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6042 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6043 p = "Draw claim: 50-move rule";
\r
6044 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6045 p = "Draw claim: 3-fold repetition";
\r
6046 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6047 p = "Draw claim: insufficient mating material";
\r
6049 SendToProgram("force\n", cps->other); // suppress reply
\r
6050 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6051 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6052 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6058 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6059 SendToProgram("force\n", cps->other); // suppress reply
\r
6060 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6061 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6063 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6070 if (gameMode == TwoMachinesPlay) {
\r
6071 /* [HGM] relaying draw offers moved to after reception of move */
\r
6072 /* and interpreting offer as claim if it brings draw condition */
\r
6073 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6074 SendToProgram("draw\n", cps->other);
\r
6076 if (cps->other->sendTime) {
\r
6077 SendTimeRemaining(cps->other,
\r
6078 cps->other->twoMachinesColor[0] == 'w');
\r
6080 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6081 if (firstMove && !bookHit) {
\r
6082 firstMove = FALSE;
\r
6083 if (cps->other->useColors) {
\r
6084 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6086 SendToProgram("go\n", cps->other);
\r
6088 cps->other->maybeThinking = TRUE;
\r
6091 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6093 if (!pausing && appData.ringBellAfterMoves) {
\r
6098 * Reenable menu items that were disabled while
\r
6099 * machine was thinking
\r
6101 if (gameMode != TwoMachinesPlay)
\r
6102 SetUserThinkingEnables();
\r
6104 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6105 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6106 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6108 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6110 strcpy(bookMove, "move ");
\r
6111 strcat(bookMove, bookHit);
\r
6112 message = bookMove;
\r
6114 programStats.depth = programStats.nodes = programStats.time =
\r
6115 programStats.score = programStats.got_only_move = 0;
\r
6116 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6118 if(cps->lastPing != cps->lastPong) {
\r
6119 savedMessage = message; // args for deferred call
\r
6121 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6124 goto FakeBookMove;
\r
6130 /* Set special modes for chess engines. Later something general
\r
6131 * could be added here; for now there is just one kludge feature,
\r
6132 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6133 * when "xboard" is given as an interactive command.
\r
6135 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6136 cps->useSigint = FALSE;
\r
6137 cps->useSigterm = FALSE;
\r
6140 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6141 * want this, I was asked to put it in, and obliged.
\r
6143 if (!strncmp(message, "setboard ", 9)) {
\r
6144 Board initial_position; int i;
\r
6146 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6148 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6149 DisplayError(_("Bad FEN received from engine"), 0);
\r
6152 Reset(FALSE, FALSE);
\r
6153 CopyBoard(boards[0], initial_position);
\r
6154 initialRulePlies = FENrulePlies;
\r
6155 epStatus[0] = FENepStatus;
\r
6156 for( i=0; i<nrCastlingRights; i++ )
\r
6157 castlingRights[0][i] = FENcastlingRights[i];
\r
6158 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6159 else gameMode = MachinePlaysBlack;
\r
6160 DrawPosition(FALSE, boards[currentMove]);
\r
6166 * Look for communication commands
\r
6168 if (!strncmp(message, "telluser ", 9)) {
\r
6169 DisplayNote(message + 9);
\r
6172 if (!strncmp(message, "tellusererror ", 14)) {
\r
6173 DisplayError(message + 14, 0);
\r
6176 if (!strncmp(message, "tellopponent ", 13)) {
\r
6177 if (appData.icsActive) {
\r
6179 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6183 DisplayNote(message + 13);
\r
6187 if (!strncmp(message, "tellothers ", 11)) {
\r
6188 if (appData.icsActive) {
\r
6190 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6196 if (!strncmp(message, "tellall ", 8)) {
\r
6197 if (appData.icsActive) {
\r
6199 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6203 DisplayNote(message + 8);
\r
6207 if (strncmp(message, "warning", 7) == 0) {
\r
6208 /* Undocumented feature, use tellusererror in new code */
\r
6209 DisplayError(message, 0);
\r
6212 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6213 strcpy(realname, cps->tidy);
\r
6214 strcat(realname, " query");
\r
6215 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6218 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6219 * sent until we are logged on. Crafty kibitzes have been known to
\r
6220 * interfere with the login process.
\r
6223 if (!strncmp(message, "tellics ", 8)) {
\r
6224 SendToICS(message + 8);
\r
6228 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6229 SendToICS(ics_prefix);
\r
6230 SendToICS(message + 15);
\r
6234 /* The following are for backward compatibility only */
\r
6235 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6236 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6237 SendToICS(ics_prefix);
\r
6238 SendToICS(message);
\r
6243 if (strncmp(message, "feature ", 8) == 0) {
\r
6244 ParseFeatures(message+8, cps);
\r
6246 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6250 * If the move is illegal, cancel it and redraw the board.
\r
6251 * Also deal with other error cases. Matching is rather loose
\r
6252 * here to accommodate engines written before the spec.
\r
6254 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6255 strncmp(message, "Error", 5) == 0) {
\r
6256 if (StrStr(message, "name") ||
\r
6257 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6258 StrStr(message, "result") || StrStr(message, "board") ||
\r
6259 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6260 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6261 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6262 StrStr(message, "accepted")) {
\r
6265 if (StrStr(message, "protover")) {
\r
6266 /* Program is responding to input, so it's apparently done
\r
6267 initializing, and this error message indicates it is
\r
6268 protocol version 1. So we don't need to wait any longer
\r
6269 for it to initialize and send feature commands. */
\r
6270 FeatureDone(cps, 1);
\r
6271 cps->protocolVersion = 1;
\r
6274 cps->maybeThinking = FALSE;
\r
6276 if (StrStr(message, "draw")) {
\r
6277 /* Program doesn't have "draw" command */
\r
6278 cps->sendDrawOffers = 0;
\r
6281 if (cps->sendTime != 1 &&
\r
6282 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6283 /* Program apparently doesn't have "time" or "otim" command */
\r
6284 cps->sendTime = 0;
\r
6287 if (StrStr(message, "analyze")) {
\r
6288 cps->analysisSupport = FALSE;
\r
6289 cps->analyzing = FALSE;
\r
6290 Reset(FALSE, TRUE);
\r
6291 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6292 DisplayError(buf2, 0);
\r
6295 if (StrStr(message, "(no matching move)st")) {
\r
6296 /* Special kludge for GNU Chess 4 only */
\r
6297 cps->stKludge = TRUE;
\r
6298 SendTimeControl(cps, movesPerSession, timeControl,
\r
6299 timeIncrement, appData.searchDepth,
\r
6303 if (StrStr(message, "(no matching move)sd")) {
\r
6304 /* Special kludge for GNU Chess 4 only */
\r
6305 cps->sdKludge = TRUE;
\r
6306 SendTimeControl(cps, movesPerSession, timeControl,
\r
6307 timeIncrement, appData.searchDepth,
\r
6311 if (!StrStr(message, "llegal")) {
\r
6314 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6315 gameMode == IcsIdle) return;
\r
6316 if (forwardMostMove <= backwardMostMove) return;
\r
6318 /* Following removed: it caused a bug where a real illegal move
\r
6319 message in analyze mored would be ignored. */
\r
6320 if (cps == &first && programStats.ok_to_send == 0) {
\r
6321 /* Bogus message from Crafty responding to "." This filtering
\r
6322 can miss some of the bad messages, but fortunately the bug
\r
6323 is fixed in current Crafty versions, so it doesn't matter. */
\r
6327 if (pausing) PauseEvent();
\r
6328 if (gameMode == PlayFromGameFile) {
\r
6329 /* Stop reading this game file */
\r
6330 gameMode = EditGame;
\r
6333 currentMove = --forwardMostMove;
\r
6334 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6336 DisplayBothClocks();
\r
6337 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6338 parseList[currentMove], cps->which);
\r
6339 DisplayMoveError(buf1);
\r
6340 DrawPosition(FALSE, boards[currentMove]);
\r
6342 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6343 /* only passes fully legal moves */
\r
6344 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6345 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6346 "False illegal-move claim", GE_XBOARD );
\r
6350 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6351 /* Program has a broken "time" command that
\r
6352 outputs a string not ending in newline.
\r
6354 cps->sendTime = 0;
\r
6358 * If chess program startup fails, exit with an error message.
\r
6359 * Attempts to recover here are futile.
\r
6361 if ((StrStr(message, "unknown host") != NULL)
\r
6362 || (StrStr(message, "No remote directory") != NULL)
\r
6363 || (StrStr(message, "not found") != NULL)
\r
6364 || (StrStr(message, "No such file") != NULL)
\r
6365 || (StrStr(message, "can't alloc") != NULL)
\r
6366 || (StrStr(message, "Permission denied") != NULL)) {
\r
6368 cps->maybeThinking = FALSE;
\r
6369 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6370 cps->which, cps->program, cps->host, message);
\r
6371 RemoveInputSource(cps->isr);
\r
6372 DisplayFatalError(buf1, 0, 1);
\r
6377 * Look for hint output
\r
6379 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6380 if (cps == &first && hintRequested) {
\r
6381 hintRequested = FALSE;
\r
6382 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6383 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6384 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6385 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6386 fromY, fromX, toY, toX, promoChar, buf1);
\r
6387 sprintf(buf2, _("Hint: %s"), buf1);
\r
6388 DisplayInformation(buf2);
\r
6390 /* Hint move could not be parsed!? */
\r
6392 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6393 buf1, cps->which);
\r
6394 DisplayError(buf2, 0);
\r
6397 strcpy(lastHint, buf1);
\r
6403 * Ignore other messages if game is not in progress
\r
6405 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6406 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6409 * look for win, lose, draw, or draw offer
\r
6411 if (strncmp(message, "1-0", 3) == 0) {
\r
6412 char *p, *q, *r = "";
\r
6413 p = strchr(message, '{');
\r
6415 q = strchr(p, '}');
\r
6421 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6423 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6424 char *p, *q, *r = "";
\r
6425 p = strchr(message, '{');
\r
6427 q = strchr(p, '}');
\r
6433 /* Kludge for Arasan 4.1 bug */
\r
6434 if (strcmp(r, "Black resigns") == 0) {
\r
6435 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6438 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6440 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6441 char *p, *q, *r = "";
\r
6442 p = strchr(message, '{');
\r
6444 q = strchr(p, '}');
\r
6451 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6454 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6455 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6457 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6458 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6460 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6461 strncmp(message, "Black matches", 13) == 0 ) {
\r
6462 /* [HGM] ignore GNUShogi noises */
\r
6464 } else if (strncmp(message, "White", 5) == 0 &&
\r
6465 message[5] != '(' &&
\r
6466 StrStr(message, "Black") == NULL) {
\r
6467 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6469 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6470 message[5] != '(') {
\r
6471 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6473 } else if (strcmp(message, "resign") == 0 ||
\r
6474 strcmp(message, "computer resigns") == 0) {
\r
6475 switch (gameMode) {
\r
6476 case MachinePlaysBlack:
\r
6477 case IcsPlayingBlack:
\r
6478 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6480 case MachinePlaysWhite:
\r
6481 case IcsPlayingWhite:
\r
6482 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6484 case TwoMachinesPlay:
\r
6485 if (cps->twoMachinesColor[0] == 'w')
\r
6486 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6488 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6491 /* can't happen */
\r
6495 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6496 switch (gameMode) {
\r
6497 case MachinePlaysBlack:
\r
6498 case IcsPlayingBlack:
\r
6499 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6501 case MachinePlaysWhite:
\r
6502 case IcsPlayingWhite:
\r
6503 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6505 case TwoMachinesPlay:
\r
6506 if (cps->twoMachinesColor[0] == 'w')
\r
6507 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6509 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6512 /* can't happen */
\r
6516 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6517 switch (gameMode) {
\r
6518 case MachinePlaysBlack:
\r
6519 case IcsPlayingBlack:
\r
6520 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6522 case MachinePlaysWhite:
\r
6523 case IcsPlayingWhite:
\r
6524 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6526 case TwoMachinesPlay:
\r
6527 if (cps->twoMachinesColor[0] == 'w')
\r
6528 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6530 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6533 /* can't happen */
\r
6537 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6538 if (WhiteOnMove(forwardMostMove)) {
\r
6539 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6541 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6544 } else if (strstr(message, "Draw") != NULL ||
\r
6545 strstr(message, "game is a draw") != NULL) {
\r
6546 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6548 } else if (strstr(message, "offer") != NULL &&
\r
6549 strstr(message, "draw") != NULL) {
\r
6551 if (appData.zippyPlay && first.initDone) {
\r
6552 /* Relay offer to ICS */
\r
6553 SendToICS(ics_prefix);
\r
6554 SendToICS("draw\n");
\r
6557 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6558 if (gameMode == TwoMachinesPlay) {
\r
6559 if (cps->other->offeredDraw) {
\r
6560 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6561 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6562 /* until after we also have move, to see if it is really claim */
\r
6566 if (cps->other->sendDrawOffers) {
\r
6567 SendToProgram("draw\n", cps->other);
\r
6571 } else if (gameMode == MachinePlaysWhite ||
\r
6572 gameMode == MachinePlaysBlack) {
\r
6573 if (userOfferedDraw) {
\r
6574 DisplayInformation(_("Machine accepts your draw offer"));
\r
6575 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6577 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6584 * Look for thinking output
\r
6586 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6587 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6589 int plylev, mvleft, mvtot, curscore, time;
\r
6590 char mvname[MOVE_LEN];
\r
6591 u64 nodes; // [DM]
\r
6593 int ignore = FALSE;
\r
6594 int prefixHint = FALSE;
\r
6595 mvname[0] = NULLCHAR;
\r
6597 switch (gameMode) {
\r
6598 case MachinePlaysBlack:
\r
6599 case IcsPlayingBlack:
\r
6600 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6602 case MachinePlaysWhite:
\r
6603 case IcsPlayingWhite:
\r
6604 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6609 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6610 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6612 case TwoMachinesPlay:
\r
6613 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6623 buf1[0] = NULLCHAR;
\r
6624 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6625 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6627 if (plyext != ' ' && plyext != '\t') {
\r
6631 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6632 if( cps->scoreIsAbsolute &&
\r
6633 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6635 curscore = -curscore;
\r
6639 programStats.depth = plylev;
\r
6640 programStats.nodes = nodes;
\r
6641 programStats.time = time;
\r
6642 programStats.score = curscore;
\r
6643 programStats.got_only_move = 0;
\r
6645 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6648 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6649 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6650 if(WhiteOnMove(forwardMostMove))
\r
6651 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6652 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6655 /* Buffer overflow protection */
\r
6656 if (buf1[0] != NULLCHAR) {
\r
6657 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6658 && appData.debugMode) {
\r
6660 "PV is too long; using the first %d bytes.\n",
\r
6661 sizeof(programStats.movelist) - 1);
\r
6664 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6666 sprintf(programStats.movelist, " no PV\n");
\r
6669 if (programStats.seen_stat) {
\r
6670 programStats.ok_to_send = 1;
\r
6673 if (strchr(programStats.movelist, '(') != NULL) {
\r
6674 programStats.line_is_book = 1;
\r
6675 programStats.nr_moves = 0;
\r
6676 programStats.moves_left = 0;
\r
6678 programStats.line_is_book = 0;
\r
6681 SendProgramStatsToFrontend( cps, &programStats );
\r
6684 [AS] Protect the thinkOutput buffer from overflow... this
\r
6685 is only useful if buf1 hasn't overflowed first!
\r
6687 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6689 (gameMode == TwoMachinesPlay ?
\r
6690 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6691 ((double) curscore) / 100.0,
\r
6692 prefixHint ? lastHint : "",
\r
6693 prefixHint ? " " : "" );
\r
6695 if( buf1[0] != NULLCHAR ) {
\r
6696 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6698 if( strlen(buf1) > max_len ) {
\r
6699 if( appData.debugMode) {
\r
6700 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6702 buf1[max_len+1] = '\0';
\r
6705 strcat( thinkOutput, buf1 );
\r
6708 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6709 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6710 DisplayMove(currentMove - 1);
\r
6711 DisplayAnalysis();
\r
6715 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6716 /* crafty (9.25+) says "(only move) <move>"
\r
6717 * if there is only 1 legal move
\r
6719 sscanf(p, "(only move) %s", buf1);
\r
6720 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6721 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6722 programStats.depth = 1;
\r
6723 programStats.nr_moves = 1;
\r
6724 programStats.moves_left = 1;
\r
6725 programStats.nodes = 1;
\r
6726 programStats.time = 1;
\r
6727 programStats.got_only_move = 1;
\r
6729 /* Not really, but we also use this member to
\r
6730 mean "line isn't going to change" (Crafty
\r
6731 isn't searching, so stats won't change) */
\r
6732 programStats.line_is_book = 1;
\r
6734 SendProgramStatsToFrontend( cps, &programStats );
\r
6736 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6737 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6738 DisplayMove(currentMove - 1);
\r
6739 DisplayAnalysis();
\r
6742 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6743 &time, &nodes, &plylev, &mvleft,
\r
6744 &mvtot, mvname) >= 5) {
\r
6745 /* The stat01: line is from Crafty (9.29+) in response
\r
6746 to the "." command */
\r
6747 programStats.seen_stat = 1;
\r
6748 cps->maybeThinking = TRUE;
\r
6750 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6753 programStats.depth = plylev;
\r
6754 programStats.time = time;
\r
6755 programStats.nodes = nodes;
\r
6756 programStats.moves_left = mvleft;
\r
6757 programStats.nr_moves = mvtot;
\r
6758 strcpy(programStats.move_name, mvname);
\r
6759 programStats.ok_to_send = 1;
\r
6760 programStats.movelist[0] = '\0';
\r
6762 SendProgramStatsToFrontend( cps, &programStats );
\r
6764 DisplayAnalysis();
\r
6767 } else if (strncmp(message,"++",2) == 0) {
\r
6768 /* Crafty 9.29+ outputs this */
\r
6769 programStats.got_fail = 2;
\r
6772 } else if (strncmp(message,"--",2) == 0) {
\r
6773 /* Crafty 9.29+ outputs this */
\r
6774 programStats.got_fail = 1;
\r
6777 } else if (thinkOutput[0] != NULLCHAR &&
\r
6778 strncmp(message, " ", 4) == 0) {
\r
6779 unsigned message_len;
\r
6782 while (*p && *p == ' ') p++;
\r
6784 message_len = strlen( p );
\r
6786 /* [AS] Avoid buffer overflow */
\r
6787 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6788 strcat(thinkOutput, " ");
\r
6789 strcat(thinkOutput, p);
\r
6792 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6793 strcat(programStats.movelist, " ");
\r
6794 strcat(programStats.movelist, p);
\r
6797 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6798 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6799 DisplayMove(currentMove - 1);
\r
6800 DisplayAnalysis();
\r
6806 buf1[0] = NULLCHAR;
\r
6808 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6809 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6811 ChessProgramStats cpstats;
\r
6813 if (plyext != ' ' && plyext != '\t') {
\r
6817 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6818 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6819 curscore = -curscore;
\r
6822 cpstats.depth = plylev;
\r
6823 cpstats.nodes = nodes;
\r
6824 cpstats.time = time;
\r
6825 cpstats.score = curscore;
\r
6826 cpstats.got_only_move = 0;
\r
6827 cpstats.movelist[0] = '\0';
\r
6829 if (buf1[0] != NULLCHAR) {
\r
6830 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6833 cpstats.ok_to_send = 0;
\r
6834 cpstats.line_is_book = 0;
\r
6835 cpstats.nr_moves = 0;
\r
6836 cpstats.moves_left = 0;
\r
6838 SendProgramStatsToFrontend( cps, &cpstats );
\r
6845 /* Parse a game score from the character string "game", and
\r
6846 record it as the history of the current game. The game
\r
6847 score is NOT assumed to start from the standard position.
\r
6848 The display is not updated in any way.
\r
6851 ParseGameHistory(game)
\r
6854 ChessMove moveType;
\r
6855 int fromX, fromY, toX, toY, boardIndex;
\r
6858 char buf[MSG_SIZ];
\r
6860 if (appData.debugMode)
\r
6861 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6863 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6864 gameInfo.site = StrSave(appData.icsHost);
\r
6865 gameInfo.date = PGNDate();
\r
6866 gameInfo.round = StrSave("-");
\r
6868 /* Parse out names of players */
\r
6869 while (*game == ' ') game++;
\r
6871 while (*game != ' ') *p++ = *game++;
\r
6873 gameInfo.white = StrSave(buf);
\r
6874 while (*game == ' ') game++;
\r
6876 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6878 gameInfo.black = StrSave(buf);
\r
6881 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6884 yyboardindex = boardIndex;
\r
6885 moveType = (ChessMove) yylex();
\r
6886 switch (moveType) {
\r
6887 case IllegalMove: /* maybe suicide chess, etc. */
\r
6888 if (appData.debugMode) {
\r
6889 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6890 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6891 setbuf(debugFP, NULL);
\r
6893 case WhitePromotionChancellor:
\r
6894 case BlackPromotionChancellor:
\r
6895 case WhitePromotionArchbishop:
\r
6896 case BlackPromotionArchbishop:
\r
6897 case WhitePromotionQueen:
\r
6898 case BlackPromotionQueen:
\r
6899 case WhitePromotionRook:
\r
6900 case BlackPromotionRook:
\r
6901 case WhitePromotionBishop:
\r
6902 case BlackPromotionBishop:
\r
6903 case WhitePromotionKnight:
\r
6904 case BlackPromotionKnight:
\r
6905 case WhitePromotionKing:
\r
6906 case BlackPromotionKing:
\r
6908 case WhiteCapturesEnPassant:
\r
6909 case BlackCapturesEnPassant:
\r
6910 case WhiteKingSideCastle:
\r
6911 case WhiteQueenSideCastle:
\r
6912 case BlackKingSideCastle:
\r
6913 case BlackQueenSideCastle:
\r
6914 case WhiteKingSideCastleWild:
\r
6915 case WhiteQueenSideCastleWild:
\r
6916 case BlackKingSideCastleWild:
\r
6917 case BlackQueenSideCastleWild:
\r
6919 case WhiteHSideCastleFR:
\r
6920 case WhiteASideCastleFR:
\r
6921 case BlackHSideCastleFR:
\r
6922 case BlackASideCastleFR:
\r
6924 fromX = currentMoveString[0] - AAA;
\r
6925 fromY = currentMoveString[1] - ONE;
\r
6926 toX = currentMoveString[2] - AAA;
\r
6927 toY = currentMoveString[3] - ONE;
\r
6928 promoChar = currentMoveString[4];
\r
6932 fromX = moveType == WhiteDrop ?
\r
6933 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6934 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6935 fromY = DROP_RANK;
\r
6936 toX = currentMoveString[2] - AAA;
\r
6937 toY = currentMoveString[3] - ONE;
\r
6938 promoChar = NULLCHAR;
\r
6940 case AmbiguousMove:
\r
6942 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6943 if (appData.debugMode) {
\r
6944 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6945 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6946 setbuf(debugFP, NULL);
\r
6948 DisplayError(buf, 0);
\r
6950 case ImpossibleMove:
\r
6952 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6953 if (appData.debugMode) {
\r
6954 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6955 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6956 setbuf(debugFP, NULL);
\r
6958 DisplayError(buf, 0);
\r
6960 case (ChessMove) 0: /* end of file */
\r
6961 if (boardIndex < backwardMostMove) {
\r
6962 /* Oops, gap. How did that happen? */
\r
6963 DisplayError(_("Gap in move list"), 0);
\r
6966 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6967 if (boardIndex > forwardMostMove) {
\r
6968 forwardMostMove = boardIndex;
\r
6972 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6973 strcat(parseList[boardIndex-1], " ");
\r
6974 strcat(parseList[boardIndex-1], yy_text);
\r
6986 case GameUnfinished:
\r
6987 if (gameMode == IcsExamining) {
\r
6988 if (boardIndex < backwardMostMove) {
\r
6989 /* Oops, gap. How did that happen? */
\r
6992 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6995 gameInfo.result = moveType;
\r
6996 p = strchr(yy_text, '{');
\r
6997 if (p == NULL) p = strchr(yy_text, '(');
\r
7000 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7002 q = strchr(p, *p == '{' ? '}' : ')');
\r
7003 if (q != NULL) *q = NULLCHAR;
\r
7006 gameInfo.resultDetails = StrSave(p);
\r
7009 if (boardIndex >= forwardMostMove &&
\r
7010 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7011 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7014 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7015 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7016 parseList[boardIndex]);
\r
7017 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7018 /* currentMoveString is set as a side-effect of yylex */
\r
7019 strcpy(moveList[boardIndex], currentMoveString);
\r
7020 strcat(moveList[boardIndex], "\n");
\r
7022 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7023 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7024 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7026 case MT_STALEMATE:
\r
7030 if(gameInfo.variant != VariantShogi)
\r
7031 strcat(parseList[boardIndex - 1], "+");
\r
7033 case MT_CHECKMATE:
\r
7034 strcat(parseList[boardIndex - 1], "#");
\r
7041 /* Apply a move to the given board */
\r
7043 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7044 int fromX, fromY, toX, toY;
\r
7048 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7050 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7051 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7052 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7055 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7056 oldEP = epStatus[p-1];
\r
7057 epStatus[p] = EP_NONE;
\r
7059 if( board[toY][toX] != EmptySquare )
\r
7060 epStatus[p] = EP_CAPTURE;
\r
7062 if( board[fromY][fromX] == WhitePawn ) {
\r
7063 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7064 epStatus[p] = EP_PAWN_MOVE;
\r
7065 if( toY-fromY==2) {
\r
7066 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7067 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7068 epStatus[p] = toX | berolina;
\r
7069 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7070 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7071 epStatus[p] = toX;
\r
7074 if( board[fromY][fromX] == BlackPawn ) {
\r
7075 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7076 epStatus[p] = EP_PAWN_MOVE;
\r
7077 if( toY-fromY== -2) {
\r
7078 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7079 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7080 epStatus[p] = toX | berolina;
\r
7081 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7082 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7083 epStatus[p] = toX;
\r
7087 for(i=0; i<nrCastlingRights; i++) {
\r
7088 castlingRights[p][i] = castlingRights[p-1][i];
\r
7089 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7090 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7091 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7096 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7097 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7098 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7100 if (fromX == toX && fromY == toY) return;
\r
7102 if (fromY == DROP_RANK) {
\r
7103 /* must be first */
\r
7104 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7106 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7107 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7108 if(gameInfo.variant == VariantKnightmate)
\r
7109 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7111 /* Code added by Tord: */
\r
7112 /* FRC castling assumed when king captures friendly rook. */
\r
7113 if (board[fromY][fromX] == WhiteKing &&
\r
7114 board[toY][toX] == WhiteRook) {
\r
7115 board[fromY][fromX] = EmptySquare;
\r
7116 board[toY][toX] = EmptySquare;
\r
7118 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7120 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7122 } else if (board[fromY][fromX] == BlackKing &&
\r
7123 board[toY][toX] == BlackRook) {
\r
7124 board[fromY][fromX] = EmptySquare;
\r
7125 board[toY][toX] = EmptySquare;
\r
7127 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7129 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7131 /* End of code added by Tord */
\r
7133 } else if (board[fromY][fromX] == king
\r
7134 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7135 && toY == fromY && toX > fromX+1) {
\r
7136 board[fromY][fromX] = EmptySquare;
\r
7137 board[toY][toX] = king;
\r
7138 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7139 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7140 } else if (board[fromY][fromX] == king
\r
7141 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7142 && toY == fromY && toX < fromX-1) {
\r
7143 board[fromY][fromX] = EmptySquare;
\r
7144 board[toY][toX] = king;
\r
7145 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7146 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7147 } else if (board[fromY][fromX] == WhitePawn
\r
7148 && toY == BOARD_HEIGHT-1
\r
7149 && gameInfo.variant != VariantXiangqi
\r
7151 /* white pawn promotion */
\r
7152 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7153 if (board[toY][toX] == EmptySquare) {
\r
7154 board[toY][toX] = WhiteQueen;
\r
7156 if(gameInfo.variant==VariantBughouse ||
\r
7157 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7158 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7159 board[fromY][fromX] = EmptySquare;
\r
7160 } else if ((fromY == BOARD_HEIGHT-4)
\r
7162 && gameInfo.variant != VariantXiangqi
\r
7163 && gameInfo.variant != VariantBerolina
\r
7164 && (board[fromY][fromX] == WhitePawn)
\r
7165 && (board[toY][toX] == EmptySquare)) {
\r
7166 board[fromY][fromX] = EmptySquare;
\r
7167 board[toY][toX] = WhitePawn;
\r
7168 captured = board[toY - 1][toX];
\r
7169 board[toY - 1][toX] = EmptySquare;
\r
7170 } else if ((fromY == BOARD_HEIGHT-4)
\r
7172 && gameInfo.variant == VariantBerolina
\r
7173 && (board[fromY][fromX] == WhitePawn)
\r
7174 && (board[toY][toX] == EmptySquare)) {
\r
7175 board[fromY][fromX] = EmptySquare;
\r
7176 board[toY][toX] = WhitePawn;
\r
7177 if(oldEP & EP_BEROLIN_A) {
\r
7178 captured = board[fromY][fromX-1];
\r
7179 board[fromY][fromX-1] = EmptySquare;
\r
7180 }else{ captured = board[fromY][fromX+1];
\r
7181 board[fromY][fromX+1] = EmptySquare;
\r
7183 } else if (board[fromY][fromX] == king
\r
7184 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7185 && toY == fromY && toX > fromX+1) {
\r
7186 board[fromY][fromX] = EmptySquare;
\r
7187 board[toY][toX] = king;
\r
7188 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7189 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7190 } else if (board[fromY][fromX] == king
\r
7191 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7192 && toY == fromY && toX < fromX-1) {
\r
7193 board[fromY][fromX] = EmptySquare;
\r
7194 board[toY][toX] = king;
\r
7195 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7196 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7197 } else if (fromY == 7 && fromX == 3
\r
7198 && board[fromY][fromX] == BlackKing
\r
7199 && toY == 7 && toX == 5) {
\r
7200 board[fromY][fromX] = EmptySquare;
\r
7201 board[toY][toX] = BlackKing;
\r
7202 board[fromY][7] = EmptySquare;
\r
7203 board[toY][4] = BlackRook;
\r
7204 } else if (fromY == 7 && fromX == 3
\r
7205 && board[fromY][fromX] == BlackKing
\r
7206 && toY == 7 && toX == 1) {
\r
7207 board[fromY][fromX] = EmptySquare;
\r
7208 board[toY][toX] = BlackKing;
\r
7209 board[fromY][0] = EmptySquare;
\r
7210 board[toY][2] = BlackRook;
\r
7211 } else if (board[fromY][fromX] == BlackPawn
\r
7213 && gameInfo.variant != VariantXiangqi
\r
7215 /* black pawn promotion */
\r
7216 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7217 if (board[0][toX] == EmptySquare) {
\r
7218 board[0][toX] = BlackQueen;
\r
7220 if(gameInfo.variant==VariantBughouse ||
\r
7221 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7222 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7223 board[fromY][fromX] = EmptySquare;
\r
7224 } else if ((fromY == 3)
\r
7226 && gameInfo.variant != VariantXiangqi
\r
7227 && gameInfo.variant != VariantBerolina
\r
7228 && (board[fromY][fromX] == BlackPawn)
\r
7229 && (board[toY][toX] == EmptySquare)) {
\r
7230 board[fromY][fromX] = EmptySquare;
\r
7231 board[toY][toX] = BlackPawn;
\r
7232 captured = board[toY + 1][toX];
\r
7233 board[toY + 1][toX] = EmptySquare;
\r
7234 } else if ((fromY == 3)
\r
7236 && gameInfo.variant == VariantBerolina
\r
7237 && (board[fromY][fromX] == BlackPawn)
\r
7238 && (board[toY][toX] == EmptySquare)) {
\r
7239 board[fromY][fromX] = EmptySquare;
\r
7240 board[toY][toX] = BlackPawn;
\r
7241 if(oldEP & EP_BEROLIN_A) {
\r
7242 captured = board[fromY][fromX-1];
\r
7243 board[fromY][fromX-1] = EmptySquare;
\r
7244 }else{ captured = board[fromY][fromX+1];
\r
7245 board[fromY][fromX+1] = EmptySquare;
\r
7248 board[toY][toX] = board[fromY][fromX];
\r
7249 board[fromY][fromX] = EmptySquare;
\r
7252 /* [HGM] now we promote for Shogi, if needed */
\r
7253 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7254 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7257 if (gameInfo.holdingsWidth != 0) {
\r
7259 /* !!A lot more code needs to be written to support holdings */
\r
7260 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7261 /* penultimate board files, so they are automaticlly stored */
\r
7262 /* in the game history. */
\r
7263 if (fromY == DROP_RANK) {
\r
7264 /* Delete from holdings, by decreasing count */
\r
7265 /* and erasing image if necessary */
\r
7267 if(p < (int) BlackPawn) { /* white drop */
\r
7268 p -= (int)WhitePawn;
\r
7269 if(p >= gameInfo.holdingsSize) p = 0;
\r
7270 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7271 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7272 } else { /* black drop */
\r
7273 p -= (int)BlackPawn;
\r
7274 if(p >= gameInfo.holdingsSize) p = 0;
\r
7275 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7276 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7279 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7280 && gameInfo.variant != VariantBughouse ) {
\r
7281 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7282 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7283 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7284 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7286 p = (int) captured;
\r
7287 if (p >= (int) BlackPawn) {
\r
7288 p -= (int)BlackPawn;
\r
7289 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7290 /* in Shogi restore piece to its original first */
\r
7291 captured = (ChessSquare) (DEMOTED captured);
\r
7294 p = PieceToNumber((ChessSquare)p);
\r
7295 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7296 board[p][BOARD_WIDTH-2]++;
\r
7297 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7299 p -= (int)WhitePawn;
\r
7300 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7301 captured = (ChessSquare) (DEMOTED captured);
\r
7304 p = PieceToNumber((ChessSquare)p);
\r
7305 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7306 board[BOARD_HEIGHT-1-p][1]++;
\r
7307 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7311 } else if (gameInfo.variant == VariantAtomic) {
\r
7312 if (captured != EmptySquare) {
\r
7314 for (y = toY-1; y <= toY+1; y++) {
\r
7315 for (x = toX-1; x <= toX+1; x++) {
\r
7316 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7317 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7318 board[y][x] = EmptySquare;
\r
7322 board[toY][toX] = EmptySquare;
\r
7325 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7326 /* [HGM] Shogi promotions */
\r
7327 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7332 /* Updates forwardMostMove */
\r
7334 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7335 int fromX, fromY, toX, toY;
\r
7338 // forwardMostMove++; // [HGM] bare: moved downstream
\r
7340 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
\r
7341 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7342 piece = boards[forwardMostMove][fromY][fromX];
\r
7343 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7344 if(gameInfo.variant == VariantKnightmate)
\r
7345 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7346 if(forwardMostMove == 0) {
\r
7347 if(blackPlaysFirst)
\r
7348 fprintf(serverMoves, "%s;", second.tidy);
\r
7349 fprintf(serverMoves, "%s;", first.tidy);
\r
7350 if(!blackPlaysFirst)
\r
7351 fprintf(serverMoves, "%s;", second.tidy);
\r
7352 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7353 lastLoadFlag = loadFlag;
\r
7354 // print base move
\r
7355 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7356 // print castling suffix
\r
7357 if( toY == fromY && piece == king ) {
\r
7359 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7361 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7364 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
\r
7365 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
\r
7366 boards[forwardMostMove][toY][toX] == EmptySquare
\r
7368 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7369 // promotion suffix
\r
7370 if(promoChar != NULLCHAR)
\r
7371 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7373 fprintf(serverMoves, "/%d/%d",
\r
7374 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
\r
7375 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
\r
7376 else timeLeft = blackTimeRemaining/1000;
\r
7377 fprintf(serverMoves, "/%d", timeLeft);
\r
7379 fflush(serverMoves);
\r
7382 if (forwardMostMove+1 >= MAX_MOVES) {
\r
7383 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7388 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
\r
7389 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
\r
7390 if (commentList[forwardMostMove+1] != NULL) {
\r
7391 free(commentList[forwardMostMove+1]);
\r
7392 commentList[forwardMostMove+1] = NULL;
\r
7394 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
\r
7395 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
\r
7396 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
\r
7397 gameInfo.result = GameUnfinished;
\r
7398 if (gameInfo.resultDetails != NULL) {
\r
7399 free(gameInfo.resultDetails);
\r
7400 gameInfo.resultDetails = NULL;
\r
7402 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7403 moveList[forwardMostMove - 1]);
\r
7404 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7405 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7406 fromY, fromX, toY, toX, promoChar,
\r
7407 parseList[forwardMostMove - 1]);
\r
7408 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7409 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7410 castlingRights[forwardMostMove]) ) {
\r
7412 case MT_STALEMATE:
\r
7416 if(gameInfo.variant != VariantShogi)
\r
7417 strcat(parseList[forwardMostMove - 1], "+");
\r
7419 case MT_CHECKMATE:
\r
7420 strcat(parseList[forwardMostMove - 1], "#");
\r
7423 if (appData.debugMode) {
\r
7424 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7429 /* Updates currentMove if not pausing */
\r
7431 ShowMove(fromX, fromY, toX, toY)
\r
7433 int instant = (gameMode == PlayFromGameFile) ?
\r
7434 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7435 if(appData.noGUI) return;
\r
7436 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7438 if (forwardMostMove == currentMove + 1) {
\r
7439 AnimateMove(boards[forwardMostMove - 1],
\r
7440 fromX, fromY, toX, toY);
\r
7442 if (appData.highlightLastMove) {
\r
7443 SetHighlights(fromX, fromY, toX, toY);
\r
7446 currentMove = forwardMostMove;
\r
7449 if (instant) return;
\r
7451 DisplayMove(currentMove - 1);
\r
7452 DrawPosition(FALSE, boards[currentMove]);
\r
7453 DisplayBothClocks();
\r
7454 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7457 void SendEgtPath(ChessProgramState *cps)
\r
7458 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7459 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7461 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7464 char c, *q = name+1, *r, *s;
\r
7466 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7467 while(*p && *p != ',') *q++ = *p++;
\r
7468 *q++ = ':'; *q = 0;
\r
7469 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7470 strcmp(name, ",nalimov:") == 0 ) {
\r
7471 // take nalimov path from the menu-changeable option first, if it is defined
\r
7472 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7473 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7475 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7476 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7477 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7478 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7479 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7480 c = *r; *r = 0; // temporarily null-terminate path info
\r
7481 *--q = 0; // strip of trailig ':' from name
\r
7482 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7484 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7486 if(*p == ',') p++; // read away comma to position for next format name
\r
7491 InitChessProgram(cps, setup)
\r
7492 ChessProgramState *cps;
\r
7493 int setup; /* [HGM] needed to setup FRC opening position */
\r
7495 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7496 if (appData.noChessProgram) return;
\r
7497 hintRequested = FALSE;
\r
7498 bookRequested = FALSE;
\r
7500 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7501 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7502 if(cps->memSize) { /* [HGM] memory */
\r
7503 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7504 SendToProgram(buf, cps);
\r
7506 SendEgtPath(cps); /* [HGM] EGT */
\r
7507 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7508 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7509 SendToProgram(buf, cps);
\r
7512 SendToProgram(cps->initString, cps);
\r
7513 if (gameInfo.variant != VariantNormal &&
\r
7514 gameInfo.variant != VariantLoadable
\r
7515 /* [HGM] also send variant if board size non-standard */
\r
7516 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7518 char *v = VariantName(gameInfo.variant);
\r
7519 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7520 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7521 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7522 DisplayFatalError(buf, 0, 1);
\r
7526 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7527 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7528 if( gameInfo.variant == VariantXiangqi )
\r
7529 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7530 if( gameInfo.variant == VariantShogi )
\r
7531 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7532 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7533 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7534 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7535 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7536 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7537 if( gameInfo.variant == VariantCourier )
\r
7538 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7539 if( gameInfo.variant == VariantSuper )
\r
7540 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7541 if( gameInfo.variant == VariantGreat )
\r
7542 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7545 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7546 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7547 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7548 if(StrStr(cps->variants, b) == NULL) {
\r
7549 // specific sized variant not known, check if general sizing allowed
\r
7550 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7551 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7552 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7553 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7554 DisplayFatalError(buf, 0, 1);
\r
7557 /* [HGM] here we really should compare with the maximum supported board size */
\r
7560 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7561 sprintf(buf, "variant %s\n", b);
\r
7562 SendToProgram(buf, cps);
\r
7564 currentlyInitializedVariant = gameInfo.variant;
\r
7566 /* [HGM] send opening position in FRC to first engine */
\r
7568 SendToProgram("force\n", cps);
\r
7569 SendBoard(cps, 0);
\r
7570 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7571 setboardSpoiledMachineBlack = 1;
\r
7574 if (cps->sendICS) {
\r
7575 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7576 SendToProgram(buf, cps);
\r
7578 cps->maybeThinking = FALSE;
\r
7579 cps->offeredDraw = 0;
\r
7580 if (!appData.icsActive) {
\r
7581 SendTimeControl(cps, movesPerSession, timeControl,
\r
7582 timeIncrement, appData.searchDepth,
\r
7585 if (appData.showThinking
\r
7586 // [HGM] thinking: four options require thinking output to be sent
\r
7587 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7589 SendToProgram("post\n", cps);
\r
7591 SendToProgram("hard\n", cps);
\r
7592 if (!appData.ponderNextMove) {
\r
7593 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7594 it without being sure what state we are in first. "hard"
\r
7595 is not a toggle, so that one is OK.
\r
7597 SendToProgram("easy\n", cps);
\r
7599 if (cps->usePing) {
\r
7600 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7601 SendToProgram(buf, cps);
\r
7603 cps->initDone = TRUE;
\r
7608 StartChessProgram(cps)
\r
7609 ChessProgramState *cps;
\r
7611 char buf[MSG_SIZ];
\r
7614 if (appData.noChessProgram) return;
\r
7615 cps->initDone = FALSE;
\r
7617 if (strcmp(cps->host, "localhost") == 0) {
\r
7618 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7619 } else if (*appData.remoteShell == NULLCHAR) {
\r
7620 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7622 if (*appData.remoteUser == NULLCHAR) {
\r
7623 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7626 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7627 cps->host, appData.remoteUser, cps->program);
\r
7629 err = StartChildProcess(buf, "", &cps->pr);
\r
7633 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7634 DisplayFatalError(buf, err, 1);
\r
7640 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7641 if (cps->protocolVersion > 1) {
\r
7642 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7643 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7644 cps->comboCnt = 0; // and values of combo boxes
\r
7645 SendToProgram(buf, cps);
\r
7647 SendToProgram("xboard\n", cps);
\r
7653 TwoMachinesEventIfReady P((void))
\r
7655 if (first.lastPing != first.lastPong) {
\r
7656 DisplayMessage("", _("Waiting for first chess program"));
\r
7657 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7660 if (second.lastPing != second.lastPong) {
\r
7661 DisplayMessage("", _("Waiting for second chess program"));
\r
7662 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7666 TwoMachinesEvent();
\r
7670 NextMatchGame P((void))
\r
7672 int index; /* [HGM] autoinc: step lod index during match */
\r
7673 Reset(FALSE, TRUE);
\r
7674 if (*appData.loadGameFile != NULLCHAR) {
\r
7675 index = appData.loadGameIndex;
\r
7676 if(index < 0) { // [HGM] autoinc
\r
7677 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7678 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7680 LoadGameFromFile(appData.loadGameFile,
\r
7682 appData.loadGameFile, FALSE);
\r
7683 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7684 index = appData.loadPositionIndex;
\r
7685 if(index < 0) { // [HGM] autoinc
\r
7686 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7687 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7689 LoadPositionFromFile(appData.loadPositionFile,
\r
7691 appData.loadPositionFile);
\r
7693 TwoMachinesEventIfReady();
\r
7696 void UserAdjudicationEvent( int result )
\r
7698 ChessMove gameResult = GameIsDrawn;
\r
7700 if( result > 0 ) {
\r
7701 gameResult = WhiteWins;
\r
7703 else if( result < 0 ) {
\r
7704 gameResult = BlackWins;
\r
7707 if( gameMode == TwoMachinesPlay ) {
\r
7708 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7714 GameEnds(result, resultDetails, whosays)
\r
7716 char *resultDetails;
\r
7719 GameMode nextGameMode;
\r
7721 char buf[MSG_SIZ];
\r
7723 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7726 if (appData.debugMode) {
\r
7727 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7728 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7731 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7732 /* If we are playing on ICS, the server decides when the
\r
7733 game is over, but the engine can offer to draw, claim
\r
7734 a draw, or resign.
\r
7737 if (appData.zippyPlay && first.initDone) {
\r
7738 if (result == GameIsDrawn) {
\r
7739 /* In case draw still needs to be claimed */
\r
7740 SendToICS(ics_prefix);
\r
7741 SendToICS("draw\n");
\r
7742 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7743 SendToICS(ics_prefix);
\r
7744 SendToICS("resign\n");
\r
7748 endingGame = 0; /* [HGM] crash */
\r
7752 /* If we're loading the game from a file, stop */
\r
7753 if (whosays == GE_FILE) {
\r
7754 (void) StopLoadGameTimer();
\r
7755 gameFileFP = NULL;
\r
7758 /* Cancel draw offers */
\r
7759 first.offeredDraw = second.offeredDraw = 0;
\r
7761 /* If this is an ICS game, only ICS can really say it's done;
\r
7762 if not, anyone can. */
\r
7763 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7764 gameMode == IcsPlayingBlack ||
\r
7765 gameMode == IcsObserving ||
\r
7766 gameMode == IcsExamining);
\r
7768 if (!isIcsGame || whosays == GE_ICS) {
\r
7769 /* OK -- not an ICS game, or ICS said it was done */
\r
7771 if (!isIcsGame && !appData.noChessProgram)
\r
7772 SetUserThinkingEnables();
\r
7774 /* [HGM] if a machine claims the game end we verify this claim */
\r
7775 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7776 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7779 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7780 first.twoMachinesColor[0] :
\r
7781 second.twoMachinesColor[0] ;
\r
7782 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7783 (result == WhiteWins && claimer == 'w' ||
\r
7784 result == BlackWins && claimer == 'b' ) ) {
\r
7785 if (appData.debugMode) {
\r
7786 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7787 result, epStatus[forwardMostMove], forwardMostMove);
\r
7789 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7790 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7791 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7792 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7793 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7794 resultDetails = buf;
\r
7797 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7798 && (forwardMostMove <= backwardMostMove ||
\r
7799 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7800 (claimer=='b')==(forwardMostMove&1))
\r
7802 /* [HGM] verify: draws that were not flagged are false claims */
\r
7803 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7804 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7805 resultDetails = buf;
\r
7807 /* (Claiming a loss is accepted no questions asked!) */
\r
7809 /* [HGM] bare: don't allow bare King to win */
\r
7810 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7811 && result != GameIsDrawn)
\r
7812 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7813 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7814 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7815 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7817 if (appData.debugMode) {
\r
7818 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7819 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7822 result = GameIsDrawn;
\r
7823 sprintf(buf, "%s but bare king", resultDetails);
\r
7824 resultDetails = buf;
\r
7830 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7831 if(result==WhiteWins) c = '+';
\r
7832 if(result==BlackWins) c = '-';
\r
7833 if(resultDetails != NULL)
\r
7834 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7836 if (resultDetails != NULL) {
\r
7837 gameInfo.result = result;
\r
7838 gameInfo.resultDetails = StrSave(resultDetails);
\r
7840 /* display last move only if game was not loaded from file */
\r
7841 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7842 DisplayMove(currentMove - 1);
\r
7844 if (forwardMostMove != 0) {
\r
7845 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7846 if (*appData.saveGameFile != NULLCHAR) {
\r
7847 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7848 } else if (appData.autoSaveGames) {
\r
7851 if (*appData.savePositionFile != NULLCHAR) {
\r
7852 SavePositionToFile(appData.savePositionFile);
\r
7857 /* Tell program how game ended in case it is learning */
\r
7858 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7859 /* engine died and we got here through time loss. In that */
\r
7860 /* case we will get a fatal error writing the pipe, which */
\r
7861 /* would otherwise lose us the PGN. */
\r
7862 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7863 /* output during GameEnds should never be fatal anymore */
\r
7864 if (gameMode == MachinePlaysWhite ||
\r
7865 gameMode == MachinePlaysBlack ||
\r
7866 gameMode == TwoMachinesPlay ||
\r
7867 gameMode == IcsPlayingWhite ||
\r
7868 gameMode == IcsPlayingBlack ||
\r
7869 gameMode == BeginningOfGame) {
\r
7870 char buf[MSG_SIZ];
\r
7871 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7873 if (first.pr != NoProc) {
\r
7874 SendToProgram(buf, &first);
\r
7876 if (second.pr != NoProc &&
\r
7877 gameMode == TwoMachinesPlay) {
\r
7878 SendToProgram(buf, &second);
\r
7883 if (appData.icsActive) {
\r
7884 if (appData.quietPlay &&
\r
7885 (gameMode == IcsPlayingWhite ||
\r
7886 gameMode == IcsPlayingBlack)) {
\r
7887 SendToICS(ics_prefix);
\r
7888 SendToICS("set shout 1\n");
\r
7890 nextGameMode = IcsIdle;
\r
7891 ics_user_moved = FALSE;
\r
7892 /* clean up premove. It's ugly when the game has ended and the
\r
7893 * premove highlights are still on the board.
\r
7896 gotPremove = FALSE;
\r
7897 ClearPremoveHighlights();
\r
7898 DrawPosition(FALSE, boards[currentMove]);
\r
7900 if (whosays == GE_ICS) {
\r
7903 if (gameMode == IcsPlayingWhite)
\r
7904 PlayIcsWinSound();
\r
7905 else if(gameMode == IcsPlayingBlack)
\r
7906 PlayIcsLossSound();
\r
7909 if (gameMode == IcsPlayingBlack)
\r
7910 PlayIcsWinSound();
\r
7911 else if(gameMode == IcsPlayingWhite)
\r
7912 PlayIcsLossSound();
\r
7915 PlayIcsDrawSound();
\r
7918 PlayIcsUnfinishedSound();
\r
7921 } else if (gameMode == EditGame ||
\r
7922 gameMode == PlayFromGameFile ||
\r
7923 gameMode == AnalyzeMode ||
\r
7924 gameMode == AnalyzeFile) {
\r
7925 nextGameMode = gameMode;
\r
7927 nextGameMode = EndOfGame;
\r
7932 nextGameMode = gameMode;
\r
7935 if (appData.noChessProgram) {
\r
7936 gameMode = nextGameMode;
\r
7938 endingGame = 0; /* [HGM] crash */
\r
7942 if (first.reuse) {
\r
7943 /* Put first chess program into idle state */
\r
7944 if (first.pr != NoProc &&
\r
7945 (gameMode == MachinePlaysWhite ||
\r
7946 gameMode == MachinePlaysBlack ||
\r
7947 gameMode == TwoMachinesPlay ||
\r
7948 gameMode == IcsPlayingWhite ||
\r
7949 gameMode == IcsPlayingBlack ||
\r
7950 gameMode == BeginningOfGame)) {
\r
7951 SendToProgram("force\n", &first);
\r
7952 if (first.usePing) {
\r
7953 char buf[MSG_SIZ];
\r
7954 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7955 SendToProgram(buf, &first);
\r
7958 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7959 /* Kill off first chess program */
\r
7960 if (first.isr != NULL)
\r
7961 RemoveInputSource(first.isr);
\r
7964 if (first.pr != NoProc) {
\r
7965 ExitAnalyzeMode();
\r
7966 DoSleep( appData.delayBeforeQuit );
\r
7967 SendToProgram("quit\n", &first);
\r
7968 DoSleep( appData.delayAfterQuit );
\r
7969 DestroyChildProcess(first.pr, first.useSigterm);
\r
7971 first.pr = NoProc;
\r
7973 if (second.reuse) {
\r
7974 /* Put second chess program into idle state */
\r
7975 if (second.pr != NoProc &&
\r
7976 gameMode == TwoMachinesPlay) {
\r
7977 SendToProgram("force\n", &second);
\r
7978 if (second.usePing) {
\r
7979 char buf[MSG_SIZ];
\r
7980 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7981 SendToProgram(buf, &second);
\r
7984 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7985 /* Kill off second chess program */
\r
7986 if (second.isr != NULL)
\r
7987 RemoveInputSource(second.isr);
\r
7988 second.isr = NULL;
\r
7990 if (second.pr != NoProc) {
\r
7991 DoSleep( appData.delayBeforeQuit );
\r
7992 SendToProgram("quit\n", &second);
\r
7993 DoSleep( appData.delayAfterQuit );
\r
7994 DestroyChildProcess(second.pr, second.useSigterm);
\r
7996 second.pr = NoProc;
\r
7999 if (matchMode && gameMode == TwoMachinesPlay) {
\r
8002 if (first.twoMachinesColor[0] == 'w') {
\r
8003 first.matchWins++;
\r
8005 second.matchWins++;
\r
8009 if (first.twoMachinesColor[0] == 'b') {
\r
8010 first.matchWins++;
\r
8012 second.matchWins++;
\r
8018 if (matchGame < appData.matchGames) {
\r
8020 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8021 tmp = first.twoMachinesColor;
\r
8022 first.twoMachinesColor = second.twoMachinesColor;
\r
8023 second.twoMachinesColor = tmp;
\r
8025 gameMode = nextGameMode;
\r
8027 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8028 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8029 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8030 endingGame = 0; /* [HGM] crash */
\r
8033 char buf[MSG_SIZ];
\r
8034 gameMode = nextGameMode;
\r
8035 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8036 first.tidy, second.tidy,
\r
8037 first.matchWins, second.matchWins,
\r
8038 appData.matchGames - (first.matchWins + second.matchWins));
\r
8039 DisplayFatalError(buf, 0, 0);
\r
8042 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8043 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8044 ExitAnalyzeMode();
\r
8045 gameMode = nextGameMode;
\r
8047 endingGame = 0; /* [HGM] crash */
\r
8050 /* Assumes program was just initialized (initString sent).
\r
8051 Leaves program in force mode. */
\r
8053 FeedMovesToProgram(cps, upto)
\r
8054 ChessProgramState *cps;
\r
8059 if (appData.debugMode)
\r
8060 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8061 startedFromSetupPosition ? "position and " : "",
\r
8062 backwardMostMove, upto, cps->which);
\r
8063 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8064 // [HGM] variantswitch: make engine aware of new variant
\r
8065 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8066 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8067 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8068 SendToProgram(buf, cps);
\r
8069 currentlyInitializedVariant = gameInfo.variant;
\r
8071 SendToProgram("force\n", cps);
\r
8072 if (startedFromSetupPosition) {
\r
8073 SendBoard(cps, backwardMostMove);
\r
8074 if (appData.debugMode) {
\r
8075 fprintf(debugFP, "feedMoves\n");
\r
8078 for (i = backwardMostMove; i < upto; i++) {
\r
8079 SendMoveToProgram(i, cps);
\r
8085 ResurrectChessProgram()
\r
8087 /* The chess program may have exited.
\r
8088 If so, restart it and feed it all the moves made so far. */
\r
8090 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8092 StartChessProgram(&first);
\r
8093 InitChessProgram(&first, FALSE);
\r
8094 FeedMovesToProgram(&first, currentMove);
\r
8096 if (!first.sendTime) {
\r
8097 /* can't tell gnuchess what its clock should read,
\r
8098 so we bow to its notion. */
\r
8100 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8101 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8104 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8105 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8106 SendToProgram("analyze\n", &first);
\r
8107 first.analyzing = TRUE;
\r
8112 * Button procedures
\r
8115 Reset(redraw, init)
\r
8120 if (appData.debugMode) {
\r
8121 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8122 redraw, init, gameMode);
\r
8124 pausing = pauseExamInvalid = FALSE;
\r
8125 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8127 whiteFlag = blackFlag = FALSE;
\r
8128 userOfferedDraw = FALSE;
\r
8129 hintRequested = bookRequested = FALSE;
\r
8130 first.maybeThinking = FALSE;
\r
8131 second.maybeThinking = FALSE;
\r
8132 first.bookSuspend = FALSE; // [HGM] book
\r
8133 second.bookSuspend = FALSE;
\r
8134 thinkOutput[0] = NULLCHAR;
\r
8135 lastHint[0] = NULLCHAR;
\r
8136 ClearGameInfo(&gameInfo);
\r
8137 gameInfo.variant = StringToVariant(appData.variant);
\r
8138 ics_user_moved = ics_clock_paused = FALSE;
\r
8139 ics_getting_history = H_FALSE;
\r
8141 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8142 ClearProgramStats();
\r
8143 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8146 ClearHighlights();
\r
8147 flipView = appData.flipView;
\r
8148 ClearPremoveHighlights();
\r
8149 gotPremove = FALSE;
\r
8150 alarmSounded = FALSE;
\r
8152 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8153 if(appData.serverMovesName != NULL) {
\r
8154 /* [HGM] prepare to make moves file for broadcasting */
\r
8155 clock_t t = clock();
\r
8156 if(serverMoves != NULL) fclose(serverMoves);
\r
8157 serverMoves = fopen(appData.serverMovesName, "r");
\r
8158 if(serverMoves != NULL) {
\r
8159 fclose(serverMoves);
\r
8160 /* delay 15 sec before overwriting, so all clients can see end */
\r
8161 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8163 serverMoves = fopen(appData.serverMovesName, "w");
\r
8166 ExitAnalyzeMode();
\r
8167 gameMode = BeginningOfGame;
\r
8169 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8170 InitPosition(redraw);
\r
8171 for (i = 0; i < MAX_MOVES; i++) {
\r
8172 if (commentList[i] != NULL) {
\r
8173 free(commentList[i]);
\r
8174 commentList[i] = NULL;
\r
8178 timeRemaining[0][0] = whiteTimeRemaining;
\r
8179 timeRemaining[1][0] = blackTimeRemaining;
\r
8180 if (first.pr == NULL) {
\r
8181 StartChessProgram(&first);
\r
8184 InitChessProgram(&first, startedFromSetupPosition);
\r
8187 DisplayMessage("", "");
\r
8188 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8192 AutoPlayGameLoop()
\r
8195 if (!AutoPlayOneMove())
\r
8197 if (matchMode || appData.timeDelay == 0)
\r
8199 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8201 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8210 int fromX, fromY, toX, toY;
\r
8212 if (appData.debugMode) {
\r
8213 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8216 if (gameMode != PlayFromGameFile)
\r
8219 if (currentMove >= forwardMostMove) {
\r
8220 gameMode = EditGame;
\r
8223 /* [AS] Clear current move marker at the end of a game */
\r
8224 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8229 toX = moveList[currentMove][2] - AAA;
\r
8230 toY = moveList[currentMove][3] - ONE;
\r
8232 if (moveList[currentMove][1] == '@') {
\r
8233 if (appData.highlightLastMove) {
\r
8234 SetHighlights(-1, -1, toX, toY);
\r
8237 fromX = moveList[currentMove][0] - AAA;
\r
8238 fromY = moveList[currentMove][1] - ONE;
\r
8240 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8242 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8244 if (appData.highlightLastMove) {
\r
8245 SetHighlights(fromX, fromY, toX, toY);
\r
8248 DisplayMove(currentMove);
\r
8249 SendMoveToProgram(currentMove++, &first);
\r
8250 DisplayBothClocks();
\r
8251 DrawPosition(FALSE, boards[currentMove]);
\r
8252 // [HGM] PV info: always display, routine tests if empty
\r
8253 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8259 LoadGameOneMove(readAhead)
\r
8260 ChessMove readAhead;
\r
8262 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8263 char promoChar = NULLCHAR;
\r
8264 ChessMove moveType;
\r
8265 char move[MSG_SIZ];
\r
8268 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8269 gameMode != AnalyzeMode && gameMode != Training) {
\r
8270 gameFileFP = NULL;
\r
8274 yyboardindex = forwardMostMove;
\r
8275 if (readAhead != (ChessMove)0) {
\r
8276 moveType = readAhead;
\r
8278 if (gameFileFP == NULL)
\r
8280 moveType = (ChessMove) yylex();
\r
8284 switch (moveType) {
\r
8286 if (appData.debugMode)
\r
8287 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8289 if (*p == '{' || *p == '[' || *p == '(') {
\r
8290 p[strlen(p) - 1] = NULLCHAR;
\r
8294 /* append the comment but don't display it */
\r
8295 while (*p == '\n') p++;
\r
8296 AppendComment(currentMove, p);
\r
8299 case WhiteCapturesEnPassant:
\r
8300 case BlackCapturesEnPassant:
\r
8301 case WhitePromotionChancellor:
\r
8302 case BlackPromotionChancellor:
\r
8303 case WhitePromotionArchbishop:
\r
8304 case BlackPromotionArchbishop:
\r
8305 case WhitePromotionCentaur:
\r
8306 case BlackPromotionCentaur:
\r
8307 case WhitePromotionQueen:
\r
8308 case BlackPromotionQueen:
\r
8309 case WhitePromotionRook:
\r
8310 case BlackPromotionRook:
\r
8311 case WhitePromotionBishop:
\r
8312 case BlackPromotionBishop:
\r
8313 case WhitePromotionKnight:
\r
8314 case BlackPromotionKnight:
\r
8315 case WhitePromotionKing:
\r
8316 case BlackPromotionKing:
\r
8318 case WhiteKingSideCastle:
\r
8319 case WhiteQueenSideCastle:
\r
8320 case BlackKingSideCastle:
\r
8321 case BlackQueenSideCastle:
\r
8322 case WhiteKingSideCastleWild:
\r
8323 case WhiteQueenSideCastleWild:
\r
8324 case BlackKingSideCastleWild:
\r
8325 case BlackQueenSideCastleWild:
\r
8327 case WhiteHSideCastleFR:
\r
8328 case WhiteASideCastleFR:
\r
8329 case BlackHSideCastleFR:
\r
8330 case BlackASideCastleFR:
\r
8332 if (appData.debugMode)
\r
8333 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8334 fromX = currentMoveString[0] - AAA;
\r
8335 fromY = currentMoveString[1] - ONE;
\r
8336 toX = currentMoveString[2] - AAA;
\r
8337 toY = currentMoveString[3] - ONE;
\r
8338 promoChar = currentMoveString[4];
\r
8343 if (appData.debugMode)
\r
8344 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8345 fromX = moveType == WhiteDrop ?
\r
8346 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8347 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8348 fromY = DROP_RANK;
\r
8349 toX = currentMoveString[2] - AAA;
\r
8350 toY = currentMoveString[3] - ONE;
\r
8356 case GameUnfinished:
\r
8357 if (appData.debugMode)
\r
8358 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8359 p = strchr(yy_text, '{');
\r
8360 if (p == NULL) p = strchr(yy_text, '(');
\r
8363 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8365 q = strchr(p, *p == '{' ? '}' : ')');
\r
8366 if (q != NULL) *q = NULLCHAR;
\r
8369 GameEnds(moveType, p, GE_FILE);
\r
8371 if (cmailMsgLoaded) {
\r
8372 ClearHighlights();
\r
8373 flipView = WhiteOnMove(currentMove);
\r
8374 if (moveType == GameUnfinished) flipView = !flipView;
\r
8375 if (appData.debugMode)
\r
8376 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8380 case (ChessMove) 0: /* end of file */
\r
8381 if (appData.debugMode)
\r
8382 fprintf(debugFP, "Parser hit end of file\n");
\r
8383 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8384 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8388 case MT_CHECKMATE:
\r
8389 if (WhiteOnMove(currentMove)) {
\r
8390 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8392 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8395 case MT_STALEMATE:
\r
8396 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8402 case MoveNumberOne:
\r
8403 if (lastLoadGameStart == GNUChessGame) {
\r
8404 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8405 if (appData.debugMode)
\r
8406 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8407 yy_text, (int) moveType);
\r
8408 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8410 /* else fall thru */
\r
8413 case GNUChessGame:
\r
8415 /* Reached start of next game in file */
\r
8416 if (appData.debugMode)
\r
8417 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8418 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8419 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8423 case MT_CHECKMATE:
\r
8424 if (WhiteOnMove(currentMove)) {
\r
8425 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8427 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8430 case MT_STALEMATE:
\r
8431 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8437 case PositionDiagram: /* should not happen; ignore */
\r
8438 case ElapsedTime: /* ignore */
\r
8439 case NAG: /* ignore */
\r
8440 if (appData.debugMode)
\r
8441 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8442 yy_text, (int) moveType);
\r
8443 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8446 if (appData.testLegality) {
\r
8447 if (appData.debugMode)
\r
8448 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8449 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8450 (forwardMostMove / 2) + 1,
\r
8451 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8452 DisplayError(move, 0);
\r
8455 if (appData.debugMode)
\r
8456 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8457 yy_text, currentMoveString);
\r
8458 fromX = currentMoveString[0] - AAA;
\r
8459 fromY = currentMoveString[1] - ONE;
\r
8460 toX = currentMoveString[2] - AAA;
\r
8461 toY = currentMoveString[3] - ONE;
\r
8462 promoChar = currentMoveString[4];
\r
8466 case AmbiguousMove:
\r
8467 if (appData.debugMode)
\r
8468 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8469 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8470 (forwardMostMove / 2) + 1,
\r
8471 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8472 DisplayError(move, 0);
\r
8477 case ImpossibleMove:
\r
8478 if (appData.debugMode)
\r
8479 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8480 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8481 (forwardMostMove / 2) + 1,
\r
8482 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8483 DisplayError(move, 0);
\r
8489 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8490 DrawPosition(FALSE, boards[currentMove]);
\r
8491 DisplayBothClocks();
\r
8492 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8493 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8495 (void) StopLoadGameTimer();
\r
8496 gameFileFP = NULL;
\r
8497 cmailOldMove = forwardMostMove;
\r
8500 /* currentMoveString is set as a side-effect of yylex */
\r
8501 strcat(currentMoveString, "\n");
\r
8502 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8504 thinkOutput[0] = NULLCHAR;
\r
8505 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8506 currentMove = forwardMostMove;
\r
8511 /* Load the nth game from the given file */
\r
8513 LoadGameFromFile(filename, n, title, useList)
\r
8517 /*Boolean*/ int useList;
\r
8520 char buf[MSG_SIZ];
\r
8522 if (strcmp(filename, "-") == 0) {
\r
8526 f = fopen(filename, "rb");
\r
8528 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8529 DisplayError(buf, errno);
\r
8533 if (fseek(f, 0, 0) == -1) {
\r
8534 /* f is not seekable; probably a pipe */
\r
8537 if (useList && n == 0) {
\r
8538 int error = GameListBuild(f);
\r
8540 DisplayError(_("Cannot build game list"), error);
\r
8541 } else if (!ListEmpty(&gameList) &&
\r
8542 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8543 GameListPopUp(f, title);
\r
8546 GameListDestroy();
\r
8549 if (n == 0) n = 1;
\r
8550 return LoadGame(f, n, title, FALSE);
\r
8555 MakeRegisteredMove()
\r
8557 int fromX, fromY, toX, toY;
\r
8559 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8560 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8563 if (appData.debugMode)
\r
8564 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8565 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8567 thinkOutput[0] = NULLCHAR;
\r
8568 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8569 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8570 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8571 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8572 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8573 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8574 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8575 ShowMove(fromX, fromY, toX, toY);
\r
8577 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8578 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8583 case MT_CHECKMATE:
\r
8584 if (WhiteOnMove(currentMove)) {
\r
8585 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8587 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8591 case MT_STALEMATE:
\r
8592 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8598 case CMAIL_RESIGN:
\r
8599 if (WhiteOnMove(currentMove)) {
\r
8600 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8602 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8606 case CMAIL_ACCEPT:
\r
8607 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8618 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8620 CmailLoadGame(f, gameNumber, title, useList)
\r
8628 if (gameNumber > nCmailGames) {
\r
8629 DisplayError(_("No more games in this message"), 0);
\r
8632 if (f == lastLoadGameFP) {
\r
8633 int offset = gameNumber - lastLoadGameNumber;
\r
8634 if (offset == 0) {
\r
8635 cmailMsg[0] = NULLCHAR;
\r
8636 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8637 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8638 nCmailMovesRegistered--;
\r
8640 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8641 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8642 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8645 if (! RegisterMove()) return FALSE;
\r
8649 retVal = LoadGame(f, gameNumber, title, useList);
\r
8651 /* Make move registered during previous look at this game, if any */
\r
8652 MakeRegisteredMove();
\r
8654 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8655 commentList[currentMove]
\r
8656 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8657 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8663 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8665 ReloadGame(offset)
\r
8668 int gameNumber = lastLoadGameNumber + offset;
\r
8669 if (lastLoadGameFP == NULL) {
\r
8670 DisplayError(_("No game has been loaded yet"), 0);
\r
8673 if (gameNumber <= 0) {
\r
8674 DisplayError(_("Can't back up any further"), 0);
\r
8677 if (cmailMsgLoaded) {
\r
8678 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8679 lastLoadGameTitle, lastLoadGameUseList);
\r
8681 return LoadGame(lastLoadGameFP, gameNumber,
\r
8682 lastLoadGameTitle, lastLoadGameUseList);
\r
8688 /* Load the nth game from open file f */
\r
8690 LoadGame(f, gameNumber, title, useList)
\r
8697 char buf[MSG_SIZ];
\r
8698 int gn = gameNumber;
\r
8699 ListGame *lg = NULL;
\r
8700 int numPGNTags = 0;
\r
8702 GameMode oldGameMode;
\r
8703 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8705 if (appData.debugMode)
\r
8706 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8708 if (gameMode == Training )
\r
8709 SetTrainingModeOff();
\r
8711 oldGameMode = gameMode;
\r
8712 if (gameMode != BeginningOfGame) {
\r
8713 Reset(FALSE, TRUE);
\r
8717 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8718 fclose(lastLoadGameFP);
\r
8722 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8725 fseek(f, lg->offset, 0);
\r
8726 GameListHighlight(gameNumber);
\r
8730 DisplayError(_("Game number out of range"), 0);
\r
8734 GameListDestroy();
\r
8735 if (fseek(f, 0, 0) == -1) {
\r
8736 if (f == lastLoadGameFP ?
\r
8737 gameNumber == lastLoadGameNumber + 1 :
\r
8738 gameNumber == 1) {
\r
8741 DisplayError(_("Can't seek on game file"), 0);
\r
8746 lastLoadGameFP = f;
\r
8747 lastLoadGameNumber = gameNumber;
\r
8748 strcpy(lastLoadGameTitle, title);
\r
8749 lastLoadGameUseList = useList;
\r
8753 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8754 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8755 lg->gameInfo.black);
\r
8756 DisplayTitle(buf);
\r
8757 } else if (*title != NULLCHAR) {
\r
8758 if (gameNumber > 1) {
\r
8759 sprintf(buf, "%s %d", title, gameNumber);
\r
8760 DisplayTitle(buf);
\r
8762 DisplayTitle(title);
\r
8766 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8767 gameMode = PlayFromGameFile;
\r
8771 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8772 CopyBoard(boards[0], initialPosition);
\r
8776 * Skip the first gn-1 games in the file.
\r
8777 * Also skip over anything that precedes an identifiable
\r
8778 * start of game marker, to avoid being confused by
\r
8779 * garbage at the start of the file. Currently
\r
8780 * recognized start of game markers are the move number "1",
\r
8781 * the pattern "gnuchess .* game", the pattern
\r
8782 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8783 * A game that starts with one of the latter two patterns
\r
8784 * will also have a move number 1, possibly
\r
8785 * following a position diagram.
\r
8786 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8787 * start with an unnumbered move. Does that break anything?
\r
8789 cm = lastLoadGameStart = (ChessMove) 0;
\r
8791 yyboardindex = forwardMostMove;
\r
8792 cm = (ChessMove) yylex();
\r
8794 case (ChessMove) 0:
\r
8795 if (cmailMsgLoaded) {
\r
8796 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8798 Reset(TRUE, TRUE);
\r
8799 DisplayError(_("Game not found in file"), 0);
\r
8803 case GNUChessGame:
\r
8806 lastLoadGameStart = cm;
\r
8809 case MoveNumberOne:
\r
8810 switch (lastLoadGameStart) {
\r
8811 case GNUChessGame:
\r
8815 case MoveNumberOne:
\r
8816 case (ChessMove) 0:
\r
8817 gn--; /* count this game */
\r
8818 lastLoadGameStart = cm;
\r
8827 switch (lastLoadGameStart) {
\r
8828 case GNUChessGame:
\r
8830 case MoveNumberOne:
\r
8831 case (ChessMove) 0:
\r
8832 gn--; /* count this game */
\r
8833 lastLoadGameStart = cm;
\r
8836 lastLoadGameStart = cm; /* game counted already */
\r
8844 yyboardindex = forwardMostMove;
\r
8845 cm = (ChessMove) yylex();
\r
8846 } while (cm == PGNTag || cm == Comment);
\r
8853 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8854 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8855 != CMAIL_OLD_RESULT) {
\r
8856 nCmailResults ++ ;
\r
8857 cmailResult[ CMAIL_MAX_GAMES
\r
8858 - gn - 1] = CMAIL_OLD_RESULT;
\r
8864 /* Only a NormalMove can be at the start of a game
\r
8865 * without a position diagram. */
\r
8866 if (lastLoadGameStart == (ChessMove) 0) {
\r
8868 lastLoadGameStart = MoveNumberOne;
\r
8877 if (appData.debugMode)
\r
8878 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8880 if (cm == XBoardGame) {
\r
8881 /* Skip any header junk before position diagram and/or move 1 */
\r
8883 yyboardindex = forwardMostMove;
\r
8884 cm = (ChessMove) yylex();
\r
8886 if (cm == (ChessMove) 0 ||
\r
8887 cm == GNUChessGame || cm == XBoardGame) {
\r
8888 /* Empty game; pretend end-of-file and handle later */
\r
8889 cm = (ChessMove) 0;
\r
8893 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8894 cm == PGNTag || cm == Comment)
\r
8897 } else if (cm == GNUChessGame) {
\r
8898 if (gameInfo.event != NULL) {
\r
8899 free(gameInfo.event);
\r
8901 gameInfo.event = StrSave(yy_text);
\r
8904 startedFromSetupPosition = FALSE;
\r
8905 while (cm == PGNTag) {
\r
8906 if (appData.debugMode)
\r
8907 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8908 err = ParsePGNTag(yy_text, &gameInfo);
\r
8909 if (!err) numPGNTags++;
\r
8911 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8912 if(gameInfo.variant != oldVariant) {
\r
8913 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8914 InitPosition(TRUE);
\r
8915 oldVariant = gameInfo.variant;
\r
8916 if (appData.debugMode)
\r
8917 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8921 if (gameInfo.fen != NULL) {
\r
8922 Board initial_position;
\r
8923 startedFromSetupPosition = TRUE;
\r
8924 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8925 Reset(TRUE, TRUE);
\r
8926 DisplayError(_("Bad FEN position in file"), 0);
\r
8929 CopyBoard(boards[0], initial_position);
\r
8930 if (blackPlaysFirst) {
\r
8931 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8932 CopyBoard(boards[1], initial_position);
\r
8933 strcpy(moveList[0], "");
\r
8934 strcpy(parseList[0], "");
\r
8935 timeRemaining[0][1] = whiteTimeRemaining;
\r
8936 timeRemaining[1][1] = blackTimeRemaining;
\r
8937 if (commentList[0] != NULL) {
\r
8938 commentList[1] = commentList[0];
\r
8939 commentList[0] = NULL;
\r
8942 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8944 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8946 initialRulePlies = FENrulePlies;
\r
8947 epStatus[forwardMostMove] = FENepStatus;
\r
8948 for( i=0; i< nrCastlingRights; i++ )
\r
8949 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8951 yyboardindex = forwardMostMove;
\r
8952 free(gameInfo.fen);
\r
8953 gameInfo.fen = NULL;
\r
8956 yyboardindex = forwardMostMove;
\r
8957 cm = (ChessMove) yylex();
\r
8959 /* Handle comments interspersed among the tags */
\r
8960 while (cm == Comment) {
\r
8962 if (appData.debugMode)
\r
8963 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8965 if (*p == '{' || *p == '[' || *p == '(') {
\r
8966 p[strlen(p) - 1] = NULLCHAR;
\r
8969 while (*p == '\n') p++;
\r
8970 AppendComment(currentMove, p);
\r
8971 yyboardindex = forwardMostMove;
\r
8972 cm = (ChessMove) yylex();
\r
8976 /* don't rely on existence of Event tag since if game was
\r
8977 * pasted from clipboard the Event tag may not exist
\r
8979 if (numPGNTags > 0){
\r
8981 if (gameInfo.variant == VariantNormal) {
\r
8982 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8985 if( appData.autoDisplayTags ) {
\r
8986 tags = PGNTags(&gameInfo);
\r
8987 TagsPopUp(tags, CmailMsg());
\r
8992 /* Make something up, but don't display it now */
\r
8997 if (cm == PositionDiagram) {
\r
9000 Board initial_position;
\r
9002 if (appData.debugMode)
\r
9003 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9005 if (!startedFromSetupPosition) {
\r
9007 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9008 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9018 initial_position[i][j++] = CharToPiece(*p);
\r
9021 while (*p == ' ' || *p == '\t' ||
\r
9022 *p == '\n' || *p == '\r') p++;
\r
9024 if (strncmp(p, "black", strlen("black"))==0)
\r
9025 blackPlaysFirst = TRUE;
\r
9027 blackPlaysFirst = FALSE;
\r
9028 startedFromSetupPosition = TRUE;
\r
9030 CopyBoard(boards[0], initial_position);
\r
9031 if (blackPlaysFirst) {
\r
9032 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9033 CopyBoard(boards[1], initial_position);
\r
9034 strcpy(moveList[0], "");
\r
9035 strcpy(parseList[0], "");
\r
9036 timeRemaining[0][1] = whiteTimeRemaining;
\r
9037 timeRemaining[1][1] = blackTimeRemaining;
\r
9038 if (commentList[0] != NULL) {
\r
9039 commentList[1] = commentList[0];
\r
9040 commentList[0] = NULL;
\r
9043 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9046 yyboardindex = forwardMostMove;
\r
9047 cm = (ChessMove) yylex();
\r
9050 if (first.pr == NoProc) {
\r
9051 StartChessProgram(&first);
\r
9053 InitChessProgram(&first, FALSE);
\r
9054 SendToProgram("force\n", &first);
\r
9055 if (startedFromSetupPosition) {
\r
9056 SendBoard(&first, forwardMostMove);
\r
9057 if (appData.debugMode) {
\r
9058 fprintf(debugFP, "Load Game\n");
\r
9060 DisplayBothClocks();
\r
9063 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9064 loadFlag = appData.suppressLoadMoves;
\r
9066 while (cm == Comment) {
\r
9068 if (appData.debugMode)
\r
9069 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9071 if (*p == '{' || *p == '[' || *p == '(') {
\r
9072 p[strlen(p) - 1] = NULLCHAR;
\r
9075 while (*p == '\n') p++;
\r
9076 AppendComment(currentMove, p);
\r
9077 yyboardindex = forwardMostMove;
\r
9078 cm = (ChessMove) yylex();
\r
9081 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9082 cm == WhiteWins || cm == BlackWins ||
\r
9083 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9084 DisplayMessage("", _("No moves in game"));
\r
9085 if (cmailMsgLoaded) {
\r
9086 if (appData.debugMode)
\r
9087 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9088 ClearHighlights();
\r
9091 DrawPosition(FALSE, boards[currentMove]);
\r
9092 DisplayBothClocks();
\r
9093 gameMode = EditGame;
\r
9095 gameFileFP = NULL;
\r
9100 // [HGM] PV info: routine tests if comment empty
\r
9101 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9102 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9104 if (!matchMode && appData.timeDelay != 0)
\r
9105 DrawPosition(FALSE, boards[currentMove]);
\r
9107 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9108 programStats.ok_to_send = 1;
\r
9111 /* if the first token after the PGN tags is a move
\r
9112 * and not move number 1, retrieve it from the parser
\r
9114 if (cm != MoveNumberOne)
\r
9115 LoadGameOneMove(cm);
\r
9117 /* load the remaining moves from the file */
\r
9118 while (LoadGameOneMove((ChessMove)0)) {
\r
9119 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9120 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9123 /* rewind to the start of the game */
\r
9124 currentMove = backwardMostMove;
\r
9126 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9128 if (oldGameMode == AnalyzeFile ||
\r
9129 oldGameMode == AnalyzeMode) {
\r
9130 AnalyzeFileEvent();
\r
9133 if (matchMode || appData.timeDelay == 0) {
\r
9135 gameMode = EditGame;
\r
9137 } else if (appData.timeDelay > 0) {
\r
9138 AutoPlayGameLoop();
\r
9141 if (appData.debugMode)
\r
9142 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9144 loadFlag = 0; /* [HGM] true game starts */
\r
9148 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9150 ReloadPosition(offset)
\r
9153 int positionNumber = lastLoadPositionNumber + offset;
\r
9154 if (lastLoadPositionFP == NULL) {
\r
9155 DisplayError(_("No position has been loaded yet"), 0);
\r
9158 if (positionNumber <= 0) {
\r
9159 DisplayError(_("Can't back up any further"), 0);
\r
9162 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9163 lastLoadPositionTitle);
\r
9166 /* Load the nth position from the given file */
\r
9168 LoadPositionFromFile(filename, n, title)
\r
9174 char buf[MSG_SIZ];
\r
9176 if (strcmp(filename, "-") == 0) {
\r
9177 return LoadPosition(stdin, n, "stdin");
\r
9179 f = fopen(filename, "rb");
\r
9181 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9182 DisplayError(buf, errno);
\r
9185 return LoadPosition(f, n, title);
\r
9190 /* Load the nth position from the given open file, and close it */
\r
9192 LoadPosition(f, positionNumber, title)
\r
9194 int positionNumber;
\r
9197 char *p, line[MSG_SIZ];
\r
9198 Board initial_position;
\r
9199 int i, j, fenMode, pn;
\r
9201 if (gameMode == Training )
\r
9202 SetTrainingModeOff();
\r
9204 if (gameMode != BeginningOfGame) {
\r
9205 Reset(FALSE, TRUE);
\r
9207 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9208 fclose(lastLoadPositionFP);
\r
9210 if (positionNumber == 0) positionNumber = 1;
\r
9211 lastLoadPositionFP = f;
\r
9212 lastLoadPositionNumber = positionNumber;
\r
9213 strcpy(lastLoadPositionTitle, title);
\r
9214 if (first.pr == NoProc) {
\r
9215 StartChessProgram(&first);
\r
9216 InitChessProgram(&first, FALSE);
\r
9218 pn = positionNumber;
\r
9219 if (positionNumber < 0) {
\r
9220 /* Negative position number means to seek to that byte offset */
\r
9221 if (fseek(f, -positionNumber, 0) == -1) {
\r
9222 DisplayError(_("Can't seek on position file"), 0);
\r
9227 if (fseek(f, 0, 0) == -1) {
\r
9228 if (f == lastLoadPositionFP ?
\r
9229 positionNumber == lastLoadPositionNumber + 1 :
\r
9230 positionNumber == 1) {
\r
9233 DisplayError(_("Can't seek on position file"), 0);
\r
9238 /* See if this file is FEN or old-style xboard */
\r
9239 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9240 DisplayError(_("Position not found in file"), 0);
\r
9244 switch (line[0]) {
\r
9245 case '#': case 'x':
\r
9249 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9250 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9251 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9252 case '7': case '8': case '9':
\r
9253 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9254 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9255 case 'C': case 'W': case 'c': case 'w':
\r
9260 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9261 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9265 if (fenMode || line[0] == '#') pn--;
\r
9267 /* skip positions before number pn */
\r
9268 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9269 Reset(TRUE, TRUE);
\r
9270 DisplayError(_("Position not found in file"), 0);
\r
9273 if (fenMode || line[0] == '#') pn--;
\r
9278 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9279 DisplayError(_("Bad FEN position in file"), 0);
\r
9283 (void) fgets(line, MSG_SIZ, f);
\r
9284 (void) fgets(line, MSG_SIZ, f);
\r
9286 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9287 (void) fgets(line, MSG_SIZ, f);
\r
9288 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9291 initial_position[i][j++] = CharToPiece(*p);
\r
9295 blackPlaysFirst = FALSE;
\r
9297 (void) fgets(line, MSG_SIZ, f);
\r
9298 if (strncmp(line, "black", strlen("black"))==0)
\r
9299 blackPlaysFirst = TRUE;
\r
9302 startedFromSetupPosition = TRUE;
\r
9304 SendToProgram("force\n", &first);
\r
9305 CopyBoard(boards[0], initial_position);
\r
9306 if (blackPlaysFirst) {
\r
9307 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9308 strcpy(moveList[0], "");
\r
9309 strcpy(parseList[0], "");
\r
9310 CopyBoard(boards[1], initial_position);
\r
9311 DisplayMessage("", _("Black to play"));
\r
9313 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9314 DisplayMessage("", _("White to play"));
\r
9316 /* [HGM] copy FEN attributes as well */
\r
9318 initialRulePlies = FENrulePlies;
\r
9319 epStatus[forwardMostMove] = FENepStatus;
\r
9320 for( i=0; i< nrCastlingRights; i++ )
\r
9321 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9323 SendBoard(&first, forwardMostMove);
\r
9324 if (appData.debugMode) {
\r
9326 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9327 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9328 fprintf(debugFP, "Load Position\n");
\r
9331 if (positionNumber > 1) {
\r
9332 sprintf(line, "%s %d", title, positionNumber);
\r
9333 DisplayTitle(line);
\r
9335 DisplayTitle(title);
\r
9337 gameMode = EditGame;
\r
9340 timeRemaining[0][1] = whiteTimeRemaining;
\r
9341 timeRemaining[1][1] = blackTimeRemaining;
\r
9342 DrawPosition(FALSE, boards[currentMove]);
\r
9349 CopyPlayerNameIntoFileName(dest, src)
\r
9350 char **dest, *src;
\r
9352 while (*src != NULLCHAR && *src != ',') {
\r
9353 if (*src == ' ') {
\r
9357 *(*dest)++ = *src++;
\r
9362 char *DefaultFileName(ext)
\r
9365 static char def[MSG_SIZ];
\r
9368 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9370 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9372 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9376 def[0] = NULLCHAR;
\r
9381 /* Save the current game to the given file */
\r
9383 SaveGameToFile(filename, append)
\r
9388 char buf[MSG_SIZ];
\r
9390 if (strcmp(filename, "-") == 0) {
\r
9391 return SaveGame(stdout, 0, NULL);
\r
9393 f = fopen(filename, append ? "a" : "w");
\r
9395 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9396 DisplayError(buf, errno);
\r
9399 return SaveGame(f, 0, NULL);
\r
9408 static char buf[MSG_SIZ];
\r
9411 p = strchr(str, ' ');
\r
9412 if (p == NULL) return str;
\r
9413 strncpy(buf, str, p - str);
\r
9414 buf[p - str] = NULLCHAR;
\r
9418 #define PGN_MAX_LINE 75
\r
9420 #define PGN_SIDE_WHITE 0
\r
9421 #define PGN_SIDE_BLACK 1
\r
9424 static int FindFirstMoveOutOfBook( int side )
\r
9428 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9429 int index = backwardMostMove;
\r
9430 int has_book_hit = 0;
\r
9432 if( (index % 2) != side ) {
\r
9436 while( index < forwardMostMove ) {
\r
9437 /* Check to see if engine is in book */
\r
9438 int depth = pvInfoList[index].depth;
\r
9439 int score = pvInfoList[index].score;
\r
9442 if( depth <= 2 ) {
\r
9445 else if( score == 0 && depth == 63 ) {
\r
9446 in_book = 1; /* Zappa */
\r
9448 else if( score == 2 && depth == 99 ) {
\r
9449 in_book = 1; /* Abrok */
\r
9452 has_book_hit += in_book;
\r
9468 void GetOutOfBookInfo( char * buf )
\r
9472 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9474 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9475 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9479 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9480 for( i=0; i<2; i++ ) {
\r
9484 if( i > 0 && oob[0] >= 0 ) {
\r
9485 strcat( buf, " " );
\r
9488 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9489 sprintf( buf+strlen(buf), "%s%.2f",
\r
9490 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9491 pvInfoList[idx].score / 100.0 );
\r
9497 /* Save game in PGN style and close the file */
\r
9502 int i, offset, linelen, newblock;
\r
9506 int movelen, numlen, blank;
\r
9507 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9509 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9511 tm = time((time_t *) NULL);
\r
9513 PrintPGNTags(f, &gameInfo);
\r
9515 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9516 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9517 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9518 fprintf(f, "\n{--------------\n");
\r
9519 PrintPosition(f, backwardMostMove);
\r
9520 fprintf(f, "--------------}\n");
\r
9524 /* [AS] Out of book annotation */
\r
9525 if( appData.saveOutOfBookInfo ) {
\r
9528 GetOutOfBookInfo( buf );
\r
9530 if( buf[0] != '\0' ) {
\r
9531 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9538 i = backwardMostMove;
\r
9542 while (i < forwardMostMove) {
\r
9543 /* Print comments preceding this move */
\r
9544 if (commentList[i] != NULL) {
\r
9545 if (linelen > 0) fprintf(f, "\n");
\r
9546 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9551 /* Format move number */
\r
9552 if ((i % 2) == 0) {
\r
9553 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9556 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9558 numtext[0] = NULLCHAR;
\r
9561 numlen = strlen(numtext);
\r
9564 /* Print move number */
\r
9565 blank = linelen > 0 && numlen > 0;
\r
9566 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9575 fprintf(f, numtext);
\r
9576 linelen += numlen;
\r
9579 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9582 blank = linelen > 0 && movelen > 0;
\r
9583 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9592 fprintf(f, parseList[i]);
\r
9593 linelen += movelen;
\r
9595 /* [AS] Add PV info if present */
\r
9596 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9597 /* [HGM] add time */
\r
9598 char buf[MSG_SIZ]; int seconds = 0;
\r
9601 if(i >= backwardMostMove) {
\r
9602 if(WhiteOnMove(i))
\r
9603 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9604 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9606 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9607 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9609 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9611 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9613 if (appData.debugMode,0) {
\r
9614 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9615 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9616 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9620 if( seconds <= 0) buf[0] = 0; else
\r
9621 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9622 seconds = (seconds + 4)/10; // round to full seconds
\r
9623 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9624 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9627 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9628 pvInfoList[i].score >= 0 ? "+" : "",
\r
9629 pvInfoList[i].score / 100.0,
\r
9630 pvInfoList[i].depth,
\r
9633 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9635 /* Print score/depth */
\r
9636 blank = linelen > 0 && movelen > 0;
\r
9637 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9646 fprintf(f, move_buffer);
\r
9647 linelen += movelen;
\r
9653 /* Start a new line */
\r
9654 if (linelen > 0) fprintf(f, "\n");
\r
9656 /* Print comments after last move */
\r
9657 if (commentList[i] != NULL) {
\r
9658 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9661 /* Print result */
\r
9662 if (gameInfo.resultDetails != NULL &&
\r
9663 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9664 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9665 PGNResult(gameInfo.result));
\r
9667 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9674 /* Save game in old style and close the file */
\r
9676 SaveGameOldStyle(f)
\r
9682 tm = time((time_t *) NULL);
\r
9684 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9685 PrintOpponents(f);
\r
9687 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9688 fprintf(f, "\n[--------------\n");
\r
9689 PrintPosition(f, backwardMostMove);
\r
9690 fprintf(f, "--------------]\n");
\r
9695 i = backwardMostMove;
\r
9696 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9698 while (i < forwardMostMove) {
\r
9699 if (commentList[i] != NULL) {
\r
9700 fprintf(f, "[%s]\n", commentList[i]);
\r
9703 if ((i % 2) == 1) {
\r
9704 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9707 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9709 if (commentList[i] != NULL) {
\r
9713 if (i >= forwardMostMove) {
\r
9717 fprintf(f, "%s\n", parseList[i]);
\r
9722 if (commentList[i] != NULL) {
\r
9723 fprintf(f, "[%s]\n", commentList[i]);
\r
9726 /* This isn't really the old style, but it's close enough */
\r
9727 if (gameInfo.resultDetails != NULL &&
\r
9728 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9729 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9730 gameInfo.resultDetails);
\r
9732 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9739 /* Save the current game to open file f and close the file */
\r
9741 SaveGame(f, dummy, dummy2)
\r
9746 if (gameMode == EditPosition) EditPositionDone();
\r
9747 if (appData.oldSaveStyle)
\r
9748 return SaveGameOldStyle(f);
\r
9750 return SaveGamePGN(f);
\r
9753 /* Save the current position to the given file */
\r
9755 SavePositionToFile(filename)
\r
9759 char buf[MSG_SIZ];
\r
9761 if (strcmp(filename, "-") == 0) {
\r
9762 return SavePosition(stdout, 0, NULL);
\r
9764 f = fopen(filename, "a");
\r
9766 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9767 DisplayError(buf, errno);
\r
9770 SavePosition(f, 0, NULL);
\r
9776 /* Save the current position to the given open file and close the file */
\r
9778 SavePosition(f, dummy, dummy2)
\r
9786 if (appData.oldSaveStyle) {
\r
9787 tm = time((time_t *) NULL);
\r
9789 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9790 PrintOpponents(f);
\r
9791 fprintf(f, "[--------------\n");
\r
9792 PrintPosition(f, currentMove);
\r
9793 fprintf(f, "--------------]\n");
\r
9795 fen = PositionToFEN(currentMove, 1);
\r
9796 fprintf(f, "%s\n", fen);
\r
9804 ReloadCmailMsgEvent(unregister)
\r
9808 static char *inFilename = NULL;
\r
9809 static char *outFilename;
\r
9811 struct stat inbuf, outbuf;
\r
9814 /* Any registered moves are unregistered if unregister is set, */
\r
9815 /* i.e. invoked by the signal handler */
\r
9817 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9818 cmailMoveRegistered[i] = FALSE;
\r
9819 if (cmailCommentList[i] != NULL) {
\r
9820 free(cmailCommentList[i]);
\r
9821 cmailCommentList[i] = NULL;
\r
9824 nCmailMovesRegistered = 0;
\r
9827 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9828 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9830 nCmailResults = 0;
\r
9832 if (inFilename == NULL) {
\r
9833 /* Because the filenames are static they only get malloced once */
\r
9834 /* and they never get freed */
\r
9835 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9836 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9838 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9839 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9842 status = stat(outFilename, &outbuf);
\r
9844 cmailMailedMove = FALSE;
\r
9846 status = stat(inFilename, &inbuf);
\r
9847 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9850 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9851 counts the games, notes how each one terminated, etc.
\r
9853 It would be nice to remove this kludge and instead gather all
\r
9854 the information while building the game list. (And to keep it
\r
9855 in the game list nodes instead of having a bunch of fixed-size
\r
9856 parallel arrays.) Note this will require getting each game's
\r
9857 termination from the PGN tags, as the game list builder does
\r
9858 not process the game moves. --mann
\r
9860 cmailMsgLoaded = TRUE;
\r
9861 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9863 /* Load first game in the file or popup game menu */
\r
9864 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9866 #endif /* !WIN32 */
\r
9874 char string[MSG_SIZ];
\r
9876 if ( cmailMailedMove
\r
9877 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9878 return TRUE; /* Allow free viewing */
\r
9881 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9882 /* with the move registered when the conditions for registering no */
\r
9884 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9885 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9886 nCmailMovesRegistered --;
\r
9888 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9890 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9891 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9895 if (cmailOldMove == -1) {
\r
9896 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9900 if (currentMove > cmailOldMove + 1) {
\r
9901 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9905 if (currentMove < cmailOldMove) {
\r
9906 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9910 if (forwardMostMove > currentMove) {
\r
9911 /* Silently truncate extra moves */
\r
9915 if ( (currentMove == cmailOldMove + 1)
\r
9916 || ( (currentMove == cmailOldMove)
\r
9917 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9918 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9919 if (gameInfo.result != GameUnfinished) {
\r
9920 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9923 if (commentList[currentMove] != NULL) {
\r
9924 cmailCommentList[lastLoadGameNumber - 1]
\r
9925 = StrSave(commentList[currentMove]);
\r
9927 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9929 if (appData.debugMode)
\r
9930 fprintf(debugFP, "Saving %s for game %d\n",
\r
9931 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9934 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9936 f = fopen(string, "w");
\r
9937 if (appData.oldSaveStyle) {
\r
9938 SaveGameOldStyle(f); /* also closes the file */
\r
9940 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9941 f = fopen(string, "w");
\r
9942 SavePosition(f, 0, NULL); /* also closes the file */
\r
9944 fprintf(f, "{--------------\n");
\r
9945 PrintPosition(f, currentMove);
\r
9946 fprintf(f, "--------------}\n\n");
\r
9948 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9951 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9952 nCmailMovesRegistered ++;
\r
9953 } else if (nCmailGames == 1) {
\r
9954 DisplayError(_("You have not made a move yet"), 0);
\r
9965 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9966 FILE *commandOutput;
\r
9967 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9968 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9974 if (! cmailMsgLoaded) {
\r
9975 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9979 if (nCmailGames == nCmailResults) {
\r
9980 DisplayError(_("No unfinished games"), 0);
\r
9984 #if CMAIL_PROHIBIT_REMAIL
\r
9985 if (cmailMailedMove) {
\r
9986 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
9987 DisplayError(msg, 0);
\r
9992 if (! (cmailMailedMove || RegisterMove())) return;
\r
9994 if ( cmailMailedMove
\r
9995 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9996 sprintf(string, partCommandString,
\r
9997 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9998 commandOutput = popen(string, "r");
\r
10000 if (commandOutput == NULL) {
\r
10001 DisplayError(_("Failed to invoke cmail"), 0);
\r
10003 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
10004 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10006 if (nBuffers > 1) {
\r
10007 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10008 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10009 nBytes = MSG_SIZ - 1;
\r
10011 (void) memcpy(msg, buffer, nBytes);
\r
10013 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10015 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10016 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10019 for (i = 0; i < nCmailGames; i ++) {
\r
10020 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10021 archived = FALSE;
\r
10025 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10027 sprintf(buffer, "%s/%s.%s.archive",
\r
10029 appData.cmailGameName,
\r
10031 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10032 cmailMsgLoaded = FALSE;
\r
10036 DisplayInformation(msg);
\r
10037 pclose(commandOutput);
\r
10040 if ((*cmailMsg) != '\0') {
\r
10041 DisplayInformation(cmailMsg);
\r
10046 #endif /* !WIN32 */
\r
10055 int prependComma = 0;
\r
10057 char string[MSG_SIZ]; /* Space for game-list */
\r
10060 if (!cmailMsgLoaded) return "";
\r
10062 if (cmailMailedMove) {
\r
10063 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10065 /* Create a list of games left */
\r
10066 sprintf(string, "[");
\r
10067 for (i = 0; i < nCmailGames; i ++) {
\r
10068 if (! ( cmailMoveRegistered[i]
\r
10069 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10070 if (prependComma) {
\r
10071 sprintf(number, ",%d", i + 1);
\r
10073 sprintf(number, "%d", i + 1);
\r
10074 prependComma = 1;
\r
10077 strcat(string, number);
\r
10080 strcat(string, "]");
\r
10082 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10083 switch (nCmailGames) {
\r
10085 sprintf(cmailMsg,
\r
10086 _("Still need to make move for game\n"));
\r
10090 sprintf(cmailMsg,
\r
10091 _("Still need to make moves for both games\n"));
\r
10095 sprintf(cmailMsg,
\r
10096 _("Still need to make moves for all %d games\n"),
\r
10101 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10103 sprintf(cmailMsg,
\r
10104 _("Still need to make a move for game %s\n"),
\r
10109 if (nCmailResults == nCmailGames) {
\r
10110 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10112 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10117 sprintf(cmailMsg,
\r
10118 _("Still need to make moves for games %s\n"),
\r
10124 #endif /* WIN32 */
\r
10130 if (gameMode == Training)
\r
10131 SetTrainingModeOff();
\r
10133 Reset(TRUE, TRUE);
\r
10134 cmailMsgLoaded = FALSE;
\r
10135 if (appData.icsActive) {
\r
10136 SendToICS(ics_prefix);
\r
10137 SendToICS("refresh\n");
\r
10142 ExitEvent(status)
\r
10146 if (exiting > 2) {
\r
10147 /* Give up on clean exit */
\r
10150 if (exiting > 1) {
\r
10151 /* Keep trying for clean exit */
\r
10155 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10157 if (telnetISR != NULL) {
\r
10158 RemoveInputSource(telnetISR);
\r
10160 if (icsPR != NoProc) {
\r
10161 DestroyChildProcess(icsPR, TRUE);
\r
10164 /* Save game if resource set and not already saved by GameEnds() */
\r
10165 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10166 && forwardMostMove > 0) {
\r
10167 if (*appData.saveGameFile != NULLCHAR) {
\r
10168 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10169 } else if (appData.autoSaveGames) {
\r
10172 if (*appData.savePositionFile != NULLCHAR) {
\r
10173 SavePositionToFile(appData.savePositionFile);
\r
10176 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10178 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10179 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10181 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10182 /* make sure this other one finishes before killing it! */
\r
10183 if(endingGame) { int count = 0;
\r
10184 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10185 while(endingGame && count++ < 10) DoSleep(1);
\r
10186 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10189 /* Kill off chess programs */
\r
10190 if (first.pr != NoProc) {
\r
10191 ExitAnalyzeMode();
\r
10193 DoSleep( appData.delayBeforeQuit );
\r
10194 SendToProgram("quit\n", &first);
\r
10195 DoSleep( appData.delayAfterQuit );
\r
10196 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10198 if (second.pr != NoProc) {
\r
10199 DoSleep( appData.delayBeforeQuit );
\r
10200 SendToProgram("quit\n", &second);
\r
10201 DoSleep( appData.delayAfterQuit );
\r
10202 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10204 if (first.isr != NULL) {
\r
10205 RemoveInputSource(first.isr);
\r
10207 if (second.isr != NULL) {
\r
10208 RemoveInputSource(second.isr);
\r
10211 ShutDownFrontEnd();
\r
10218 if (appData.debugMode)
\r
10219 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10223 if (gameMode == MachinePlaysWhite ||
\r
10224 gameMode == MachinePlaysBlack) {
\r
10227 DisplayBothClocks();
\r
10229 if (gameMode == PlayFromGameFile) {
\r
10230 if (appData.timeDelay >= 0)
\r
10231 AutoPlayGameLoop();
\r
10232 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10233 Reset(FALSE, TRUE);
\r
10234 SendToICS(ics_prefix);
\r
10235 SendToICS("refresh\n");
\r
10236 } else if (currentMove < forwardMostMove) {
\r
10237 ForwardInner(forwardMostMove);
\r
10239 pauseExamInvalid = FALSE;
\r
10241 switch (gameMode) {
\r
10244 case IcsExamining:
\r
10245 pauseExamForwardMostMove = forwardMostMove;
\r
10246 pauseExamInvalid = FALSE;
\r
10247 /* fall through */
\r
10248 case IcsObserving:
\r
10249 case IcsPlayingWhite:
\r
10250 case IcsPlayingBlack:
\r
10254 case PlayFromGameFile:
\r
10255 (void) StopLoadGameTimer();
\r
10259 case BeginningOfGame:
\r
10260 if (appData.icsActive) return;
\r
10261 /* else fall through */
\r
10262 case MachinePlaysWhite:
\r
10263 case MachinePlaysBlack:
\r
10264 case TwoMachinesPlay:
\r
10265 if (forwardMostMove == 0)
\r
10266 return; /* don't pause if no one has moved */
\r
10267 if ((gameMode == MachinePlaysWhite &&
\r
10268 !WhiteOnMove(forwardMostMove)) ||
\r
10269 (gameMode == MachinePlaysBlack &&
\r
10270 WhiteOnMove(forwardMostMove))) {
\r
10281 EditCommentEvent()
\r
10283 char title[MSG_SIZ];
\r
10285 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10286 strcpy(title, _("Edit comment"));
\r
10288 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10289 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10290 parseList[currentMove - 1]);
\r
10293 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10300 char *tags = PGNTags(&gameInfo);
\r
10301 EditTagsPopUp(tags);
\r
10306 AnalyzeModeEvent()
\r
10308 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10311 if (gameMode != AnalyzeFile) {
\r
10312 if (!appData.icsEngineAnalyze) {
\r
10314 if (gameMode != EditGame) return;
\r
10316 ResurrectChessProgram();
\r
10317 SendToProgram("analyze\n", &first);
\r
10318 first.analyzing = TRUE;
\r
10319 /*first.maybeThinking = TRUE;*/
\r
10320 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10321 AnalysisPopUp(_("Analysis"),
\r
10322 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10324 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10329 StartAnalysisClock();
\r
10330 GetTimeMark(&lastNodeCountTime);
\r
10331 lastNodeCount = 0;
\r
10335 AnalyzeFileEvent()
\r
10337 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10340 if (gameMode != AnalyzeMode) {
\r
10342 if (gameMode != EditGame) return;
\r
10343 ResurrectChessProgram();
\r
10344 SendToProgram("analyze\n", &first);
\r
10345 first.analyzing = TRUE;
\r
10346 /*first.maybeThinking = TRUE;*/
\r
10347 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10348 AnalysisPopUp(_("Analysis"),
\r
10349 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10351 gameMode = AnalyzeFile;
\r
10356 StartAnalysisClock();
\r
10357 GetTimeMark(&lastNodeCountTime);
\r
10358 lastNodeCount = 0;
\r
10362 MachineWhiteEvent()
\r
10364 char buf[MSG_SIZ];
\r
10365 char *bookHit = NULL;
\r
10367 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10371 if (gameMode == PlayFromGameFile ||
\r
10372 gameMode == TwoMachinesPlay ||
\r
10373 gameMode == Training ||
\r
10374 gameMode == AnalyzeMode ||
\r
10375 gameMode == EndOfGame)
\r
10378 if (gameMode == EditPosition)
\r
10379 EditPositionDone();
\r
10381 if (!WhiteOnMove(currentMove)) {
\r
10382 DisplayError(_("It is not White's turn"), 0);
\r
10386 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10387 ExitAnalyzeMode();
\r
10389 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10390 gameMode == AnalyzeFile)
\r
10393 ResurrectChessProgram(); /* in case it isn't running */
\r
10394 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10395 gameMode = MachinePlaysWhite;
\r
10398 gameMode = MachinePlaysWhite;
\r
10402 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10403 DisplayTitle(buf);
\r
10404 if (first.sendName) {
\r
10405 sprintf(buf, "name %s\n", gameInfo.black);
\r
10406 SendToProgram(buf, &first);
\r
10408 if (first.sendTime) {
\r
10409 if (first.useColors) {
\r
10410 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10412 SendTimeRemaining(&first, TRUE);
\r
10414 if (first.useColors) {
\r
10415 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10417 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10418 SetMachineThinkingEnables();
\r
10419 first.maybeThinking = TRUE;
\r
10422 if (appData.autoFlipView && !flipView) {
\r
10423 flipView = !flipView;
\r
10424 DrawPosition(FALSE, NULL);
\r
10425 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10428 if(bookHit) { // [HGM] book: simulate book reply
\r
10429 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10431 programStats.depth = programStats.nodes = programStats.time =
\r
10432 programStats.score = programStats.got_only_move = 0;
\r
10433 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10435 strcpy(bookMove, "move ");
\r
10436 strcat(bookMove, bookHit);
\r
10437 HandleMachineMove(bookMove, &first);
\r
10442 MachineBlackEvent()
\r
10444 char buf[MSG_SIZ];
\r
10445 char *bookHit = NULL;
\r
10447 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10451 if (gameMode == PlayFromGameFile ||
\r
10452 gameMode == TwoMachinesPlay ||
\r
10453 gameMode == Training ||
\r
10454 gameMode == AnalyzeMode ||
\r
10455 gameMode == EndOfGame)
\r
10458 if (gameMode == EditPosition)
\r
10459 EditPositionDone();
\r
10461 if (WhiteOnMove(currentMove)) {
\r
10462 DisplayError(_("It is not Black's turn"), 0);
\r
10466 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10467 ExitAnalyzeMode();
\r
10469 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10470 gameMode == AnalyzeFile)
\r
10473 ResurrectChessProgram(); /* in case it isn't running */
\r
10474 gameMode = MachinePlaysBlack;
\r
10478 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10479 DisplayTitle(buf);
\r
10480 if (first.sendName) {
\r
10481 sprintf(buf, "name %s\n", gameInfo.white);
\r
10482 SendToProgram(buf, &first);
\r
10484 if (first.sendTime) {
\r
10485 if (first.useColors) {
\r
10486 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10488 SendTimeRemaining(&first, FALSE);
\r
10490 if (first.useColors) {
\r
10491 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10493 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10494 SetMachineThinkingEnables();
\r
10495 first.maybeThinking = TRUE;
\r
10498 if (appData.autoFlipView && flipView) {
\r
10499 flipView = !flipView;
\r
10500 DrawPosition(FALSE, NULL);
\r
10501 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10503 if(bookHit) { // [HGM] book: simulate book reply
\r
10504 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10506 programStats.depth = programStats.nodes = programStats.time =
\r
10507 programStats.score = programStats.got_only_move = 0;
\r
10508 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10510 strcpy(bookMove, "move ");
\r
10511 strcat(bookMove, bookHit);
\r
10512 HandleMachineMove(bookMove, &first);
\r
10518 DisplayTwoMachinesTitle()
\r
10520 char buf[MSG_SIZ];
\r
10521 if (appData.matchGames > 0) {
\r
10522 if (first.twoMachinesColor[0] == 'w') {
\r
10523 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10524 gameInfo.white, gameInfo.black,
\r
10525 first.matchWins, second.matchWins,
\r
10526 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10528 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10529 gameInfo.white, gameInfo.black,
\r
10530 second.matchWins, first.matchWins,
\r
10531 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10534 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10536 DisplayTitle(buf);
\r
10540 TwoMachinesEvent P((void))
\r
10543 char buf[MSG_SIZ];
\r
10544 ChessProgramState *onmove;
\r
10545 char *bookHit = NULL;
\r
10547 if (appData.noChessProgram) return;
\r
10549 switch (gameMode) {
\r
10550 case TwoMachinesPlay:
\r
10552 case MachinePlaysWhite:
\r
10553 case MachinePlaysBlack:
\r
10554 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10555 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10558 /* fall through */
\r
10559 case BeginningOfGame:
\r
10560 case PlayFromGameFile:
\r
10563 if (gameMode != EditGame) return;
\r
10565 case EditPosition:
\r
10566 EditPositionDone();
\r
10568 case AnalyzeMode:
\r
10569 case AnalyzeFile:
\r
10570 ExitAnalyzeMode();
\r
10577 forwardMostMove = currentMove;
\r
10578 ResurrectChessProgram(); /* in case first program isn't running */
\r
10580 if (second.pr == NULL) {
\r
10581 StartChessProgram(&second);
\r
10582 if (second.protocolVersion == 1) {
\r
10583 TwoMachinesEventIfReady();
\r
10585 /* kludge: allow timeout for initial "feature" command */
\r
10587 DisplayMessage("", _("Starting second chess program"));
\r
10588 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10592 DisplayMessage("", "");
\r
10593 InitChessProgram(&second, FALSE);
\r
10594 SendToProgram("force\n", &second);
\r
10595 if (startedFromSetupPosition) {
\r
10596 SendBoard(&second, backwardMostMove);
\r
10597 if (appData.debugMode) {
\r
10598 fprintf(debugFP, "Two Machines\n");
\r
10601 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10602 SendMoveToProgram(i, &second);
\r
10605 gameMode = TwoMachinesPlay;
\r
10609 DisplayTwoMachinesTitle();
\r
10610 firstMove = TRUE;
\r
10611 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10614 onmove = &second;
\r
10617 SendToProgram(first.computerString, &first);
\r
10618 if (first.sendName) {
\r
10619 sprintf(buf, "name %s\n", second.tidy);
\r
10620 SendToProgram(buf, &first);
\r
10622 SendToProgram(second.computerString, &second);
\r
10623 if (second.sendName) {
\r
10624 sprintf(buf, "name %s\n", first.tidy);
\r
10625 SendToProgram(buf, &second);
\r
10629 if (!first.sendTime || !second.sendTime) {
\r
10630 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10631 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10633 if (onmove->sendTime) {
\r
10634 if (onmove->useColors) {
\r
10635 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10637 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10639 if (onmove->useColors) {
\r
10640 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10642 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10643 // SendToProgram("go\n", onmove);
\r
10644 onmove->maybeThinking = TRUE;
\r
10645 SetMachineThinkingEnables();
\r
10649 if(bookHit) { // [HGM] book: simulate book reply
\r
10650 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10652 programStats.depth = programStats.nodes = programStats.time =
\r
10653 programStats.score = programStats.got_only_move = 0;
\r
10654 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10656 strcpy(bookMove, "move ");
\r
10657 strcat(bookMove, bookHit);
\r
10658 HandleMachineMove(bookMove, &first);
\r
10665 if (gameMode == Training) {
\r
10666 SetTrainingModeOff();
\r
10667 gameMode = PlayFromGameFile;
\r
10668 DisplayMessage("", _("Training mode off"));
\r
10670 gameMode = Training;
\r
10671 animateTraining = appData.animate;
\r
10673 /* make sure we are not already at the end of the game */
\r
10674 if (currentMove < forwardMostMove) {
\r
10675 SetTrainingModeOn();
\r
10676 DisplayMessage("", _("Training mode on"));
\r
10678 gameMode = PlayFromGameFile;
\r
10679 DisplayError(_("Already at end of game"), 0);
\r
10688 if (!appData.icsActive) return;
\r
10689 switch (gameMode) {
\r
10690 case IcsPlayingWhite:
\r
10691 case IcsPlayingBlack:
\r
10692 case IcsObserving:
\r
10694 case BeginningOfGame:
\r
10695 case IcsExamining:
\r
10701 case EditPosition:
\r
10702 EditPositionDone();
\r
10705 case AnalyzeMode:
\r
10706 case AnalyzeFile:
\r
10707 ExitAnalyzeMode();
\r
10715 gameMode = IcsIdle;
\r
10726 switch (gameMode) {
\r
10728 SetTrainingModeOff();
\r
10730 case MachinePlaysWhite:
\r
10731 case MachinePlaysBlack:
\r
10732 case BeginningOfGame:
\r
10733 SendToProgram("force\n", &first);
\r
10734 SetUserThinkingEnables();
\r
10736 case PlayFromGameFile:
\r
10737 (void) StopLoadGameTimer();
\r
10738 if (gameFileFP != NULL) {
\r
10739 gameFileFP = NULL;
\r
10742 case EditPosition:
\r
10743 EditPositionDone();
\r
10745 case AnalyzeMode:
\r
10746 case AnalyzeFile:
\r
10747 ExitAnalyzeMode();
\r
10748 SendToProgram("force\n", &first);
\r
10750 case TwoMachinesPlay:
\r
10751 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10752 ResurrectChessProgram();
\r
10753 SetUserThinkingEnables();
\r
10756 ResurrectChessProgram();
\r
10758 case IcsPlayingBlack:
\r
10759 case IcsPlayingWhite:
\r
10760 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10762 case IcsObserving:
\r
10763 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10765 case IcsExamining:
\r
10766 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10777 first.offeredDraw = second.offeredDraw = 0;
\r
10779 if (gameMode == PlayFromGameFile) {
\r
10780 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10781 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10782 DisplayTitle("");
\r
10785 if (gameMode == MachinePlaysWhite ||
\r
10786 gameMode == MachinePlaysBlack ||
\r
10787 gameMode == TwoMachinesPlay ||
\r
10788 gameMode == EndOfGame) {
\r
10789 i = forwardMostMove;
\r
10790 while (i > currentMove) {
\r
10791 SendToProgram("undo\n", &first);
\r
10794 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10795 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10796 DisplayBothClocks();
\r
10797 if (whiteFlag || blackFlag) {
\r
10798 whiteFlag = blackFlag = 0;
\r
10800 DisplayTitle("");
\r
10803 gameMode = EditGame;
\r
10810 EditPositionEvent()
\r
10812 if (gameMode == EditPosition) {
\r
10818 if (gameMode != EditGame) return;
\r
10820 gameMode = EditPosition;
\r
10823 if (currentMove > 0)
\r
10824 CopyBoard(boards[0], boards[currentMove]);
\r
10826 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10828 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10829 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10834 ExitAnalyzeMode()
\r
10836 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10837 if (appData.icsEngineAnalyze) {
\r
10838 appData.icsEngineAnalyze = FALSE;
\r
10840 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10842 if (first.analysisSupport && first.analyzing) {
\r
10843 SendToProgram("exit\n", &first);
\r
10844 first.analyzing = FALSE;
\r
10846 AnalysisPopDown();
\r
10847 thinkOutput[0] = NULLCHAR;
\r
10851 EditPositionDone()
\r
10853 startedFromSetupPosition = TRUE;
\r
10854 InitChessProgram(&first, FALSE);
\r
10855 SendToProgram("force\n", &first);
\r
10856 if (blackPlaysFirst) {
\r
10857 strcpy(moveList[0], "");
\r
10858 strcpy(parseList[0], "");
\r
10859 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10860 CopyBoard(boards[1], boards[0]);
\r
10861 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10863 epStatus[1] = epStatus[0];
\r
10864 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10867 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10869 SendBoard(&first, forwardMostMove);
\r
10870 if (appData.debugMode) {
\r
10871 fprintf(debugFP, "EditPosDone\n");
\r
10873 DisplayTitle("");
\r
10874 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10875 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10876 gameMode = EditGame;
\r
10878 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10879 ClearHighlights(); /* [AS] */
\r
10882 /* Pause for `ms' milliseconds */
\r
10883 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10890 GetTimeMark(&m1);
\r
10892 GetTimeMark(&m2);
\r
10893 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10896 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10898 SendMultiLineToICS(buf)
\r
10901 char temp[MSG_SIZ+1], *p;
\r
10904 len = strlen(buf);
\r
10905 if (len > MSG_SIZ)
\r
10908 strncpy(temp, buf, len);
\r
10913 if (*p == '\n' || *p == '\r')
\r
10918 strcat(temp, "\n");
\r
10920 SendToPlayer(temp, strlen(temp));
\r
10924 SetWhiteToPlayEvent()
\r
10926 if (gameMode == EditPosition) {
\r
10927 blackPlaysFirst = FALSE;
\r
10928 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10929 } else if (gameMode == IcsExamining) {
\r
10930 SendToICS(ics_prefix);
\r
10931 SendToICS("tomove white\n");
\r
10936 SetBlackToPlayEvent()
\r
10938 if (gameMode == EditPosition) {
\r
10939 blackPlaysFirst = TRUE;
\r
10940 currentMove = 1; /* kludge */
\r
10941 DisplayBothClocks();
\r
10943 } else if (gameMode == IcsExamining) {
\r
10944 SendToICS(ics_prefix);
\r
10945 SendToICS("tomove black\n");
\r
10950 EditPositionMenuEvent(selection, x, y)
\r
10951 ChessSquare selection;
\r
10954 char buf[MSG_SIZ];
\r
10955 ChessSquare piece = boards[0][y][x];
\r
10957 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10959 switch (selection) {
\r
10961 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10962 SendToICS(ics_prefix);
\r
10963 SendToICS("bsetup clear\n");
\r
10964 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10965 SendToICS(ics_prefix);
\r
10966 SendToICS("clearboard\n");
\r
10968 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10969 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10970 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10971 if (gameMode == IcsExamining) {
\r
10972 if (boards[currentMove][y][x] != EmptySquare) {
\r
10973 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10974 AAA + x, ONE + y);
\r
10978 boards[0][y][x] = p;
\r
10983 if (gameMode == EditPosition) {
\r
10984 DrawPosition(FALSE, boards[0]);
\r
10989 SetWhiteToPlayEvent();
\r
10993 SetBlackToPlayEvent();
\r
10996 case EmptySquare:
\r
10997 if (gameMode == IcsExamining) {
\r
10998 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
11001 boards[0][y][x] = EmptySquare;
\r
11002 DrawPosition(FALSE, boards[0]);
\r
11006 case PromotePiece:
\r
11007 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11008 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11009 selection = (ChessSquare) (PROMOTED piece);
\r
11010 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11011 else selection = (ChessSquare)((int)piece - 1);
\r
11012 goto defaultlabel;
\r
11014 case DemotePiece:
\r
11015 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11016 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11017 selection = (ChessSquare) (DEMOTED piece);
\r
11018 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11019 else selection = (ChessSquare)((int)piece + 1);
\r
11020 goto defaultlabel;
\r
11024 if(gameInfo.variant == VariantShatranj ||
\r
11025 gameInfo.variant == VariantXiangqi ||
\r
11026 gameInfo.variant == VariantCourier )
\r
11027 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11028 goto defaultlabel;
\r
11032 if(gameInfo.variant == VariantXiangqi)
\r
11033 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11034 if(gameInfo.variant == VariantKnightmate)
\r
11035 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11038 if (gameMode == IcsExamining) {
\r
11039 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11040 PieceToChar(selection), AAA + x, ONE + y);
\r
11043 boards[0][y][x] = selection;
\r
11044 DrawPosition(FALSE, boards[0]);
\r
11052 DropMenuEvent(selection, x, y)
\r
11053 ChessSquare selection;
\r
11056 ChessMove moveType;
\r
11058 switch (gameMode) {
\r
11059 case IcsPlayingWhite:
\r
11060 case MachinePlaysBlack:
\r
11061 if (!WhiteOnMove(currentMove)) {
\r
11062 DisplayMoveError(_("It is Black's turn"));
\r
11065 moveType = WhiteDrop;
\r
11067 case IcsPlayingBlack:
\r
11068 case MachinePlaysWhite:
\r
11069 if (WhiteOnMove(currentMove)) {
\r
11070 DisplayMoveError(_("It is White's turn"));
\r
11073 moveType = BlackDrop;
\r
11076 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11082 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11083 selection = (ChessSquare) ((int) selection
\r
11084 + (int) BlackPawn - (int) WhitePawn);
\r
11086 if (boards[currentMove][y][x] != EmptySquare) {
\r
11087 DisplayMoveError(_("That square is occupied"));
\r
11091 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11097 /* Accept a pending offer of any kind from opponent */
\r
11099 if (appData.icsActive) {
\r
11100 SendToICS(ics_prefix);
\r
11101 SendToICS("accept\n");
\r
11102 } else if (cmailMsgLoaded) {
\r
11103 if (currentMove == cmailOldMove &&
\r
11104 commentList[cmailOldMove] != NULL &&
\r
11105 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11106 "Black offers a draw" : "White offers a draw")) {
\r
11108 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11109 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11111 DisplayError(_("There is no pending offer on this move"), 0);
\r
11112 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11115 /* Not used for offers from chess program */
\r
11122 /* Decline a pending offer of any kind from opponent */
\r
11124 if (appData.icsActive) {
\r
11125 SendToICS(ics_prefix);
\r
11126 SendToICS("decline\n");
\r
11127 } else if (cmailMsgLoaded) {
\r
11128 if (currentMove == cmailOldMove &&
\r
11129 commentList[cmailOldMove] != NULL &&
\r
11130 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11131 "Black offers a draw" : "White offers a draw")) {
\r
11133 AppendComment(cmailOldMove, "Draw declined");
\r
11134 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11135 #endif /*NOTDEF*/
\r
11137 DisplayError(_("There is no pending offer on this move"), 0);
\r
11140 /* Not used for offers from chess program */
\r
11147 /* Issue ICS rematch command */
\r
11148 if (appData.icsActive) {
\r
11149 SendToICS(ics_prefix);
\r
11150 SendToICS("rematch\n");
\r
11157 /* Call your opponent's flag (claim a win on time) */
\r
11158 if (appData.icsActive) {
\r
11159 SendToICS(ics_prefix);
\r
11160 SendToICS("flag\n");
\r
11162 switch (gameMode) {
\r
11165 case MachinePlaysWhite:
\r
11168 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11171 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11173 DisplayError(_("Your opponent is not out of time"), 0);
\r
11176 case MachinePlaysBlack:
\r
11179 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11182 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11184 DisplayError(_("Your opponent is not out of time"), 0);
\r
11194 /* Offer draw or accept pending draw offer from opponent */
\r
11196 if (appData.icsActive) {
\r
11197 /* Note: tournament rules require draw offers to be
\r
11198 made after you make your move but before you punch
\r
11199 your clock. Currently ICS doesn't let you do that;
\r
11200 instead, you immediately punch your clock after making
\r
11201 a move, but you can offer a draw at any time. */
\r
11203 SendToICS(ics_prefix);
\r
11204 SendToICS("draw\n");
\r
11205 } else if (cmailMsgLoaded) {
\r
11206 if (currentMove == cmailOldMove &&
\r
11207 commentList[cmailOldMove] != NULL &&
\r
11208 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11209 "Black offers a draw" : "White offers a draw")) {
\r
11210 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11211 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11212 } else if (currentMove == cmailOldMove + 1) {
\r
11213 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11214 "White offers a draw" : "Black offers a draw";
\r
11215 AppendComment(currentMove, offer);
\r
11216 DisplayComment(currentMove - 1, offer);
\r
11217 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11219 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11220 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11222 } else if (first.offeredDraw) {
\r
11223 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11225 if (first.sendDrawOffers) {
\r
11226 SendToProgram("draw\n", &first);
\r
11227 userOfferedDraw = TRUE;
\r
11235 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11237 if (appData.icsActive) {
\r
11238 SendToICS(ics_prefix);
\r
11239 SendToICS("adjourn\n");
\r
11241 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11249 /* Offer Abort or accept pending Abort offer from opponent */
\r
11251 if (appData.icsActive) {
\r
11252 SendToICS(ics_prefix);
\r
11253 SendToICS("abort\n");
\r
11255 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11262 /* Resign. You can do this even if it's not your turn. */
\r
11264 if (appData.icsActive) {
\r
11265 SendToICS(ics_prefix);
\r
11266 SendToICS("resign\n");
\r
11268 switch (gameMode) {
\r
11269 case MachinePlaysWhite:
\r
11270 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11272 case MachinePlaysBlack:
\r
11273 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11276 if (cmailMsgLoaded) {
\r
11278 if (WhiteOnMove(cmailOldMove)) {
\r
11279 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11281 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11283 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11294 StopObservingEvent()
\r
11296 /* Stop observing current games */
\r
11297 SendToICS(ics_prefix);
\r
11298 SendToICS("unobserve\n");
\r
11302 StopExaminingEvent()
\r
11304 /* Stop observing current game */
\r
11305 SendToICS(ics_prefix);
\r
11306 SendToICS("unexamine\n");
\r
11310 ForwardInner(target)
\r
11315 if (appData.debugMode)
\r
11316 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11317 target, currentMove, forwardMostMove);
\r
11319 if (gameMode == EditPosition)
\r
11322 if (gameMode == PlayFromGameFile && !pausing)
\r
11325 if (gameMode == IcsExamining && pausing)
\r
11326 limit = pauseExamForwardMostMove;
\r
11328 limit = forwardMostMove;
\r
11330 if (target > limit) target = limit;
\r
11332 if (target > 0 && moveList[target - 1][0]) {
\r
11333 int fromX, fromY, toX, toY;
\r
11334 toX = moveList[target - 1][2] - AAA;
\r
11335 toY = moveList[target - 1][3] - ONE;
\r
11336 if (moveList[target - 1][1] == '@') {
\r
11337 if (appData.highlightLastMove) {
\r
11338 SetHighlights(-1, -1, toX, toY);
\r
11341 fromX = moveList[target - 1][0] - AAA;
\r
11342 fromY = moveList[target - 1][1] - ONE;
\r
11343 if (target == currentMove + 1) {
\r
11344 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11346 if (appData.highlightLastMove) {
\r
11347 SetHighlights(fromX, fromY, toX, toY);
\r
11351 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11352 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11353 gameMode == AnalyzeFile) {
\r
11354 while (currentMove < target) {
\r
11355 SendMoveToProgram(currentMove++, &first);
\r
11358 currentMove = target;
\r
11361 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11362 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11363 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11365 DisplayBothClocks();
\r
11366 DisplayMove(currentMove - 1);
\r
11367 DrawPosition(FALSE, boards[currentMove]);
\r
11368 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11369 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11370 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11378 if (gameMode == IcsExamining && !pausing) {
\r
11379 SendToICS(ics_prefix);
\r
11380 SendToICS("forward\n");
\r
11382 ForwardInner(currentMove + 1);
\r
11389 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11390 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11391 * the remaining moves to the engine. Otherwise we get analysis output
\r
11392 * after each move.
\r
11394 if (first.analysisSupport) {
\r
11395 SendToProgram("exit\nforce\n", &first);
\r
11396 first.analyzing = FALSE;
\r
11400 if (gameMode == IcsExamining && !pausing) {
\r
11401 SendToICS(ics_prefix);
\r
11402 SendToICS("forward 999999\n");
\r
11404 ForwardInner(forwardMostMove);
\r
11407 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11408 /* we have fed all the moves, so reactivate analysis mode */
\r
11409 SendToProgram("analyze\n", &first);
\r
11410 first.analyzing = TRUE;
\r
11411 /*first.maybeThinking = TRUE;*/
\r
11412 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11417 BackwardInner(target)
\r
11420 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11422 if (appData.debugMode)
\r
11423 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11424 target, currentMove, forwardMostMove);
\r
11426 if (gameMode == EditPosition) return;
\r
11427 if (currentMove <= backwardMostMove) {
\r
11428 ClearHighlights();
\r
11429 DrawPosition(full_redraw, boards[currentMove]);
\r
11432 if (gameMode == PlayFromGameFile && !pausing)
\r
11435 if (moveList[target][0]) {
\r
11436 int fromX, fromY, toX, toY;
\r
11437 toX = moveList[target][2] - AAA;
\r
11438 toY = moveList[target][3] - ONE;
\r
11439 if (moveList[target][1] == '@') {
\r
11440 if (appData.highlightLastMove) {
\r
11441 SetHighlights(-1, -1, toX, toY);
\r
11444 fromX = moveList[target][0] - AAA;
\r
11445 fromY = moveList[target][1] - ONE;
\r
11446 if (target == currentMove - 1) {
\r
11447 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11449 if (appData.highlightLastMove) {
\r
11450 SetHighlights(fromX, fromY, toX, toY);
\r
11454 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11455 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11456 while (currentMove > target) {
\r
11457 SendToProgram("undo\n", &first);
\r
11461 currentMove = target;
\r
11464 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11465 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11466 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11468 DisplayBothClocks();
\r
11469 DisplayMove(currentMove - 1);
\r
11470 DrawPosition(full_redraw, boards[currentMove]);
\r
11471 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11472 // [HGM] PV info: routine tests if comment empty
\r
11473 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11479 if (gameMode == IcsExamining && !pausing) {
\r
11480 SendToICS(ics_prefix);
\r
11481 SendToICS("backward\n");
\r
11483 BackwardInner(currentMove - 1);
\r
11490 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11491 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11492 * all the moves. Otherwise we get analysis output after each undo.
\r
11494 if (first.analysisSupport) {
\r
11495 SendToProgram("exit\nforce\n", &first);
\r
11496 first.analyzing = FALSE;
\r
11500 if (gameMode == IcsExamining && !pausing) {
\r
11501 SendToICS(ics_prefix);
\r
11502 SendToICS("backward 999999\n");
\r
11504 BackwardInner(backwardMostMove);
\r
11507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11508 /* we have fed all the moves, so reactivate analysis mode */
\r
11509 SendToProgram("analyze\n", &first);
\r
11510 first.analyzing = TRUE;
\r
11511 /*first.maybeThinking = TRUE;*/
\r
11512 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11517 ToNrEvent(int to)
\r
11519 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11520 if (to >= forwardMostMove) to = forwardMostMove;
\r
11521 if (to <= backwardMostMove) to = backwardMostMove;
\r
11522 if (to < currentMove) {
\r
11523 BackwardInner(to);
\r
11525 ForwardInner(to);
\r
11532 if (gameMode != IcsExamining) {
\r
11533 DisplayError(_("You are not examining a game"), 0);
\r
11537 DisplayError(_("You can't revert while pausing"), 0);
\r
11540 SendToICS(ics_prefix);
\r
11541 SendToICS("revert\n");
\r
11545 RetractMoveEvent()
\r
11547 switch (gameMode) {
\r
11548 case MachinePlaysWhite:
\r
11549 case MachinePlaysBlack:
\r
11550 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11551 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11554 if (forwardMostMove < 2) return;
\r
11555 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11556 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11557 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11558 DisplayBothClocks();
\r
11559 DisplayMove(currentMove - 1);
\r
11560 ClearHighlights();/*!! could figure this out*/
\r
11561 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11562 SendToProgram("remove\n", &first);
\r
11563 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11566 case BeginningOfGame:
\r
11570 case IcsPlayingWhite:
\r
11571 case IcsPlayingBlack:
\r
11572 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11573 SendToICS(ics_prefix);
\r
11574 SendToICS("takeback 2\n");
\r
11576 SendToICS(ics_prefix);
\r
11577 SendToICS("takeback 1\n");
\r
11586 ChessProgramState *cps;
\r
11588 switch (gameMode) {
\r
11589 case MachinePlaysWhite:
\r
11590 if (!WhiteOnMove(forwardMostMove)) {
\r
11591 DisplayError(_("It is your turn"), 0);
\r
11596 case MachinePlaysBlack:
\r
11597 if (WhiteOnMove(forwardMostMove)) {
\r
11598 DisplayError(_("It is your turn"), 0);
\r
11603 case TwoMachinesPlay:
\r
11604 if (WhiteOnMove(forwardMostMove) ==
\r
11605 (first.twoMachinesColor[0] == 'w')) {
\r
11611 case BeginningOfGame:
\r
11615 SendToProgram("?\n", cps);
\r
11619 TruncateGameEvent()
\r
11622 if (gameMode != EditGame) return;
\r
11629 if (forwardMostMove > currentMove) {
\r
11630 if (gameInfo.resultDetails != NULL) {
\r
11631 free(gameInfo.resultDetails);
\r
11632 gameInfo.resultDetails = NULL;
\r
11633 gameInfo.result = GameUnfinished;
\r
11635 forwardMostMove = currentMove;
\r
11636 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11644 if (appData.noChessProgram) return;
\r
11645 switch (gameMode) {
\r
11646 case MachinePlaysWhite:
\r
11647 if (WhiteOnMove(forwardMostMove)) {
\r
11648 DisplayError(_("Wait until your turn"), 0);
\r
11652 case BeginningOfGame:
\r
11653 case MachinePlaysBlack:
\r
11654 if (!WhiteOnMove(forwardMostMove)) {
\r
11655 DisplayError(_("Wait until your turn"), 0);
\r
11660 DisplayError(_("No hint available"), 0);
\r
11663 SendToProgram("hint\n", &first);
\r
11664 hintRequested = TRUE;
\r
11670 if (appData.noChessProgram) return;
\r
11671 switch (gameMode) {
\r
11672 case MachinePlaysWhite:
\r
11673 if (WhiteOnMove(forwardMostMove)) {
\r
11674 DisplayError(_("Wait until your turn"), 0);
\r
11678 case BeginningOfGame:
\r
11679 case MachinePlaysBlack:
\r
11680 if (!WhiteOnMove(forwardMostMove)) {
\r
11681 DisplayError(_("Wait until your turn"), 0);
\r
11685 case EditPosition:
\r
11686 EditPositionDone();
\r
11688 case TwoMachinesPlay:
\r
11693 SendToProgram("bk\n", &first);
\r
11694 bookOutput[0] = NULLCHAR;
\r
11695 bookRequested = TRUE;
\r
11701 char *tags = PGNTags(&gameInfo);
\r
11702 TagsPopUp(tags, CmailMsg());
\r
11706 /* end button procedures */
\r
11709 PrintPosition(fp, move)
\r
11715 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11716 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11717 char c = PieceToChar(boards[move][i][j]);
\r
11718 fputc(c == 'x' ? '.' : c, fp);
\r
11719 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11722 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11723 fprintf(fp, "white to play\n");
\r
11725 fprintf(fp, "black to play\n");
\r
11729 PrintOpponents(fp)
\r
11732 if (gameInfo.white != NULL) {
\r
11733 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11735 fprintf(fp, "\n");
\r
11739 /* Find last component of program's own name, using some heuristics */
\r
11741 TidyProgramName(prog, host, buf)
\r
11742 char *prog, *host, buf[MSG_SIZ];
\r
11745 int local = (strcmp(host, "localhost") == 0);
\r
11746 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11748 while (*p == ' ') p++;
\r
11751 if (*prog == '"' || *prog == '\'') {
\r
11752 q = strchr(prog + 1, *prog);
\r
11754 q = strchr(prog, ' ');
\r
11756 if (q == NULL) q = prog + strlen(prog);
\r
11758 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11760 if(p == prog && *p == '"') p++;
\r
11761 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11762 memcpy(buf, p, q - p);
\r
11763 buf[q - p] = NULLCHAR;
\r
11765 strcat(buf, "@");
\r
11766 strcat(buf, host);
\r
11771 TimeControlTagValue()
\r
11773 char buf[MSG_SIZ];
\r
11774 if (!appData.clockMode) {
\r
11775 strcpy(buf, "-");
\r
11776 } else if (movesPerSession > 0) {
\r
11777 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11778 } else if (timeIncrement == 0) {
\r
11779 sprintf(buf, "%ld", timeControl/1000);
\r
11781 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11783 return StrSave(buf);
\r
11789 /* This routine is used only for certain modes */
\r
11790 VariantClass v = gameInfo.variant;
\r
11791 ClearGameInfo(&gameInfo);
\r
11792 gameInfo.variant = v;
\r
11794 switch (gameMode) {
\r
11795 case MachinePlaysWhite:
\r
11796 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11797 gameInfo.site = StrSave(HostName());
\r
11798 gameInfo.date = PGNDate();
\r
11799 gameInfo.round = StrSave("-");
\r
11800 gameInfo.white = StrSave(first.tidy);
\r
11801 gameInfo.black = StrSave(UserName());
\r
11802 gameInfo.timeControl = TimeControlTagValue();
\r
11805 case MachinePlaysBlack:
\r
11806 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11807 gameInfo.site = StrSave(HostName());
\r
11808 gameInfo.date = PGNDate();
\r
11809 gameInfo.round = StrSave("-");
\r
11810 gameInfo.white = StrSave(UserName());
\r
11811 gameInfo.black = StrSave(first.tidy);
\r
11812 gameInfo.timeControl = TimeControlTagValue();
\r
11815 case TwoMachinesPlay:
\r
11816 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11817 gameInfo.site = StrSave(HostName());
\r
11818 gameInfo.date = PGNDate();
\r
11819 if (matchGame > 0) {
\r
11820 char buf[MSG_SIZ];
\r
11821 sprintf(buf, "%d", matchGame);
\r
11822 gameInfo.round = StrSave(buf);
\r
11824 gameInfo.round = StrSave("-");
\r
11826 if (first.twoMachinesColor[0] == 'w') {
\r
11827 gameInfo.white = StrSave(first.tidy);
\r
11828 gameInfo.black = StrSave(second.tidy);
\r
11830 gameInfo.white = StrSave(second.tidy);
\r
11831 gameInfo.black = StrSave(first.tidy);
\r
11833 gameInfo.timeControl = TimeControlTagValue();
\r
11837 gameInfo.event = StrSave("Edited game");
\r
11838 gameInfo.site = StrSave(HostName());
\r
11839 gameInfo.date = PGNDate();
\r
11840 gameInfo.round = StrSave("-");
\r
11841 gameInfo.white = StrSave("-");
\r
11842 gameInfo.black = StrSave("-");
\r
11845 case EditPosition:
\r
11846 gameInfo.event = StrSave("Edited position");
\r
11847 gameInfo.site = StrSave(HostName());
\r
11848 gameInfo.date = PGNDate();
\r
11849 gameInfo.round = StrSave("-");
\r
11850 gameInfo.white = StrSave("-");
\r
11851 gameInfo.black = StrSave("-");
\r
11854 case IcsPlayingWhite:
\r
11855 case IcsPlayingBlack:
\r
11856 case IcsObserving:
\r
11857 case IcsExamining:
\r
11860 case PlayFromGameFile:
\r
11861 gameInfo.event = StrSave("Game from non-PGN file");
\r
11862 gameInfo.site = StrSave(HostName());
\r
11863 gameInfo.date = PGNDate();
\r
11864 gameInfo.round = StrSave("-");
\r
11865 gameInfo.white = StrSave("?");
\r
11866 gameInfo.black = StrSave("?");
\r
11875 ReplaceComment(index, text)
\r
11881 while (*text == '\n') text++;
\r
11882 len = strlen(text);
\r
11883 while (len > 0 && text[len - 1] == '\n') len--;
\r
11885 if (commentList[index] != NULL)
\r
11886 free(commentList[index]);
\r
11889 commentList[index] = NULL;
\r
11892 commentList[index] = (char *) malloc(len + 2);
\r
11893 strncpy(commentList[index], text, len);
\r
11894 commentList[index][len] = '\n';
\r
11895 commentList[index][len + 1] = NULLCHAR;
\r
11908 if (ch == '\r') continue;
\r
11910 } while (ch != '\0');
\r
11914 AppendComment(index, text)
\r
11921 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11924 while (*text == '\n') text++;
\r
11925 len = strlen(text);
\r
11926 while (len > 0 && text[len - 1] == '\n') len--;
\r
11928 if (len == 0) return;
\r
11930 if (commentList[index] != NULL) {
\r
11931 old = commentList[index];
\r
11932 oldlen = strlen(old);
\r
11933 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11934 strcpy(commentList[index], old);
\r
11936 strncpy(&commentList[index][oldlen], text, len);
\r
11937 commentList[index][oldlen + len] = '\n';
\r
11938 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11940 commentList[index] = (char *) malloc(len + 2);
\r
11941 strncpy(commentList[index], text, len);
\r
11942 commentList[index][len] = '\n';
\r
11943 commentList[index][len + 1] = NULLCHAR;
\r
11947 static char * FindStr( char * text, char * sub_text )
\r
11949 char * result = strstr( text, sub_text );
\r
11951 if( result != NULL ) {
\r
11952 result += strlen( sub_text );
\r
11958 /* [AS] Try to extract PV info from PGN comment */
\r
11959 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11960 char *GetInfoFromComment( int index, char * text )
\r
11962 char * sep = text;
\r
11964 if( text != NULL && index > 0 ) {
\r
11967 int time = -1, sec = 0, deci;
\r
11968 char * s_eval = FindStr( text, "[%eval " );
\r
11969 char * s_emt = FindStr( text, "[%emt " );
\r
11971 if( s_eval != NULL || s_emt != NULL ) {
\r
11975 if( s_eval != NULL ) {
\r
11976 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11980 if( delim != ']' ) {
\r
11985 if( s_emt != NULL ) {
\r
11989 /* We expect something like: [+|-]nnn.nn/dd */
\r
11990 int score_lo = 0;
\r
11992 sep = strchr( text, '/' );
\r
11993 if( sep == NULL || sep < (text+4) ) {
\r
11997 time = -1; sec = -1; deci = -1;
\r
11998 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11999 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
12000 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
12001 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12005 if( score_lo < 0 || score_lo >= 100 ) {
\r
12009 if(sec >= 0) time = 600*time + 10*sec; else
\r
12010 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12012 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12014 /* [HGM] PV time: now locate end of PV info */
\r
12015 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12017 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12019 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12021 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12022 while(*sep == ' ') sep++;
\r
12025 if( depth <= 0 ) {
\r
12033 pvInfoList[index-1].depth = depth;
\r
12034 pvInfoList[index-1].score = score;
\r
12035 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12041 SendToProgram(message, cps)
\r
12043 ChessProgramState *cps;
\r
12045 int count, outCount, error;
\r
12046 char buf[MSG_SIZ];
\r
12048 if (cps->pr == NULL) return;
\r
12051 if (appData.debugMode) {
\r
12053 GetTimeMark(&now);
\r
12054 fprintf(debugFP, "%ld >%-6s: %s",
\r
12055 SubtractTimeMarks(&now, &programStartTime),
\r
12056 cps->which, message);
\r
12059 count = strlen(message);
\r
12060 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12061 if (outCount < count && !exiting
\r
12062 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12063 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12064 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12065 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12066 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12067 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12069 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12071 gameInfo.resultDetails = buf;
\r
12073 DisplayFatalError(buf, error, 1);
\r
12078 ReceiveFromProgram(isr, closure, message, count, error)
\r
12079 InputSourceRef isr;
\r
12080 VOIDSTAR closure;
\r
12086 char buf[MSG_SIZ];
\r
12087 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12089 if (isr != cps->isr) return; /* Killed intentionally */
\r
12090 if (count <= 0) {
\r
12091 if (count == 0) {
\r
12093 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12094 cps->which, cps->program);
\r
12095 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12096 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12097 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12098 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12100 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12102 gameInfo.resultDetails = buf;
\r
12104 RemoveInputSource(cps->isr);
\r
12105 DisplayFatalError(buf, 0, 1);
\r
12108 _("Error reading from %s chess program (%s)"),
\r
12109 cps->which, cps->program);
\r
12110 RemoveInputSource(cps->isr);
\r
12112 /* [AS] Program is misbehaving badly... kill it */
\r
12113 if( count == -2 ) {
\r
12114 DestroyChildProcess( cps->pr, 9 );
\r
12115 cps->pr = NoProc;
\r
12118 DisplayFatalError(buf, error, 1);
\r
12123 if ((end_str = strchr(message, '\r')) != NULL)
\r
12124 *end_str = NULLCHAR;
\r
12125 if ((end_str = strchr(message, '\n')) != NULL)
\r
12126 *end_str = NULLCHAR;
\r
12128 if (appData.debugMode) {
\r
12129 TimeMark now; int print = 1;
\r
12130 char *quote = ""; char c; int i;
\r
12132 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12133 char start = message[0];
\r
12134 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12135 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12136 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12137 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12138 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12139 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12140 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12141 { quote = "# "; print = (appData.engineComments == 2); }
\r
12142 message[0] = start; // restore original message
\r
12145 GetTimeMark(&now);
\r
12146 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12147 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12153 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12154 if (appData.icsEngineAnalyze) {
\r
12155 if (strstr(message, "whisper") != NULL ||
\r
12156 strstr(message, "kibitz") != NULL ||
\r
12157 strstr(message, "tellics") != NULL) return;
\r
12160 HandleMachineMove(message, cps);
\r
12165 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12166 ChessProgramState *cps;
\r
12167 int mps, inc, sd, st;
\r
12170 char buf[MSG_SIZ];
\r
12173 if( timeControl_2 > 0 ) {
\r
12174 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12175 tc = timeControl_2;
\r
12178 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12179 inc /= cps->timeOdds;
\r
12180 st /= cps->timeOdds;
\r
12182 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12185 /* Set exact time per move, normally using st command */
\r
12186 if (cps->stKludge) {
\r
12187 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12188 seconds = st % 60;
\r
12189 if (seconds == 0) {
\r
12190 sprintf(buf, "level 1 %d\n", st/60);
\r
12192 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12195 sprintf(buf, "st %d\n", st);
\r
12198 /* Set conventional or incremental time control, using level command */
\r
12199 if (seconds == 0) {
\r
12200 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12201 Fixed in later versions, but still avoid :seconds
\r
12202 when seconds is 0. */
\r
12203 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12205 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12206 seconds, inc/1000);
\r
12209 SendToProgram(buf, cps);
\r
12211 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12212 /* Orthogonally, limit search to given depth */
\r
12214 if (cps->sdKludge) {
\r
12215 sprintf(buf, "depth\n%d\n", sd);
\r
12217 sprintf(buf, "sd %d\n", sd);
\r
12219 SendToProgram(buf, cps);
\r
12222 if(cps->nps > 0) { /* [HGM] nps */
\r
12223 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12225 sprintf(buf, "nps %d\n", cps->nps);
\r
12226 SendToProgram(buf, cps);
\r
12231 ChessProgramState *WhitePlayer()
\r
12232 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12234 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12235 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12241 SendTimeRemaining(cps, machineWhite)
\r
12242 ChessProgramState *cps;
\r
12243 int /*boolean*/ machineWhite;
\r
12245 char message[MSG_SIZ];
\r
12246 long time, otime;
\r
12248 /* Note: this routine must be called when the clocks are stopped
\r
12249 or when they have *just* been set or switched; otherwise
\r
12250 it will be off by the time since the current tick started.
\r
12252 if (machineWhite) {
\r
12253 time = whiteTimeRemaining / 10;
\r
12254 otime = blackTimeRemaining / 10;
\r
12256 time = blackTimeRemaining / 10;
\r
12257 otime = whiteTimeRemaining / 10;
\r
12259 /* [HGM] translate opponent's time by time-odds factor */
\r
12260 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12261 if (appData.debugMode) {
\r
12262 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12265 if (time <= 0) time = 1;
\r
12266 if (otime <= 0) otime = 1;
\r
12268 sprintf(message, "time %ld\n", time);
\r
12269 SendToProgram(message, cps);
\r
12271 sprintf(message, "otim %ld\n", otime);
\r
12272 SendToProgram(message, cps);
\r
12276 BoolFeature(p, name, loc, cps)
\r
12280 ChessProgramState *cps;
\r
12282 char buf[MSG_SIZ];
\r
12283 int len = strlen(name);
\r
12285 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12287 sscanf(*p, "%d", &val);
\r
12288 *loc = (val != 0);
\r
12289 while (**p && **p != ' ') (*p)++;
\r
12290 sprintf(buf, "accepted %s\n", name);
\r
12291 SendToProgram(buf, cps);
\r
12298 IntFeature(p, name, loc, cps)
\r
12302 ChessProgramState *cps;
\r
12304 char buf[MSG_SIZ];
\r
12305 int len = strlen(name);
\r
12306 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12308 sscanf(*p, "%d", loc);
\r
12309 while (**p && **p != ' ') (*p)++;
\r
12310 sprintf(buf, "accepted %s\n", name);
\r
12311 SendToProgram(buf, cps);
\r
12318 StringFeature(p, name, loc, cps)
\r
12322 ChessProgramState *cps;
\r
12324 char buf[MSG_SIZ];
\r
12325 int len = strlen(name);
\r
12326 if (strncmp((*p), name, len) == 0
\r
12327 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12329 sscanf(*p, "%[^\"]", loc);
\r
12330 while (**p && **p != '\"') (*p)++;
\r
12331 if (**p == '\"') (*p)++;
\r
12332 sprintf(buf, "accepted %s\n", name);
\r
12333 SendToProgram(buf, cps);
\r
12340 ParseOption(Option *opt, ChessProgramState *cps)
\r
12341 // [HGM] options: process the string that defines an engine option, and determine
\r
12342 // name, type, default value, and allowed value range
\r
12344 char *p, *q, buf[MSG_SIZ];
\r
12345 int n, min = (-1)<<31, max = 1<<31, def;
\r
12347 if(p = strstr(opt->name, " -spin ")) {
\r
12348 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12349 if(max < min) max = min; // enforce consistency
\r
12350 if(def < min) def = min;
\r
12351 if(def > max) def = max;
\r
12352 opt->value = def;
\r
12355 opt->type = Spin;
\r
12356 } else if(p = strstr(opt->name, " -string ")) {
\r
12357 opt->textValue = p+9;
\r
12358 opt->type = TextBox;
\r
12359 } else if(p = strstr(opt->name, " -check ")) {
\r
12360 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12361 opt->value = (def != 0);
\r
12362 opt->type = CheckBox;
\r
12363 } else if(p = strstr(opt->name, " -combo ")) {
\r
12364 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12365 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12366 opt->value = n = 0;
\r
12367 while(q = StrStr(q, " /// ")) {
\r
12368 n++; *q = 0; // count choices, and null-terminate each of them
\r
12370 if(*q == '*') { // remember default, which is marked with * prefix
\r
12374 cps->comboList[cps->comboCnt++] = q;
\r
12376 cps->comboList[cps->comboCnt++] = NULL;
\r
12377 opt->max = n + 1;
\r
12378 opt->type = ComboBox;
\r
12379 } else if(p = strstr(opt->name, " -button")) {
\r
12380 opt->type = Button;
\r
12381 } else if(p = strstr(opt->name, " -save")) {
\r
12382 opt->type = SaveButton;
\r
12383 } else return FALSE;
\r
12384 *p = 0; // terminate option name
\r
12385 // now look if the command-line options define a setting for this engine option.
\r
12386 p = strstr(cps->optionSettings, opt->name);
\r
12387 if(p == cps->optionSettings || p[-1] == ',') {
\r
12388 sprintf(buf, "option %s", p);
\r
12389 if(p = strstr(buf, ",")) *p = 0;
\r
12390 strcat(buf, "\n");
\r
12391 SendToProgram(buf, cps);
\r
12397 FeatureDone(cps, val)
\r
12398 ChessProgramState* cps;
\r
12401 DelayedEventCallback cb = GetDelayedEvent();
\r
12402 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12403 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12404 CancelDelayedEvent();
\r
12405 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12407 cps->initDone = val;
\r
12410 /* Parse feature command from engine */
\r
12412 ParseFeatures(args, cps)
\r
12414 ChessProgramState *cps;
\r
12419 char buf[MSG_SIZ];
\r
12422 while (*p == ' ') p++;
\r
12423 if (*p == NULLCHAR) return;
\r
12425 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12426 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12427 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12428 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12429 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12430 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12431 /* Engine can disable reuse, but can't enable it if user said no */
\r
12432 if (!val) cps->reuse = FALSE;
\r
12435 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12436 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12437 if (gameMode == TwoMachinesPlay) {
\r
12438 DisplayTwoMachinesTitle();
\r
12440 DisplayTitle("");
\r
12444 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12445 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12446 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12447 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12448 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12449 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12450 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12451 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12452 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12453 if (IntFeature(&p, "done", &val, cps)) {
\r
12454 FeatureDone(cps, val);
\r
12457 /* Added by Tord: */
\r
12458 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12459 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12460 /* End of additions by Tord */
\r
12462 /* [HGM] added features: */
\r
12463 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12464 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12465 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12466 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12467 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12468 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12469 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12470 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12471 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12472 cps->nrOptions--;
\r
12473 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12474 DisplayError(buf, 0);
\r
12478 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12479 /* End of additions by HGM */
\r
12481 /* unknown feature: complain and skip */
\r
12483 while (*q && *q != '=') q++;
\r
12484 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12485 SendToProgram(buf, cps);
\r
12489 if (*p == '\"') {
\r
12491 while (*p && *p != '\"') p++;
\r
12492 if (*p == '\"') p++;
\r
12494 while (*p && *p != ' ') p++;
\r
12502 PeriodicUpdatesEvent(newState)
\r
12505 if (newState == appData.periodicUpdates)
\r
12508 appData.periodicUpdates=newState;
\r
12510 /* Display type changes, so update it now */
\r
12511 DisplayAnalysis();
\r
12513 /* Get the ball rolling again... */
\r
12515 AnalysisPeriodicEvent(1);
\r
12516 StartAnalysisClock();
\r
12521 PonderNextMoveEvent(newState)
\r
12524 if (newState == appData.ponderNextMove) return;
\r
12525 if (gameMode == EditPosition) EditPositionDone();
\r
12527 SendToProgram("hard\n", &first);
\r
12528 if (gameMode == TwoMachinesPlay) {
\r
12529 SendToProgram("hard\n", &second);
\r
12532 SendToProgram("easy\n", &first);
\r
12533 thinkOutput[0] = NULLCHAR;
\r
12534 if (gameMode == TwoMachinesPlay) {
\r
12535 SendToProgram("easy\n", &second);
\r
12538 appData.ponderNextMove = newState;
\r
12542 NewSettingEvent(option, command, value)
\r
12544 int option, value;
\r
12546 char buf[MSG_SIZ];
\r
12548 if (gameMode == EditPosition) EditPositionDone();
\r
12549 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12550 SendToProgram(buf, &first);
\r
12551 if (gameMode == TwoMachinesPlay) {
\r
12552 SendToProgram(buf, &second);
\r
12557 ShowThinkingEvent()
\r
12558 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12560 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12561 int newState = appData.showThinking
\r
12562 // [HGM] thinking: other features now need thinking output as well
\r
12563 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12565 if (oldState == newState) return;
\r
12566 oldState = newState;
\r
12567 if (gameMode == EditPosition) EditPositionDone();
\r
12569 SendToProgram("post\n", &first);
\r
12570 if (gameMode == TwoMachinesPlay) {
\r
12571 SendToProgram("post\n", &second);
\r
12574 SendToProgram("nopost\n", &first);
\r
12575 thinkOutput[0] = NULLCHAR;
\r
12576 if (gameMode == TwoMachinesPlay) {
\r
12577 SendToProgram("nopost\n", &second);
\r
12580 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12584 AskQuestionEvent(title, question, replyPrefix, which)
\r
12585 char *title; char *question; char *replyPrefix; char *which;
\r
12587 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12588 if (pr == NoProc) return;
\r
12589 AskQuestion(title, question, replyPrefix, pr);
\r
12593 DisplayMove(moveNumber)
\r
12596 char message[MSG_SIZ];
\r
12597 char res[MSG_SIZ];
\r
12598 char cpThinkOutput[MSG_SIZ];
\r
12600 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
\r
12602 if (moveNumber == forwardMostMove - 1 ||
\r
12603 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12605 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12607 if (strchr(cpThinkOutput, '\n')) {
\r
12608 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12611 *cpThinkOutput = NULLCHAR;
\r
12614 /* [AS] Hide thinking from human user */
\r
12615 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12616 *cpThinkOutput = NULLCHAR;
\r
12617 if( thinkOutput[0] != NULLCHAR ) {
\r
12620 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12621 cpThinkOutput[i] = '.';
\r
12623 cpThinkOutput[i] = NULLCHAR;
\r
12624 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12628 if (moveNumber == forwardMostMove - 1 &&
\r
12629 gameInfo.resultDetails != NULL) {
\r
12630 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12631 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12633 sprintf(res, " {%s} %s",
\r
12634 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12637 res[0] = NULLCHAR;
\r
12640 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12641 DisplayMessage(res, cpThinkOutput);
\r
12643 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12644 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12645 parseList[moveNumber], res);
\r
12646 DisplayMessage(message, cpThinkOutput);
\r
12651 DisplayAnalysisText(text)
\r
12654 char buf[MSG_SIZ];
\r
12656 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12657 || appData.icsEngineAnalyze) {
\r
12658 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12659 AnalysisPopUp(buf, text);
\r
12664 only_one_move(str)
\r
12667 while (*str && isspace(*str)) ++str;
\r
12668 while (*str && !isspace(*str)) ++str;
\r
12669 if (!*str) return 1;
\r
12670 while (*str && isspace(*str)) ++str;
\r
12671 if (!*str) return 1;
\r
12676 DisplayAnalysis()
\r
12678 char buf[MSG_SIZ];
\r
12679 char lst[MSG_SIZ / 2];
\r
12681 static char *xtra[] = { "", " (--)", " (++)" };
\r
12684 if (programStats.time == 0) {
\r
12685 programStats.time = 1;
\r
12688 if (programStats.got_only_move) {
\r
12689 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12691 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12693 nps = (u64ToDouble(programStats.nodes) /
\r
12694 ((double)programStats.time /100.0));
\r
12696 cs = programStats.time % 100;
\r
12697 s = programStats.time / 100;
\r
12698 h = (s / (60*60));
\r
12703 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12704 if (programStats.move_name[0] != NULLCHAR) {
\r
12705 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12706 programStats.depth,
\r
12707 programStats.nr_moves-programStats.moves_left,
\r
12708 programStats.nr_moves, programStats.move_name,
\r
12709 ((float)programStats.score)/100.0, lst,
\r
12710 only_one_move(lst)?
\r
12711 xtra[programStats.got_fail] : "",
\r
12712 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12714 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12715 programStats.depth,
\r
12716 programStats.nr_moves-programStats.moves_left,
\r
12717 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12719 only_one_move(lst)?
\r
12720 xtra[programStats.got_fail] : "",
\r
12721 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12724 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12725 programStats.depth,
\r
12726 ((float)programStats.score)/100.0,
\r
12728 only_one_move(lst)?
\r
12729 xtra[programStats.got_fail] : "",
\r
12730 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12733 DisplayAnalysisText(buf);
\r
12737 DisplayComment(moveNumber, text)
\r
12741 char title[MSG_SIZ];
\r
12742 char buf[8000]; // comment can be long!
\r
12743 int score, depth;
\r
12745 if( appData.autoDisplayComment ) {
\r
12746 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12747 strcpy(title, "Comment");
\r
12749 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12750 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12751 parseList[moveNumber]);
\r
12753 } else title[0] = 0;
\r
12755 // [HGM] PV info: display PV info together with (or as) comment
\r
12756 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12757 if(text == NULL) text = "";
\r
12758 score = pvInfoList[moveNumber].score;
\r
12759 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12760 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12761 CommentPopUp(title, buf);
\r
12763 if (text != NULL)
\r
12764 CommentPopUp(title, text);
\r
12767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12768 * might be busy thinking or pondering. It can be omitted if your
\r
12769 * gnuchess is configured to stop thinking immediately on any user
\r
12770 * input. However, that gnuchess feature depends on the FIONREAD
\r
12771 * ioctl, which does not work properly on some flavors of Unix.
\r
12775 ChessProgramState *cps;
\r
12778 if (!cps->useSigint) return;
\r
12779 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12780 switch (gameMode) {
\r
12781 case MachinePlaysWhite:
\r
12782 case MachinePlaysBlack:
\r
12783 case TwoMachinesPlay:
\r
12784 case IcsPlayingWhite:
\r
12785 case IcsPlayingBlack:
\r
12786 case AnalyzeMode:
\r
12787 case AnalyzeFile:
\r
12788 /* Skip if we know it isn't thinking */
\r
12789 if (!cps->maybeThinking) return;
\r
12790 if (appData.debugMode)
\r
12791 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12792 InterruptChildProcess(cps->pr);
\r
12793 cps->maybeThinking = FALSE;
\r
12798 #endif /*ATTENTION*/
\r
12804 if (whiteTimeRemaining <= 0) {
\r
12805 if (!whiteFlag) {
\r
12806 whiteFlag = TRUE;
\r
12807 if (appData.icsActive) {
\r
12808 if (appData.autoCallFlag &&
\r
12809 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12810 SendToICS(ics_prefix);
\r
12811 SendToICS("flag\n");
\r
12815 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12817 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12818 if (appData.autoCallFlag) {
\r
12819 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12826 if (blackTimeRemaining <= 0) {
\r
12827 if (!blackFlag) {
\r
12828 blackFlag = TRUE;
\r
12829 if (appData.icsActive) {
\r
12830 if (appData.autoCallFlag &&
\r
12831 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12832 SendToICS(ics_prefix);
\r
12833 SendToICS("flag\n");
\r
12837 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12839 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12840 if (appData.autoCallFlag) {
\r
12841 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12852 CheckTimeControl()
\r
12854 if (!appData.clockMode || appData.icsActive ||
\r
12855 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12858 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12860 if ( !WhiteOnMove(forwardMostMove) )
\r
12861 /* White made time control */
\r
12862 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12863 /* [HGM] time odds: correct new time quota for time odds! */
\r
12864 / WhitePlayer()->timeOdds;
\r
12866 /* Black made time control */
\r
12867 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12868 / WhitePlayer()->other->timeOdds;
\r
12872 DisplayBothClocks()
\r
12874 int wom = gameMode == EditPosition ?
\r
12875 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12876 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12877 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12881 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12882 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12883 to use other calls if you don't. Clocks will be less accurate if
\r
12884 you have neither ftime nor gettimeofday.
\r
12887 /* Get the current time as a TimeMark */
\r
12892 #if HAVE_GETTIMEOFDAY
\r
12894 struct timeval timeVal;
\r
12895 struct timezone timeZone;
\r
12897 gettimeofday(&timeVal, &timeZone);
\r
12898 tm->sec = (long) timeVal.tv_sec;
\r
12899 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12901 #else /*!HAVE_GETTIMEOFDAY*/
\r
12904 #include <sys/timeb.h>
\r
12905 struct timeb timeB;
\r
12908 tm->sec = (long) timeB.time;
\r
12909 tm->ms = (int) timeB.millitm;
\r
12911 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12912 tm->sec = (long) time(NULL);
\r
12918 /* Return the difference in milliseconds between two
\r
12919 time marks. We assume the difference will fit in a long!
\r
12922 SubtractTimeMarks(tm2, tm1)
\r
12923 TimeMark *tm2, *tm1;
\r
12925 return 1000L*(tm2->sec - tm1->sec) +
\r
12926 (long) (tm2->ms - tm1->ms);
\r
12931 * Code to manage the game clocks.
\r
12933 * In tournament play, black starts the clock and then white makes a move.
\r
12934 * We give the human user a slight advantage if he is playing white---the
\r
12935 * clocks don't run until he makes his first move, so it takes zero time.
\r
12936 * Also, we don't account for network lag, so we could get out of sync
\r
12937 * with GNU Chess's clock -- but then, referees are always right.
\r
12940 static TimeMark tickStartTM;
\r
12941 static long intendedTickLength;
\r
12944 NextTickLength(timeRemaining)
\r
12945 long timeRemaining;
\r
12947 long nominalTickLength, nextTickLength;
\r
12949 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12950 nominalTickLength = 100L;
\r
12952 nominalTickLength = 1000L;
\r
12953 nextTickLength = timeRemaining % nominalTickLength;
\r
12954 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12956 return nextTickLength;
\r
12959 /* Adjust clock one minute up or down */
\r
12961 AdjustClock(Boolean which, int dir)
\r
12963 if(which) blackTimeRemaining += 60000*dir;
\r
12964 else whiteTimeRemaining += 60000*dir;
\r
12965 DisplayBothClocks();
\r
12968 /* Stop clocks and reset to a fresh time control */
\r
12972 (void) StopClockTimer();
\r
12973 if (appData.icsActive) {
\r
12974 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12975 } else { /* [HGM] correct new time quote for time odds */
\r
12976 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12977 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12979 if (whiteFlag || blackFlag) {
\r
12980 DisplayTitle("");
\r
12981 whiteFlag = blackFlag = FALSE;
\r
12983 DisplayBothClocks();
\r
12986 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12988 /* Decrement running clock by amount of time that has passed */
\r
12990 DecrementClocks()
\r
12992 long timeRemaining;
\r
12993 long lastTickLength, fudge;
\r
12996 if (!appData.clockMode) return;
\r
12997 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12999 GetTimeMark(&now);
\r
13001 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13003 /* Fudge if we woke up a little too soon */
\r
13004 fudge = intendedTickLength - lastTickLength;
\r
13005 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
13007 if (WhiteOnMove(forwardMostMove)) {
\r
13008 if(whiteNPS >= 0) lastTickLength = 0;
\r
13009 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
13010 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
13011 WhiteOnMove(currentMove));
\r
13013 if(blackNPS >= 0) lastTickLength = 0;
\r
13014 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13015 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13016 !WhiteOnMove(currentMove));
\r
13019 if (CheckFlags()) return;
\r
13021 tickStartTM = now;
\r
13022 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13023 StartClockTimer(intendedTickLength);
\r
13025 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13026 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13027 * with increment) the time remaining has increased to a level above the
\r
13028 * threshold, reset the alarm so it can sound again.
\r
13031 if (appData.icsActive && appData.icsAlarm) {
\r
13033 /* make sure we are dealing with the user's clock */
\r
13034 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13035 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13038 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13039 alarmSounded = FALSE;
\r
13040 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13041 PlayAlarmSound();
\r
13042 alarmSounded = TRUE;
\r
13048 /* A player has just moved, so stop the previously running
\r
13049 clock and (if in clock mode) start the other one.
\r
13050 We redisplay both clocks in case we're in ICS mode, because
\r
13051 ICS gives us an update to both clocks after every move.
\r
13052 Note that this routine is called *after* forwardMostMove
\r
13053 is updated, so the last fractional tick must be subtracted
\r
13054 from the color that is *not* on move now.
\r
13059 long lastTickLength;
\r
13061 int flagged = FALSE;
\r
13063 GetTimeMark(&now);
\r
13065 if (StopClockTimer() && appData.clockMode) {
\r
13066 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13067 if (WhiteOnMove(forwardMostMove)) {
\r
13068 if(blackNPS >= 0) lastTickLength = 0;
\r
13069 blackTimeRemaining -= lastTickLength;
\r
13070 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13071 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13072 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13073 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13075 if(whiteNPS >= 0) lastTickLength = 0;
\r
13076 whiteTimeRemaining -= lastTickLength;
\r
13077 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13078 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13079 pvInfoList[forwardMostMove-1].time =
\r
13080 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13082 flagged = CheckFlags();
\r
13084 CheckTimeControl();
\r
13086 if (flagged || !appData.clockMode) return;
\r
13088 switch (gameMode) {
\r
13089 case MachinePlaysBlack:
\r
13090 case MachinePlaysWhite:
\r
13091 case BeginningOfGame:
\r
13092 if (pausing) return;
\r
13096 case PlayFromGameFile:
\r
13097 case IcsExamining:
\r
13104 tickStartTM = now;
\r
13105 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13106 whiteTimeRemaining : blackTimeRemaining);
\r
13107 StartClockTimer(intendedTickLength);
\r
13111 /* Stop both clocks */
\r
13115 long lastTickLength;
\r
13118 if (!StopClockTimer()) return;
\r
13119 if (!appData.clockMode) return;
\r
13121 GetTimeMark(&now);
\r
13123 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13124 if (WhiteOnMove(forwardMostMove)) {
\r
13125 if(whiteNPS >= 0) lastTickLength = 0;
\r
13126 whiteTimeRemaining -= lastTickLength;
\r
13127 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13129 if(blackNPS >= 0) lastTickLength = 0;
\r
13130 blackTimeRemaining -= lastTickLength;
\r
13131 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13136 /* Start clock of player on move. Time may have been reset, so
\r
13137 if clock is already running, stop and restart it. */
\r
13141 (void) StopClockTimer(); /* in case it was running already */
\r
13142 DisplayBothClocks();
\r
13143 if (CheckFlags()) return;
\r
13145 if (!appData.clockMode) return;
\r
13146 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13148 GetTimeMark(&tickStartTM);
\r
13149 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13150 whiteTimeRemaining : blackTimeRemaining);
\r
13152 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13153 whiteNPS = blackNPS = -1;
\r
13154 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13155 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13156 whiteNPS = first.nps;
\r
13157 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13158 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13159 blackNPS = first.nps;
\r
13160 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13161 whiteNPS = second.nps;
\r
13162 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13163 blackNPS = second.nps;
\r
13164 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13166 StartClockTimer(intendedTickLength);
\r
13173 long second, minute, hour, day;
\r
13175 static char buf[32];
\r
13177 if (ms > 0 && ms <= 9900) {
\r
13178 /* convert milliseconds to tenths, rounding up */
\r
13179 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13181 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13185 /* convert milliseconds to seconds, rounding up */
\r
13186 /* use floating point to avoid strangeness of integer division
\r
13187 with negative dividends on many machines */
\r
13188 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13190 if (second < 0) {
\r
13192 second = -second;
\r
13195 day = second / (60 * 60 * 24);
\r
13196 second = second % (60 * 60 * 24);
\r
13197 hour = second / (60 * 60);
\r
13198 second = second % (60 * 60);
\r
13199 minute = second / 60;
\r
13200 second = second % 60;
\r
13203 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13204 sign, day, hour, minute, second);
\r
13205 else if (hour > 0)
\r
13206 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13208 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13215 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13218 StrStr(string, match)
\r
13219 char *string, *match;
\r
13223 length = strlen(match);
\r
13225 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13226 if (!strncmp(match, string, length))
\r
13233 StrCaseStr(string, match)
\r
13234 char *string, *match;
\r
13236 int i, j, length;
\r
13238 length = strlen(match);
\r
13240 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13241 for (j = 0; j < length; j++) {
\r
13242 if (ToLower(match[j]) != ToLower(string[j]))
\r
13245 if (j == length) return string;
\r
13251 #ifndef _amigados
\r
13253 StrCaseCmp(s1, s2)
\r
13259 c1 = ToLower(*s1++);
\r
13260 c2 = ToLower(*s2++);
\r
13261 if (c1 > c2) return 1;
\r
13262 if (c1 < c2) return -1;
\r
13263 if (c1 == NULLCHAR) return 0;
\r
13272 return isupper(c) ? tolower(c) : c;
\r
13280 return islower(c) ? toupper(c) : c;
\r
13282 #endif /* !_amigados */
\r
13290 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13297 StrSavePtr(s, savePtr)
\r
13298 char *s, **savePtr;
\r
13303 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13304 strcpy(*savePtr, s);
\r
13306 return(*savePtr);
\r
13314 char buf[MSG_SIZ];
\r
13316 clock = time((time_t *)NULL);
\r
13317 tm = localtime(&clock);
\r
13318 sprintf(buf, "%04d.%02d.%02d",
\r
13319 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13320 return StrSave(buf);
\r
13325 PositionToFEN(move, useFEN960)
\r
13329 int i, j, fromX, fromY, toX, toY;
\r
13334 ChessSquare piece;
\r
13336 whiteToPlay = (gameMode == EditPosition) ?
\r
13337 !blackPlaysFirst : (move % 2 == 0);
\r
13340 /* Piece placement data */
\r
13341 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13343 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13344 if (boards[move][i][j] == EmptySquare) {
\r
13346 } else { ChessSquare piece = boards[move][i][j];
\r
13347 if (emptycount > 0) {
\r
13348 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13349 *p++ = '0' + emptycount;
\r
13350 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13353 if(PieceToChar(piece) == '+') {
\r
13354 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13356 piece = (ChessSquare)(DEMOTED piece);
\r
13358 *p++ = PieceToChar(piece);
\r
13359 if(p[-1] == '~') {
\r
13360 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13361 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13366 if (emptycount > 0) {
\r
13367 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13368 *p++ = '0' + emptycount;
\r
13369 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13376 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13377 if( gameInfo.holdingsWidth ) {
\r
13378 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13380 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13381 piece = boards[move][i][BOARD_WIDTH-1];
\r
13382 if( piece != EmptySquare )
\r
13383 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13384 *p++ = PieceToChar(piece);
\r
13386 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13387 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13388 if( piece != EmptySquare )
\r
13389 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13390 *p++ = PieceToChar(piece);
\r
13393 if( q == p ) *p++ = '-';
\r
13398 /* Active color */
\r
13399 *p++ = whiteToPlay ? 'w' : 'b';
\r
13402 if(nrCastlingRights) {
\r
13404 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13405 /* [HGM] write directly from rights */
\r
13406 if(castlingRights[move][2] >= 0 &&
\r
13407 castlingRights[move][0] >= 0 )
\r
13408 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13409 if(castlingRights[move][2] >= 0 &&
\r
13410 castlingRights[move][1] >= 0 )
\r
13411 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13412 if(castlingRights[move][5] >= 0 &&
\r
13413 castlingRights[move][3] >= 0 )
\r
13414 *p++ = castlingRights[move][3] + AAA;
\r
13415 if(castlingRights[move][5] >= 0 &&
\r
13416 castlingRights[move][4] >= 0 )
\r
13417 *p++ = castlingRights[move][4] + AAA;
\r
13420 /* [HGM] write true castling rights */
\r
13421 if( nrCastlingRights == 6 ) {
\r
13422 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13423 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13424 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13425 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13426 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13427 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13428 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13429 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13432 if (q == p) *p++ = '-'; /* No castling rights */
\r
13436 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13437 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13438 /* En passant target square */
\r
13439 if (move > backwardMostMove) {
\r
13440 fromX = moveList[move - 1][0] - AAA;
\r
13441 fromY = moveList[move - 1][1] - ONE;
\r
13442 toX = moveList[move - 1][2] - AAA;
\r
13443 toY = moveList[move - 1][3] - ONE;
\r
13444 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13445 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13446 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13448 /* 2-square pawn move just happened */
\r
13449 *p++ = toX + AAA;
\r
13450 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13460 /* [HGM] find reversible plies */
\r
13461 { int i = 0, j=move;
\r
13463 if (appData.debugMode) { int k;
\r
13464 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13465 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13466 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13470 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13471 if( j == backwardMostMove ) i += initialRulePlies;
\r
13472 sprintf(p, "%d ", i);
\r
13473 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13475 /* Fullmove number */
\r
13476 sprintf(p, "%d", (move / 2) + 1);
\r
13478 return StrSave(buf);
\r
13482 ParseFEN(board, blackPlaysFirst, fen)
\r
13484 int *blackPlaysFirst;
\r
13490 ChessSquare piece;
\r
13494 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13495 if(gameInfo.holdingsWidth) {
\r
13496 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13497 board[i][0] = EmptySquare; /* black holdings */
\r
13498 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13499 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13500 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13504 /* Piece placement data */
\r
13505 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13508 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13509 if (*p == '/') p++;
\r
13510 emptycount = gameInfo.boardWidth - j;
\r
13511 while (emptycount--)
\r
13512 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13514 #if(BOARD_SIZE >= 10)
\r
13515 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13516 p++; emptycount=10;
\r
13517 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13518 while (emptycount--)
\r
13519 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13521 } else if (isdigit(*p)) {
\r
13522 emptycount = *p++ - '0';
\r
13523 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13524 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13525 while (emptycount--)
\r
13526 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13527 } else if (*p == '+' || isalpha(*p)) {
\r
13528 if (j >= gameInfo.boardWidth) return FALSE;
\r
13530 piece = CharToPiece(*++p);
\r
13531 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13532 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13533 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13534 } else piece = CharToPiece(*p++);
\r
13536 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13537 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13538 piece = (ChessSquare) (PROMOTED piece);
\r
13539 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13542 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13548 while (*p == '/' || *p == ' ') p++;
\r
13550 /* [HGM] look for Crazyhouse holdings here */
\r
13551 while(*p==' ') p++;
\r
13552 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13553 if(*p == '[') p++;
\r
13554 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13555 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13556 /* if we would allow FEN reading to set board size, we would */
\r
13557 /* have to add holdings and shift the board read so far here */
\r
13558 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13560 if((int) piece >= (int) BlackPawn ) {
\r
13561 i = (int)piece - (int)BlackPawn;
\r
13562 i = PieceToNumber((ChessSquare)i);
\r
13563 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13564 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13565 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13567 i = (int)piece - (int)WhitePawn;
\r
13568 i = PieceToNumber((ChessSquare)i);
\r
13569 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13570 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13571 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13575 if(*p == ']') *p++;
\r
13578 while(*p == ' ') p++;
\r
13580 /* Active color */
\r
13583 *blackPlaysFirst = FALSE;
\r
13586 *blackPlaysFirst = TRUE;
\r
13592 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13593 /* return the extra info in global variiables */
\r
13595 /* set defaults in case FEN is incomplete */
\r
13596 FENepStatus = EP_UNKNOWN;
\r
13597 for(i=0; i<nrCastlingRights; i++ ) {
\r
13598 FENcastlingRights[i] =
\r
13599 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13600 } /* assume possible unless obviously impossible */
\r
13601 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13602 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13603 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13604 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13605 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13606 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13607 FENrulePlies = 0;
\r
13609 while(*p==' ') p++;
\r
13610 if(nrCastlingRights) {
\r
13611 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13612 /* castling indicator present, so default becomes no castlings */
\r
13613 for(i=0; i<nrCastlingRights; i++ ) {
\r
13614 FENcastlingRights[i] = -1;
\r
13617 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13618 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13619 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13620 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13621 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13623 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13624 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13625 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13629 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13630 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13631 FENcastlingRights[2] = whiteKingFile;
\r
13634 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13635 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13636 FENcastlingRights[2] = whiteKingFile;
\r
13639 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13640 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13641 FENcastlingRights[5] = blackKingFile;
\r
13644 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13645 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13646 FENcastlingRights[5] = blackKingFile;
\r
13649 default: /* FRC castlings */
\r
13650 if(c >= 'a') { /* black rights */
\r
13651 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13652 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13653 if(i == BOARD_RGHT) break;
\r
13654 FENcastlingRights[5] = i;
\r
13656 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13657 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13659 FENcastlingRights[3] = c;
\r
13661 FENcastlingRights[4] = c;
\r
13662 } else { /* white rights */
\r
13663 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13664 if(board[0][i] == WhiteKing) break;
\r
13665 if(i == BOARD_RGHT) break;
\r
13666 FENcastlingRights[2] = i;
\r
13667 c -= AAA - 'a' + 'A';
\r
13668 if(board[0][c] >= WhiteKing) break;
\r
13670 FENcastlingRights[0] = c;
\r
13672 FENcastlingRights[1] = c;
\r
13676 if (appData.debugMode) {
\r
13677 fprintf(debugFP, "FEN castling rights:");
\r
13678 for(i=0; i<nrCastlingRights; i++)
\r
13679 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13680 fprintf(debugFP, "\n");
\r
13683 while(*p==' ') p++;
\r
13686 /* read e.p. field in games that know e.p. capture */
\r
13687 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13688 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13690 p++; FENepStatus = EP_NONE;
\r
13692 char c = *p++ - AAA;
\r
13694 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13695 if(*p >= '0' && *p <='9') *p++;
\r
13701 if(sscanf(p, "%d", &i) == 1) {
\r
13702 FENrulePlies = i; /* 50-move ply counter */
\r
13703 /* (The move number is still ignored) */
\r
13710 EditPositionPasteFEN(char *fen)
\r
13712 if (fen != NULL) {
\r
13713 Board initial_position;
\r
13715 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13716 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13719 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13720 EditPositionEvent();
\r
13721 blackPlaysFirst = savedBlackPlaysFirst;
\r
13722 CopyBoard(boards[0], initial_position);
\r
13723 /* [HGM] copy FEN attributes as well */
\r
13725 initialRulePlies = FENrulePlies;
\r
13726 epStatus[0] = FENepStatus;
\r
13727 for( i=0; i<nrCastlingRights; i++ )
\r
13728 castlingRights[0][i] = FENcastlingRights[i];
\r
13730 EditPositionDone();
\r
13731 DisplayBothClocks();
\r
13732 DrawPosition(FALSE, boards[currentMove]);
\r