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 )
\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
124 /* A point in time */
\r
126 long sec; /* Assuming this is >= 32 bits */
\r
127 int ms; /* Assuming this is >= 16 bits */
\r
130 /* Search stats from chessprogram */
\r
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
\r
133 int depth; /* Current search depth */
\r
134 int nr_moves; /* Total nr of root moves */
\r
135 int moves_left; /* Moves remaining to be searched */
\r
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
\r
137 unsigned long nodes; /* # of nodes searched */
\r
138 int time; /* Search time (centiseconds) */
\r
139 int score; /* Score (centipawns) */
\r
140 int got_only_move; /* If last msg was "(only move)" */
\r
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
\r
142 int ok_to_send; /* handshaking between send & recv */
\r
143 int line_is_book; /* 1 if movelist is book moves */
\r
144 int seen_stat; /* 1 if we've seen the stat01: line */
\r
145 } ChessProgramStats;
\r
147 int establish P((void));
\r
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
149 char *buf, int count, int error));
\r
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
151 char *buf, int count, int error));
\r
152 void SendToICS P((char *s));
\r
153 void SendToICSDelayed P((char *s, long msdelay));
\r
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
155 int toX, int toY));
\r
156 void InitPosition P((int redraw));
\r
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
158 int AutoPlayOneMove P((void));
\r
159 int LoadGameOneMove P((ChessMove readAhead));
\r
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
161 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
162 int SavePositionToFile P((char *filename));
\r
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
168 /*char*/int promoChar));
\r
169 void BackwardInner P((int target));
\r
170 void ForwardInner P((int target));
\r
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
172 void EditPositionDone P((void));
\r
173 void PrintOpponents P((FILE *fp));
\r
174 void PrintPosition P((FILE *fp, int move));
\r
175 void StartChessProgram P((ChessProgramState *cps));
\r
176 void SendToProgram P((char *message, ChessProgramState *cps));
\r
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
179 char *buf, int count, int error));
\r
180 void SendTimeControl P((ChessProgramState *cps,
\r
181 int mps, long tc, int inc, int sd, int st));
\r
182 char *TimeControlTagValue P((void));
\r
183 void Attention P((ChessProgramState *cps));
\r
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
185 void ResurrectChessProgram P((void));
\r
186 void DisplayComment P((int moveNumber, char *text));
\r
187 void DisplayMove P((int moveNumber));
\r
188 void DisplayAnalysis P((void));
\r
190 void ParseGameHistory P((char *game));
\r
191 void ParseBoard12 P((char *string));
\r
192 void StartClocks P((void));
\r
193 void SwitchClocks P((void));
\r
194 void StopClocks P((void));
\r
195 void ResetClocks P((void));
\r
196 char *PGNDate P((void));
\r
197 void SetGameInfo P((void));
\r
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
199 int RegisterMove P((void));
\r
200 void MakeRegisteredMove P((void));
\r
201 void TruncateGame P((void));
\r
202 int looking_at P((char *, int *, char *));
\r
203 void CopyPlayerNameIntoFileName P((char **, char *));
\r
204 char *SavePart P((char *));
\r
205 int SaveGameOldStyle P((FILE *));
\r
206 int SaveGamePGN P((FILE *));
\r
207 void GetTimeMark P((TimeMark *));
\r
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
209 int CheckFlags P((void));
\r
210 long NextTickLength P((long));
\r
211 void CheckTimeControl P((void));
\r
212 void show_bytes P((FILE *, char *, int));
\r
213 int string_to_rating P((char *str));
\r
214 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
215 void InitBackEnd3 P((void));
\r
216 void FeatureDone P((ChessProgramState* cps, int val));
\r
217 void InitChessProgram P((ChessProgramState *cps));
\r
219 void GetInfoFromComment( int, char * );
\r
221 extern int tinyLayout, smallLayout;
\r
222 static ChessProgramStats programStats;
\r
224 /* States for ics_getting_history */
\r
226 #define H_REQUESTED 1
\r
227 #define H_GOT_REQ_HEADER 2
\r
228 #define H_GOT_UNREQ_HEADER 3
\r
229 #define H_GETTING_MOVES 4
\r
230 #define H_GOT_UNWANTED_HEADER 5
\r
232 /* whosays values for GameEnds */
\r
234 #define GE_ENGINE 1
\r
235 #define GE_PLAYER 2
\r
237 #define GE_XBOARD 4
\r
238 #define GE_ENGINE1 5
\r
239 #define GE_ENGINE2 6
\r
241 /* Maximum number of games in a cmail message */
\r
242 #define CMAIL_MAX_GAMES 20
\r
244 /* Different types of move when calling RegisterMove */
\r
245 #define CMAIL_MOVE 0
\r
246 #define CMAIL_RESIGN 1
\r
247 #define CMAIL_DRAW 2
\r
248 #define CMAIL_ACCEPT 3
\r
250 /* Different types of result to remember for each game */
\r
251 #define CMAIL_NOT_RESULT 0
\r
252 #define CMAIL_OLD_RESULT 1
\r
253 #define CMAIL_NEW_RESULT 2
\r
255 /* Telnet protocol constants */
\r
256 #define TN_WILL 0373
\r
257 #define TN_WONT 0374
\r
259 #define TN_DONT 0376
\r
260 #define TN_IAC 0377
\r
261 #define TN_ECHO 0001
\r
262 #define TN_SGA 0003
\r
266 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
268 assert( dst != NULL );
\r
269 assert( src != NULL );
\r
270 assert( count > 0 );
\r
272 strncpy( dst, src, count );
\r
273 dst[ count-1 ] = '\0';
\r
277 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
281 assert( dst != NULL );
\r
282 assert( src != NULL );
\r
283 assert( count > 0 );
\r
285 dst_len = strlen(dst);
\r
287 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
289 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
294 /* Fake up flags for now, as we aren't keeping track of castling
\r
295 availability yet */
\r
299 int flags = F_ALL_CASTLE_OK;
\r
300 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
301 switch (gameInfo.variant) {
\r
302 case VariantSuicide:
\r
303 case VariantGiveaway:
\r
304 flags |= F_IGNORE_CHECK;
\r
305 flags &= ~F_ALL_CASTLE_OK;
\r
307 case VariantAtomic:
\r
308 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
310 case VariantKriegspiel:
\r
311 flags |= F_KRIEGSPIEL_CAPTURE;
\r
313 case VariantNoCastle:
\r
314 flags &= ~F_ALL_CASTLE_OK;
\r
322 FILE *gameFileFP, *debugFP;
\r
325 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
326 into a fixed-size buffer. Because of this, we must be prepared to
\r
327 receive strings as long as the size of the input buffer, which is currently
\r
328 set to 4K for Windows and 8K for the rest.
\r
329 So, we must either allocate sufficiently large buffers here, or
\r
330 reduce the size of the input buffer in the input reading part.
\r
333 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
334 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
335 char thinkOutput1[MSG_SIZ*10];
\r
337 ChessProgramState first, second;
\r
339 /* premove variables */
\r
340 int premoveToX = 0;
\r
341 int premoveToY = 0;
\r
342 int premoveFromX = 0;
\r
343 int premoveFromY = 0;
\r
344 int premovePromoChar = 0;
\r
345 int gotPremove = 0;
\r
346 Boolean alarmSounded;
\r
347 /* end premove variables */
\r
349 #define ICS_GENERIC 0
\r
352 #define ICS_CHESSNET 3 /* not really supported */
\r
353 int ics_type = ICS_GENERIC;
\r
354 char *ics_prefix = "$";
\r
356 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
357 int pauseExamForwardMostMove = 0;
\r
358 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
359 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
360 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
361 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
362 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
363 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
364 int whiteFlag = FALSE, blackFlag = FALSE;
\r
365 int userOfferedDraw = FALSE;
\r
366 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
367 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
368 int cmailMoveType[CMAIL_MAX_GAMES];
\r
369 long ics_clock_paused = 0;
\r
370 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
371 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
372 GameMode gameMode = BeginningOfGame;
\r
373 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
374 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
375 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
376 int hiddenThinkOutputState = 0; /* [AS] */
\r
377 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
378 int adjudicateLossPlies = 6;
\r
379 char white_holding[64], black_holding[64];
\r
380 TimeMark lastNodeCountTime;
\r
381 long lastNodeCount=0;
\r
382 int have_sent_ICS_logon = 0;
\r
383 int movesPerSession;
\r
384 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
385 long timeControl_2; /* [AS] Allow separate time controls */
\r
386 long timeRemaining[2][MAX_MOVES];
\r
388 TimeMark programStartTime;
\r
389 char ics_handle[MSG_SIZ];
\r
390 int have_set_title = 0;
\r
392 /* animateTraining preserves the state of appData.animate
\r
393 * when Training mode is activated. This allows the
\r
394 * response to be animated when appData.animate == TRUE and
\r
395 * appData.animateDragging == TRUE.
\r
397 Boolean animateTraining;
\r
403 Board boards[MAX_MOVES];
\r
404 /* [HGM] Following 7 needed for accurate legality tests: */
\r
405 char epStatus[MAX_MOVES];
\r
406 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
407 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
408 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE];
\r
409 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
410 int initialRulePlies, FENrulePlies;
\r
413 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
414 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
415 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
416 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
417 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
420 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
421 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
422 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
423 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
424 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
428 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
429 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
430 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
431 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
432 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
435 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
436 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
437 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
438 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
439 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
442 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
443 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
444 WhiteKing, WhiteAlfil, WhiteKnight, WhiteRook },
\r
445 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
446 BlackKing, BlackAlfil, BlackKnight, BlackRook }
\r
450 #if (BOARD_SIZE>=10)
\r
451 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
452 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
453 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
454 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
455 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
458 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
459 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
460 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
461 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
462 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
465 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
466 { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen,
\r
467 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
468 { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen,
\r
469 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
473 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
475 WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook },
\r
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
477 BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook }
\r
480 #define GothicArray CapablancaArray
\r
483 #else // !(BOARD_SIZE>=10)
\r
484 #define XiangqiPosition FIDEArray
\r
485 #define CapablancaArray FIDEArray
\r
486 #define GothicArray FIDEArray
\r
487 #endif // !(BOARD_SIZE>=10)
\r
489 #if (BOARD_SIZE>=12)
\r
490 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
491 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
492 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
493 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
494 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
496 #else // !(BOARD_SIZE>=12)
\r
497 #define CourierArray CapablancaArray
\r
498 #endif // !(BOARD_SIZE>=12)
\r
502 Board initialPosition;
\r
505 /* Convert str to a rating. Checks for special cases of "----",
\r
507 "++++", etc. Also strips ()'s */
\r
509 string_to_rating(str)
\r
512 while(*str && !isdigit(*str)) ++str;
\r
514 return 0; /* One of the special "no rating" cases */
\r
520 ClearProgramStats()
\r
522 /* Init programStats */
\r
523 programStats.movelist[0] = 0;
\r
524 programStats.depth = 0;
\r
525 programStats.nr_moves = 0;
\r
526 programStats.moves_left = 0;
\r
527 programStats.nodes = 0;
\r
528 programStats.time = 100;
\r
529 programStats.score = 0;
\r
530 programStats.got_only_move = 0;
\r
531 programStats.got_fail = 0;
\r
532 programStats.line_is_book = 0;
\r
538 int matched, min, sec;
\r
540 GetTimeMark(&programStartTime);
\r
542 ClearProgramStats();
\r
543 programStats.ok_to_send = 1;
\r
544 programStats.seen_stat = 0;
\r
547 * Initialize game list
\r
549 ListNew(&gameList);
\r
553 * Internet chess server status
\r
555 if (appData.icsActive) {
\r
556 appData.matchMode = FALSE;
\r
557 appData.matchGames = 0;
\r
559 appData.noChessProgram = !appData.zippyPlay;
\r
561 appData.zippyPlay = FALSE;
\r
562 appData.zippyTalk = FALSE;
\r
563 appData.noChessProgram = TRUE;
\r
565 if (*appData.icsHelper != NULLCHAR) {
\r
566 appData.useTelnet = TRUE;
\r
567 appData.telnetProgram = appData.icsHelper;
\r
570 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
573 /* [AS] Initialize pv info list [HGM] and game state */
\r
577 for( i=0; i<MAX_MOVES; i++ ) {
\r
578 pvInfoList[i].depth = -1;
\r
579 epStatus[i]=EP_NONE;
\r
580 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
585 * Parse timeControl resource
\r
587 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
588 appData.movesPerSession)) {
\r
590 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
591 DisplayFatalError(buf, 0, 2);
\r
595 * Parse searchTime resource
\r
597 if (*appData.searchTime != NULLCHAR) {
\r
598 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
599 if (matched == 1) {
\r
600 searchTime = min * 60;
\r
601 } else if (matched == 2) {
\r
602 searchTime = min * 60 + sec;
\r
605 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
606 DisplayFatalError(buf, 0, 2);
\r
610 /* [AS] Adjudication threshold */
\r
611 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
613 first.which = "first";
\r
614 second.which = "second";
\r
615 first.maybeThinking = second.maybeThinking = FALSE;
\r
616 first.pr = second.pr = NoProc;
\r
617 first.isr = second.isr = NULL;
\r
618 first.sendTime = second.sendTime = 2;
\r
619 first.sendDrawOffers = 1;
\r
620 if (appData.firstPlaysBlack) {
\r
621 first.twoMachinesColor = "black\n";
\r
622 second.twoMachinesColor = "white\n";
\r
624 first.twoMachinesColor = "white\n";
\r
625 second.twoMachinesColor = "black\n";
\r
627 first.program = appData.firstChessProgram;
\r
628 second.program = appData.secondChessProgram;
\r
629 first.host = appData.firstHost;
\r
630 second.host = appData.secondHost;
\r
631 first.dir = appData.firstDirectory;
\r
632 second.dir = appData.secondDirectory;
\r
633 first.other = &second;
\r
634 second.other = &first;
\r
635 first.initString = appData.initString;
\r
636 second.initString = appData.secondInitString;
\r
637 first.computerString = appData.firstComputerString;
\r
638 second.computerString = appData.secondComputerString;
\r
639 first.useSigint = second.useSigint = TRUE;
\r
640 first.useSigterm = second.useSigterm = TRUE;
\r
641 first.reuse = appData.reuseFirst;
\r
642 second.reuse = appData.reuseSecond;
\r
643 first.useSetboard = second.useSetboard = FALSE;
\r
644 first.useSAN = second.useSAN = FALSE;
\r
645 first.usePing = second.usePing = FALSE;
\r
646 first.lastPing = second.lastPing = 0;
\r
647 first.lastPong = second.lastPong = 0;
\r
648 first.usePlayother = second.usePlayother = FALSE;
\r
649 first.useColors = second.useColors = TRUE;
\r
650 first.useUsermove = second.useUsermove = FALSE;
\r
651 first.sendICS = second.sendICS = FALSE;
\r
652 first.sendName = second.sendName = appData.icsActive;
\r
653 first.sdKludge = second.sdKludge = FALSE;
\r
654 first.stKludge = second.stKludge = FALSE;
\r
655 TidyProgramName(first.program, first.host, first.tidy);
\r
656 TidyProgramName(second.program, second.host, second.tidy);
\r
657 first.matchWins = second.matchWins = 0;
\r
658 strcpy(first.variants, appData.variant);
\r
659 strcpy(second.variants, appData.variant);
\r
660 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
661 first.analyzing = second.analyzing = FALSE;
\r
662 first.initDone = second.initDone = FALSE;
\r
664 /* New features added by Tord: */
\r
665 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
666 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
667 /* End of new features added by Tord. */
\r
669 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
670 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
671 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
672 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
673 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
674 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
676 if (appData.firstProtocolVersion > PROTOVER ||
\r
677 appData.firstProtocolVersion < 1) {
\r
679 sprintf(buf, "protocol version %d not supported",
\r
680 appData.firstProtocolVersion);
\r
681 DisplayFatalError(buf, 0, 2);
\r
683 first.protocolVersion = appData.firstProtocolVersion;
\r
686 if (appData.secondProtocolVersion > PROTOVER ||
\r
687 appData.secondProtocolVersion < 1) {
\r
689 sprintf(buf, "protocol version %d not supported",
\r
690 appData.secondProtocolVersion);
\r
691 DisplayFatalError(buf, 0, 2);
\r
693 second.protocolVersion = appData.secondProtocolVersion;
\r
696 if (appData.icsActive) {
\r
697 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
698 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
699 appData.clockMode = FALSE;
\r
700 first.sendTime = second.sendTime = 0;
\r
704 /* Override some settings from environment variables, for backward
\r
705 compatibility. Unfortunately it's not feasible to have the env
\r
706 vars just set defaults, at least in xboard. Ugh.
\r
708 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
713 if (appData.noChessProgram) {
\r
714 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
715 + strlen(PATCHLEVEL));
\r
716 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
720 while (*q != ' ' && *q != NULLCHAR) q++;
\r
722 while (p > first.program && *(p-1) != '/') p--;
\r
723 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
724 + strlen(PATCHLEVEL) + (q - p));
\r
725 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
726 strncat(programVersion, p, q - p);
\r
729 if (!appData.icsActive) {
\r
731 /* Check for variants that are supported only in ICS mode,
\r
732 or not at all. Some that are accepted here nevertheless
\r
733 have bugs; see comments below.
\r
735 VariantClass variant = StringToVariant(appData.variant);
\r
737 case VariantBughouse: /* need four players and two boards */
\r
738 case VariantKriegspiel: /* need to hide pieces and move details */
\r
739 /* case VariantFischeRandom: (Fabien: moved below) */
\r
740 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
741 DisplayFatalError(buf, 0, 2);
\r
744 case VariantUnknown:
\r
745 case VariantLoadable:
\r
755 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
756 DisplayFatalError(buf, 0, 2);
\r
759 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
760 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
761 case VariantGothic: /* [HGM] should work */
\r
762 case VariantCapablanca: /* [HGM] should work */
\r
763 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
764 case VariantShogi: /* [HGM] drops not tested for legality */
\r
765 case VariantShowgi: /* [HGM] not a valid variant */
\r
766 case VariantKnightmate: /* [HGM] should work */
\r
767 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
768 offboard interposition not understood */
\r
769 case VariantNormal: /* definitely works! */
\r
770 case VariantWildCastle: /* pieces not automatically shuffled */
\r
771 case VariantNoCastle: /* pieces not automatically shuffled */
\r
772 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
\r
773 case VariantLosers: /* should work except for win condition,
\r
774 and doesn't know captures are mandatory */
\r
775 case VariantSuicide: /* should work except for win condition,
\r
776 and doesn't know captures are mandatory */
\r
777 case VariantGiveaway: /* should work except for win condition,
\r
778 and doesn't know captures are mandatory */
\r
779 case VariantTwoKings: /* should work */
\r
780 case VariantAtomic: /* should work except for win condition */
\r
781 case Variant3Check: /* should work except for win condition */
\r
782 case VariantShatranj: /* might work if TestLegality is off */
\r
788 int NextIntegerFromString( char ** str, long * value )
\r
793 while( *s == ' ' || *s == '\t' ) {
\r
799 if( *s >= '0' && *s <= '9' ) {
\r
800 while( *s >= '0' && *s <= '9' ) {
\r
801 *value = *value * 10 + (*s - '0');
\r
813 int NextTimeControlFromString( char ** str, long * value )
\r
816 int result = NextIntegerFromString( str, &temp );
\r
818 if( result == 0 ) {
\r
819 *value = temp * 60; /* Minutes */
\r
820 if( **str == ':' ) {
\r
822 result = NextIntegerFromString( str, &temp );
\r
823 *value += temp; /* Seconds */
\r
830 int GetTimeControlForWhite()
\r
832 int result = timeControl;
\r
837 int GetTimeControlForBlack()
\r
839 int result = timeControl;
\r
841 if( timeControl_2 > 0 ) {
\r
842 result = timeControl_2;
\r
849 ParseTimeControl(tc, ti, mps)
\r
855 int matched, min, sec;
\r
857 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
858 if (matched == 1) {
\r
859 timeControl = min * 60 * 1000;
\r
860 } else if (matched == 2) {
\r
861 timeControl = (min * 60 + sec) * 1000;
\r
869 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
874 /* Parse second time control */
\r
877 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
885 timeControl_2 = tc2 * 1000;
\r
895 timeControl = tc1 * 1000;
\r
899 timeIncrement = ti * 1000; /* convert to ms */
\r
900 movesPerSession = 0;
\r
903 movesPerSession = mps;
\r
911 if (appData.debugMode) {
\r
912 fprintf(debugFP, "%s\n", programVersion);
\r
915 if (appData.matchGames > 0) {
\r
916 appData.matchMode = TRUE;
\r
917 } else if (appData.matchMode) {
\r
918 appData.matchGames = 1;
\r
920 Reset(TRUE, FALSE);
\r
921 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
924 /* kludge: allow timeout for initial "feature" commands */
\r
926 DisplayMessage("", "Starting chess program");
\r
927 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
932 InitBackEnd3 P((void))
\r
934 GameMode initialMode;
\r
938 InitChessProgram(&first);
\r
940 if (appData.icsActive) {
\r
943 if (*appData.icsCommPort != NULLCHAR) {
\r
944 sprintf(buf, "Could not open comm port %s",
\r
945 appData.icsCommPort);
\r
947 sprintf(buf, "Could not connect to host %s, port %s",
\r
948 appData.icsHost, appData.icsPort);
\r
950 DisplayFatalError(buf, err, 1);
\r
955 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
957 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
958 } else if (appData.noChessProgram) {
\r
964 if (*appData.cmailGameName != NULLCHAR) {
\r
966 OpenLoopback(&cmailPR);
\r
968 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
972 DisplayMessage("", "");
\r
973 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
974 initialMode = BeginningOfGame;
\r
975 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
976 initialMode = TwoMachinesPlay;
\r
977 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
978 initialMode = AnalyzeFile;
\r
979 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
980 initialMode = AnalyzeMode;
\r
981 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
982 initialMode = MachinePlaysWhite;
\r
983 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
984 initialMode = MachinePlaysBlack;
\r
985 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
986 initialMode = EditGame;
\r
987 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
988 initialMode = EditPosition;
\r
989 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
990 initialMode = Training;
\r
992 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
993 DisplayFatalError(buf, 0, 2);
\r
997 if (appData.matchMode) {
\r
998 /* Set up machine vs. machine match */
\r
999 if (appData.noChessProgram) {
\r
1000 DisplayFatalError("Can't have a match with no chess programs",
\r
1006 if (*appData.loadGameFile != NULLCHAR) {
\r
1007 if (!LoadGameFromFile(appData.loadGameFile,
\r
1008 appData.loadGameIndex,
\r
1009 appData.loadGameFile, FALSE)) {
\r
1010 DisplayFatalError("Bad game file", 0, 1);
\r
1013 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1014 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1015 appData.loadPositionIndex,
\r
1016 appData.loadPositionFile)) {
\r
1017 DisplayFatalError("Bad position file", 0, 1);
\r
1021 TwoMachinesEvent();
\r
1022 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1023 /* Set up cmail mode */
\r
1024 ReloadCmailMsgEvent(TRUE);
\r
1026 /* Set up other modes */
\r
1027 if (initialMode == AnalyzeFile) {
\r
1028 if (*appData.loadGameFile == NULLCHAR) {
\r
1029 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1033 if (*appData.loadGameFile != NULLCHAR) {
\r
1034 (void) LoadGameFromFile(appData.loadGameFile,
\r
1035 appData.loadGameIndex,
\r
1036 appData.loadGameFile, TRUE);
\r
1037 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1038 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1039 appData.loadPositionIndex,
\r
1040 appData.loadPositionFile);
\r
1042 if (initialMode == AnalyzeMode) {
\r
1043 if (appData.noChessProgram) {
\r
1044 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1047 if (appData.icsActive) {
\r
1048 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1051 AnalyzeModeEvent();
\r
1052 } else if (initialMode == AnalyzeFile) {
\r
1053 ShowThinkingEvent(TRUE);
\r
1054 AnalyzeFileEvent();
\r
1055 AnalysisPeriodicEvent(1);
\r
1056 } else if (initialMode == MachinePlaysWhite) {
\r
1057 if (appData.noChessProgram) {
\r
1058 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1062 if (appData.icsActive) {
\r
1063 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1067 MachineWhiteEvent();
\r
1068 } else if (initialMode == MachinePlaysBlack) {
\r
1069 if (appData.noChessProgram) {
\r
1070 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1074 if (appData.icsActive) {
\r
1075 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1079 MachineBlackEvent();
\r
1080 } else if (initialMode == TwoMachinesPlay) {
\r
1081 if (appData.noChessProgram) {
\r
1082 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1086 if (appData.icsActive) {
\r
1087 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1091 TwoMachinesEvent();
\r
1092 } else if (initialMode == EditGame) {
\r
1094 } else if (initialMode == EditPosition) {
\r
1095 EditPositionEvent();
\r
1096 } else if (initialMode == Training) {
\r
1097 if (*appData.loadGameFile == NULLCHAR) {
\r
1098 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1107 * Establish will establish a contact to a remote host.port.
\r
1108 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1109 * used to talk to the host.
\r
1110 * Returns 0 if okay, error code if not.
\r
1115 char buf[MSG_SIZ];
\r
1117 if (*appData.icsCommPort != NULLCHAR) {
\r
1118 /* Talk to the host through a serial comm port */
\r
1119 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1121 } else if (*appData.gateway != NULLCHAR) {
\r
1122 if (*appData.remoteShell == NULLCHAR) {
\r
1123 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1124 sprintf(buf, "%s %s %s",
\r
1125 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1126 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1129 /* Use the rsh program to run telnet program on a gateway host */
\r
1130 if (*appData.remoteUser == NULLCHAR) {
\r
1131 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1132 appData.gateway, appData.telnetProgram,
\r
1133 appData.icsHost, appData.icsPort);
\r
1135 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1136 appData.remoteShell, appData.gateway,
\r
1137 appData.remoteUser, appData.telnetProgram,
\r
1138 appData.icsHost, appData.icsPort);
\r
1140 return StartChildProcess(buf, "", &icsPR);
\r
1143 } else if (appData.useTelnet) {
\r
1144 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1147 /* TCP socket interface differs somewhat between
\r
1148 Unix and NT; handle details in the front end.
\r
1150 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1155 show_bytes(fp, buf, count)
\r
1161 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1162 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1171 /* Returns an errno value */
\r
1173 OutputMaybeTelnet(pr, message, count, outError)
\r
1179 char buf[8192], *p, *q, *buflim;
\r
1180 int left, newcount, outcount;
\r
1182 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1183 *appData.gateway != NULLCHAR) {
\r
1184 if (appData.debugMode) {
\r
1185 fprintf(debugFP, ">ICS: ");
\r
1186 show_bytes(debugFP, message, count);
\r
1187 fprintf(debugFP, "\n");
\r
1189 return OutputToProcess(pr, message, count, outError);
\r
1192 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1198 if (q >= buflim) {
\r
1199 if (appData.debugMode) {
\r
1200 fprintf(debugFP, ">ICS: ");
\r
1201 show_bytes(debugFP, buf, newcount);
\r
1202 fprintf(debugFP, "\n");
\r
1204 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1205 if (outcount < newcount) return -1; /* to be sure */
\r
1212 } else if (((unsigned char) *p) == TN_IAC) {
\r
1213 *q++ = (char) TN_IAC;
\r
1220 if (appData.debugMode) {
\r
1221 fprintf(debugFP, ">ICS: ");
\r
1222 show_bytes(debugFP, buf, newcount);
\r
1223 fprintf(debugFP, "\n");
\r
1225 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1226 if (outcount < newcount) return -1; /* to be sure */
\r
1231 read_from_player(isr, closure, message, count, error)
\r
1232 InputSourceRef isr;
\r
1238 int outError, outCount;
\r
1239 static int gotEof = 0;
\r
1241 /* Pass data read from player on to ICS */
\r
1244 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1245 if (outCount < count) {
\r
1246 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1248 } else if (count < 0) {
\r
1249 RemoveInputSource(isr);
\r
1250 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1251 } else if (gotEof++ > 0) {
\r
1252 RemoveInputSource(isr);
\r
1253 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1261 int count, outCount, outError;
\r
1263 if (icsPR == NULL) return;
\r
1265 count = strlen(s);
\r
1266 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1267 if (outCount < count) {
\r
1268 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1272 /* This is used for sending logon scripts to the ICS. Sending
\r
1273 without a delay causes problems when using timestamp on ICC
\r
1274 (at least on my machine). */
\r
1276 SendToICSDelayed(s,msdelay)
\r
1280 int count, outCount, outError;
\r
1282 if (icsPR == NULL) return;
\r
1284 count = strlen(s);
\r
1285 if (appData.debugMode) {
\r
1286 fprintf(debugFP, ">ICS: ");
\r
1287 show_bytes(debugFP, s, count);
\r
1288 fprintf(debugFP, "\n");
\r
1290 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1292 if (outCount < count) {
\r
1293 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1298 /* Remove all highlighting escape sequences in s
\r
1299 Also deletes any suffix starting with '('
\r
1302 StripHighlightAndTitle(s)
\r
1305 static char retbuf[MSG_SIZ];
\r
1308 while (*s != NULLCHAR) {
\r
1309 while (*s == '\033') {
\r
1310 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1311 if (*s != NULLCHAR) s++;
\r
1313 while (*s != NULLCHAR && *s != '\033') {
\r
1314 if (*s == '(' || *s == '[') {
\r
1325 /* Remove all highlighting escape sequences in s */
\r
1330 static char retbuf[MSG_SIZ];
\r
1333 while (*s != NULLCHAR) {
\r
1334 while (*s == '\033') {
\r
1335 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1336 if (*s != NULLCHAR) s++;
\r
1338 while (*s != NULLCHAR && *s != '\033') {
\r
1346 char *variantNames[] = VARIANT_NAMES;
\r
1351 return variantNames[v];
\r
1355 /* Identify a variant from the strings the chess servers use or the
\r
1356 PGN Variant tag names we use. */
\r
1358 StringToVariant(e)
\r
1363 VariantClass v = VariantNormal;
\r
1364 int i, found = FALSE;
\r
1365 char buf[MSG_SIZ];
\r
1369 /* [HGM] skip over optional board-size prefixes */
\r
1370 if( sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1371 while( *e++ != '_');
\r
1372 } else if( sscanf(e, "%dx%d_", &i, &i) == 2 ) {
\r
1373 while( *e++ != '_');
\r
1376 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1377 if (StrCaseStr(e, variantNames[i])) {
\r
1378 v = (VariantClass) i;
\r
1385 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1386 || StrCaseStr(e, "wild/fr")) {
\r
1387 v = VariantFischeRandom;
\r
1388 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1389 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1391 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1392 if (isdigit(*p)) {
\r
1398 case 0: /* FICS only, actually */
\r
1400 /* Castling legal even if K starts on d-file */
\r
1401 v = VariantWildCastle;
\r
1406 /* Castling illegal even if K & R happen to start in
\r
1407 normal positions. */
\r
1408 v = VariantNoCastle;
\r
1421 /* Castling legal iff K & R start in normal positions */
\r
1422 v = VariantNormal;
\r
1427 /* Special wilds for position setup; unclear what to do here */
\r
1428 v = VariantLoadable;
\r
1431 /* Bizarre ICC game */
\r
1432 v = VariantTwoKings;
\r
1435 v = VariantKriegspiel;
\r
1438 v = VariantLosers;
\r
1441 v = VariantFischeRandom;
\r
1444 v = VariantCrazyhouse;
\r
1447 v = VariantBughouse;
\r
1450 v = Variant3Check;
\r
1453 /* Not quite the same as FICS suicide! */
\r
1454 v = VariantGiveaway;
\r
1457 v = VariantAtomic;
\r
1460 v = VariantShatranj;
\r
1463 /* Temporary names for future ICC types. The name *will* change in
\r
1464 the next xboard/WinBoard release after ICC defines it. */
\r
1493 v = VariantXiangqi;
\r
1496 v = VariantCourier;
\r
1499 v = VariantGothic;
\r
1502 v = VariantCapablanca;
\r
1505 v = VariantKnightmate;
\r
1511 v = VariantShowgi;
\r
1515 /* Found "wild" or "w" in the string but no number;
\r
1516 must assume it's normal chess. */
\r
1517 v = VariantNormal;
\r
1520 sprintf(buf, "Unknown wild type %d", wnum);
\r
1521 DisplayError(buf, 0);
\r
1522 v = VariantUnknown;
\r
1527 if (appData.debugMode) {
\r
1528 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1529 e, wnum, VariantName(v));
\r
1534 static int leftover_start = 0, leftover_len = 0;
\r
1535 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1537 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1538 advance *index beyond it, and set leftover_start to the new value of
\r
1539 *index; else return FALSE. If pattern contains the character '*', it
\r
1540 matches any sequence of characters not containing '\r', '\n', or the
\r
1541 character following the '*' (if any), and the matched sequence(s) are
\r
1542 copied into star_match.
\r
1545 looking_at(buf, index, pattern)
\r
1550 char *bufp = &buf[*index], *patternp = pattern;
\r
1551 int star_count = 0;
\r
1552 char *matchp = star_match[0];
\r
1555 if (*patternp == NULLCHAR) {
\r
1556 *index = leftover_start = bufp - buf;
\r
1557 *matchp = NULLCHAR;
\r
1560 if (*bufp == NULLCHAR) return FALSE;
\r
1561 if (*patternp == '*') {
\r
1562 if (*bufp == *(patternp + 1)) {
\r
1563 *matchp = NULLCHAR;
\r
1564 matchp = star_match[++star_count];
\r
1568 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1570 if (*patternp == NULLCHAR)
\r
1575 *matchp++ = *bufp++;
\r
1579 if (*patternp != *bufp) return FALSE;
\r
1586 SendToPlayer(data, length)
\r
1590 int error, outCount;
\r
1591 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1592 if (outCount < length) {
\r
1593 DisplayFatalError("Error writing to display", error, 1);
\r
1598 PackHolding(packed, holding)
\r
1602 char *p = holding;
\r
1604 int runlength = 0;
\r
1610 switch (runlength) {
\r
1621 sprintf(q, "%d", runlength);
\r
1633 /* Telnet protocol requests from the front end */
\r
1635 TelnetRequest(ddww, option)
\r
1636 unsigned char ddww, option;
\r
1638 unsigned char msg[3];
\r
1639 int outCount, outError;
\r
1641 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1643 if (appData.debugMode) {
\r
1644 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1660 sprintf(buf1, "%d", ddww);
\r
1665 optionStr = "ECHO";
\r
1669 sprintf(buf2, "%d", option);
\r
1672 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1677 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1678 if (outCount < 3) {
\r
1679 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1686 if (!appData.icsActive) return;
\r
1687 TelnetRequest(TN_DO, TN_ECHO);
\r
1693 if (!appData.icsActive) return;
\r
1694 TelnetRequest(TN_DONT, TN_ECHO);
\r
1698 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1700 /* put the holdings sent to us by the server on the board holdings area */
\r
1701 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1703 ChessSquare piece;
\r
1705 if(gameInfo.holdingsWidth < 1) return;
\r
1707 if( (int)lowestPiece >= BlackPawn ) {
\r
1708 holdingsColumn = 0;
\r
1710 holdingsStartRow = BOARD_HEIGHT-1;
\r
1713 holdingsColumn = BOARD_WIDTH-1;
\r
1714 countsColumn = BOARD_WIDTH-2;
\r
1715 holdingsStartRow = 0;
\r
1719 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1720 board[i][holdingsColumn] = EmptySquare;
\r
1721 board[i][countsColumn] = (ChessSquare) 0;
\r
1723 while( (p=*holdings++) != NULLCHAR ) {
\r
1724 piece = CharToPiece( ToUpper(p) );
\r
1725 if(piece == EmptySquare) continue;
\r
1726 j = (int) piece - (int) WhitePawn;
\r
1727 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1728 if(j < 0) continue; /* should not happen */
\r
1729 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1730 board[holdingsStartRow+i*direction][holdingsColumn] = piece;
\r
1731 board[holdingsStartRow+i*direction][countsColumn]++;
\r
1736 static int loggedOn = FALSE;
\r
1738 /*-- Game start info cache: --*/
\r
1740 char gs_kind[MSG_SIZ];
\r
1741 static char player1Name[128] = "";
\r
1742 static char player2Name[128] = "";
\r
1743 static int player1Rating = -1;
\r
1744 static int player2Rating = -1;
\r
1745 /*----------------------------*/
\r
1747 ColorClass curColor = ColorNormal;
\r
1750 read_from_ics(isr, closure, data, count, error)
\r
1751 InputSourceRef isr;
\r
1757 #define BUF_SIZE 8192
\r
1758 #define STARTED_NONE 0
\r
1759 #define STARTED_MOVES 1
\r
1760 #define STARTED_BOARD 2
\r
1761 #define STARTED_OBSERVE 3
\r
1762 #define STARTED_HOLDINGS 4
\r
1763 #define STARTED_CHATTER 5
\r
1764 #define STARTED_COMMENT 6
\r
1765 #define STARTED_MOVES_NOHIDE 7
\r
1767 static int started = STARTED_NONE;
\r
1768 static char parse[20000];
\r
1769 static int parse_pos = 0;
\r
1770 static char buf[BUF_SIZE + 1];
\r
1771 static int firstTime = TRUE, intfSet = FALSE;
\r
1772 static ColorClass prevColor = ColorNormal;
\r
1773 static int savingComment = FALSE;
\r
1782 if (appData.debugMode) {
\r
1784 fprintf(debugFP, "<ICS: ");
\r
1785 show_bytes(debugFP, data, count);
\r
1786 fprintf(debugFP, "\n");
\r
1792 /* If last read ended with a partial line that we couldn't parse,
\r
1793 prepend it to the new read and try again. */
\r
1794 if (leftover_len > 0) {
\r
1795 for (i=0; i<leftover_len; i++)
\r
1796 buf[i] = buf[leftover_start + i];
\r
1799 /* Copy in new characters, removing nulls and \r's */
\r
1800 buf_len = leftover_len;
\r
1801 for (i = 0; i < count; i++) {
\r
1802 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1803 buf[buf_len++] = data[i];
\r
1806 buf[buf_len] = NULLCHAR;
\r
1807 next_out = leftover_len;
\r
1808 leftover_start = 0;
\r
1811 while (i < buf_len) {
\r
1812 /* Deal with part of the TELNET option negotiation
\r
1813 protocol. We refuse to do anything beyond the
\r
1814 defaults, except that we allow the WILL ECHO option,
\r
1815 which ICS uses to turn off password echoing when we are
\r
1816 directly connected to it. We reject this option
\r
1817 if localLineEditing mode is on (always on in xboard)
\r
1818 and we are talking to port 23, which might be a real
\r
1819 telnet server that will try to keep WILL ECHO on permanently.
\r
1821 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1822 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1823 unsigned char option;
\r
1825 switch ((unsigned char) buf[++i]) {
\r
1827 if (appData.debugMode)
\r
1828 fprintf(debugFP, "\n<WILL ");
\r
1829 switch (option = (unsigned char) buf[++i]) {
\r
1831 if (appData.debugMode)
\r
1832 fprintf(debugFP, "ECHO ");
\r
1833 /* Reply only if this is a change, according
\r
1834 to the protocol rules. */
\r
1835 if (remoteEchoOption) break;
\r
1836 if (appData.localLineEditing &&
\r
1837 atoi(appData.icsPort) == TN_PORT) {
\r
1838 TelnetRequest(TN_DONT, TN_ECHO);
\r
1841 TelnetRequest(TN_DO, TN_ECHO);
\r
1842 remoteEchoOption = TRUE;
\r
1846 if (appData.debugMode)
\r
1847 fprintf(debugFP, "%d ", option);
\r
1848 /* Whatever this is, we don't want it. */
\r
1849 TelnetRequest(TN_DONT, option);
\r
1854 if (appData.debugMode)
\r
1855 fprintf(debugFP, "\n<WONT ");
\r
1856 switch (option = (unsigned char) buf[++i]) {
\r
1858 if (appData.debugMode)
\r
1859 fprintf(debugFP, "ECHO ");
\r
1860 /* Reply only if this is a change, according
\r
1861 to the protocol rules. */
\r
1862 if (!remoteEchoOption) break;
\r
1864 TelnetRequest(TN_DONT, TN_ECHO);
\r
1865 remoteEchoOption = FALSE;
\r
1868 if (appData.debugMode)
\r
1869 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1870 /* Whatever this is, it must already be turned
\r
1871 off, because we never agree to turn on
\r
1872 anything non-default, so according to the
\r
1873 protocol rules, we don't reply. */
\r
1878 if (appData.debugMode)
\r
1879 fprintf(debugFP, "\n<DO ");
\r
1880 switch (option = (unsigned char) buf[++i]) {
\r
1882 /* Whatever this is, we refuse to do it. */
\r
1883 if (appData.debugMode)
\r
1884 fprintf(debugFP, "%d ", option);
\r
1885 TelnetRequest(TN_WONT, option);
\r
1890 if (appData.debugMode)
\r
1891 fprintf(debugFP, "\n<DONT ");
\r
1892 switch (option = (unsigned char) buf[++i]) {
\r
1894 if (appData.debugMode)
\r
1895 fprintf(debugFP, "%d ", option);
\r
1896 /* Whatever this is, we are already not doing
\r
1897 it, because we never agree to do anything
\r
1898 non-default, so according to the protocol
\r
1899 rules, we don't reply. */
\r
1904 if (appData.debugMode)
\r
1905 fprintf(debugFP, "\n<IAC ");
\r
1906 /* Doubled IAC; pass it through */
\r
1910 if (appData.debugMode)
\r
1911 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1912 /* Drop all other telnet commands on the floor */
\r
1915 if (oldi > next_out)
\r
1916 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1917 if (++i > next_out)
\r
1922 /* OK, this at least will *usually* work */
\r
1923 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1927 if (loggedOn && !intfSet) {
\r
1928 if (ics_type == ICS_ICC) {
\r
1930 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1933 } else if (ics_type == ICS_CHESSNET) {
\r
1934 sprintf(str, "/style 12\n");
\r
1936 strcpy(str, "alias $ @\n$set interface ");
\r
1937 strcat(str, programVersion);
\r
1938 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1940 strcat(str, "$iset nohighlight 1\n");
\r
1942 strcat(str, "$iset lock 1\n$style 12\n");
\r
1948 if (started == STARTED_COMMENT) {
\r
1949 /* Accumulate characters in comment */
\r
1950 parse[parse_pos++] = buf[i];
\r
1951 if (buf[i] == '\n') {
\r
1952 parse[parse_pos] = NULLCHAR;
\r
1953 AppendComment(forwardMostMove, StripHighlight(parse));
\r
1954 started = STARTED_NONE;
\r
1956 /* Don't match patterns against characters in chatter */
\r
1961 if (started == STARTED_CHATTER) {
\r
1962 if (buf[i] != '\n') {
\r
1963 /* Don't match patterns against characters in chatter */
\r
1967 started = STARTED_NONE;
\r
1970 /* Kludge to deal with rcmd protocol */
\r
1971 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
1972 DisplayFatalError(&buf[1], 0, 1);
\r
1975 firstTime = FALSE;
\r
1978 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
1979 ics_type = ICS_ICC;
\r
1981 if (appData.debugMode)
\r
1982 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1985 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
1986 ics_type = ICS_FICS;
\r
1988 if (appData.debugMode)
\r
1989 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1992 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
1993 ics_type = ICS_CHESSNET;
\r
1995 if (appData.debugMode)
\r
1996 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2001 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2002 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2003 looking_at(buf, &i, "will be \"*\""))) {
\r
2004 strcpy(ics_handle, star_match[0]);
\r
2008 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2009 char buf[MSG_SIZ];
\r
2010 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2011 DisplayIcsInteractionTitle(buf);
\r
2012 have_set_title = TRUE;
\r
2015 /* skip finger notes */
\r
2016 if (started == STARTED_NONE &&
\r
2017 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2018 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2019 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2020 started = STARTED_CHATTER;
\r
2025 /* skip formula vars */
\r
2026 if (started == STARTED_NONE &&
\r
2027 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2028 started = STARTED_CHATTER;
\r
2034 if (appData.zippyTalk || appData.zippyPlay) {
\r
2036 if (ZippyControl(buf, &i) ||
\r
2037 ZippyConverse(buf, &i) ||
\r
2038 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2044 if (/* Don't color "message" or "messages" output */
\r
2045 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2046 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2047 looking_at(buf, &i, "--* (*:*): ") ||
\r
2048 /* Regular tells and says */
\r
2049 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2050 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2051 looking_at(buf, &i, "* says: ") ||
\r
2052 /* Message notifications (same color as tells) */
\r
2053 looking_at(buf, &i, "* has left a message ") ||
\r
2054 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2055 /* Whispers and kibitzes */
\r
2056 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2057 looking_at(buf, &i, "* kibitzes: ") ||
\r
2058 /* Channel tells */
\r
2059 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2061 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2062 /* Avoid "tells you:" spoofs in channels */
\r
2065 if (star_match[0][0] == NULLCHAR ||
\r
2066 strchr(star_match[0], ' ') ||
\r
2067 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2068 /* Reject bogus matches */
\r
2071 if (appData.colorize) {
\r
2072 if (oldi > next_out) {
\r
2073 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2078 Colorize(ColorTell, FALSE);
\r
2079 curColor = ColorTell;
\r
2082 Colorize(ColorKibitz, FALSE);
\r
2083 curColor = ColorKibitz;
\r
2086 p = strrchr(star_match[1], '(');
\r
2088 p = star_match[1];
\r
2092 if (atoi(p) == 1) {
\r
2093 Colorize(ColorChannel1, FALSE);
\r
2094 curColor = ColorChannel1;
\r
2096 Colorize(ColorChannel, FALSE);
\r
2097 curColor = ColorChannel;
\r
2101 curColor = ColorNormal;
\r
2105 if (started == STARTED_NONE && appData.autoComment &&
\r
2106 (gameMode == IcsObserving ||
\r
2107 gameMode == IcsPlayingWhite ||
\r
2108 gameMode == IcsPlayingBlack)) {
\r
2109 parse_pos = i - oldi;
\r
2110 memcpy(parse, &buf[oldi], parse_pos);
\r
2111 parse[parse_pos] = NULLCHAR;
\r
2112 started = STARTED_COMMENT;
\r
2113 savingComment = TRUE;
\r
2115 started = STARTED_CHATTER;
\r
2116 savingComment = FALSE;
\r
2123 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2124 looking_at(buf, &i, "* c-shouts: ")) {
\r
2125 if (appData.colorize) {
\r
2126 if (oldi > next_out) {
\r
2127 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2130 Colorize(ColorSShout, FALSE);
\r
2131 curColor = ColorSShout;
\r
2134 started = STARTED_CHATTER;
\r
2138 if (looking_at(buf, &i, "--->")) {
\r
2143 if (looking_at(buf, &i, "* shouts: ") ||
\r
2144 looking_at(buf, &i, "--> ")) {
\r
2145 if (appData.colorize) {
\r
2146 if (oldi > next_out) {
\r
2147 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2150 Colorize(ColorShout, FALSE);
\r
2151 curColor = ColorShout;
\r
2154 started = STARTED_CHATTER;
\r
2158 if (looking_at( buf, &i, "Challenge:")) {
\r
2159 if (appData.colorize) {
\r
2160 if (oldi > next_out) {
\r
2161 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2164 Colorize(ColorChallenge, FALSE);
\r
2165 curColor = ColorChallenge;
\r
2171 if (looking_at(buf, &i, "* offers you") ||
\r
2172 looking_at(buf, &i, "* offers to be") ||
\r
2173 looking_at(buf, &i, "* would like to") ||
\r
2174 looking_at(buf, &i, "* requests to") ||
\r
2175 looking_at(buf, &i, "Your opponent offers") ||
\r
2176 looking_at(buf, &i, "Your opponent requests")) {
\r
2178 if (appData.colorize) {
\r
2179 if (oldi > next_out) {
\r
2180 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2183 Colorize(ColorRequest, FALSE);
\r
2184 curColor = ColorRequest;
\r
2189 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2190 if (appData.colorize) {
\r
2191 if (oldi > next_out) {
\r
2192 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2195 Colorize(ColorSeek, FALSE);
\r
2196 curColor = ColorSeek;
\r
2202 if (looking_at(buf, &i, "\\ ")) {
\r
2203 if (prevColor != ColorNormal) {
\r
2204 if (oldi > next_out) {
\r
2205 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2208 Colorize(prevColor, TRUE);
\r
2209 curColor = prevColor;
\r
2211 if (savingComment) {
\r
2212 parse_pos = i - oldi;
\r
2213 memcpy(parse, &buf[oldi], parse_pos);
\r
2214 parse[parse_pos] = NULLCHAR;
\r
2215 started = STARTED_COMMENT;
\r
2217 started = STARTED_CHATTER;
\r
2222 if (looking_at(buf, &i, "Black Strength :") ||
\r
2223 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2224 looking_at(buf, &i, "<10>") ||
\r
2225 looking_at(buf, &i, "#@#")) {
\r
2226 /* Wrong board style */
\r
2228 SendToICS(ics_prefix);
\r
2229 SendToICS("set style 12\n");
\r
2230 SendToICS(ics_prefix);
\r
2231 SendToICS("refresh\n");
\r
2235 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2237 have_sent_ICS_logon = 1;
\r
2241 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2242 (looking_at(buf, &i, "\n<12> ") ||
\r
2243 looking_at(buf, &i, "<12> "))) {
\r
2245 if (oldi > next_out) {
\r
2246 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2249 started = STARTED_BOARD;
\r
2254 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2255 looking_at(buf, &i, "<b1> ")) {
\r
2256 if (oldi > next_out) {
\r
2257 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2260 started = STARTED_HOLDINGS;
\r
2265 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2267 /* Header for a move list -- first line */
\r
2269 switch (ics_getting_history) {
\r
2271 switch (gameMode) {
\r
2273 case BeginningOfGame:
\r
2274 /* User typed "moves" or "oldmoves" while we
\r
2275 were idle. Pretend we asked for these
\r
2276 moves and soak them up so user can step
\r
2277 through them and/or save them.
\r
2279 Reset(FALSE, TRUE);
\r
2280 gameMode = IcsObserving;
\r
2283 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2285 case EditGame: /*?*/
\r
2286 case EditPosition: /*?*/
\r
2287 /* Should above feature work in these modes too? */
\r
2288 /* For now it doesn't */
\r
2289 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2292 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2297 /* Is this the right one? */
\r
2298 if (gameInfo.white && gameInfo.black &&
\r
2299 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2300 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2302 ics_getting_history = H_GOT_REQ_HEADER;
\r
2305 case H_GOT_REQ_HEADER:
\r
2306 case H_GOT_UNREQ_HEADER:
\r
2307 case H_GOT_UNWANTED_HEADER:
\r
2308 case H_GETTING_MOVES:
\r
2309 /* Should not happen */
\r
2310 DisplayError("Error gathering move list: two headers", 0);
\r
2311 ics_getting_history = H_FALSE;
\r
2315 /* Save player ratings into gameInfo if needed */
\r
2316 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2317 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2318 (gameInfo.whiteRating == -1 ||
\r
2319 gameInfo.blackRating == -1)) {
\r
2321 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2322 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2323 if (appData.debugMode)
\r
2324 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2325 gameInfo.whiteRating, gameInfo.blackRating);
\r
2330 if (looking_at(buf, &i,
\r
2331 "* * match, initial time: * minute*, increment: * second")) {
\r
2332 /* Header for a move list -- second line */
\r
2333 /* Initial board will follow if this is a wild game */
\r
2335 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2336 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2337 gameInfo.event = StrSave(str);
\r
2338 gameInfo.variant = StringToVariant(gameInfo.event);
\r
2342 if (looking_at(buf, &i, "Move ")) {
\r
2343 /* Beginning of a move list */
\r
2344 switch (ics_getting_history) {
\r
2346 /* Normally should not happen */
\r
2347 /* Maybe user hit reset while we were parsing */
\r
2350 /* Happens if we are ignoring a move list that is not
\r
2351 * the one we just requested. Common if the user
\r
2352 * tries to observe two games without turning off
\r
2355 case H_GETTING_MOVES:
\r
2356 /* Should not happen */
\r
2357 DisplayError("Error gathering move list: nested", 0);
\r
2358 ics_getting_history = H_FALSE;
\r
2360 case H_GOT_REQ_HEADER:
\r
2361 ics_getting_history = H_GETTING_MOVES;
\r
2362 started = STARTED_MOVES;
\r
2364 if (oldi > next_out) {
\r
2365 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2368 case H_GOT_UNREQ_HEADER:
\r
2369 ics_getting_history = H_GETTING_MOVES;
\r
2370 started = STARTED_MOVES_NOHIDE;
\r
2373 case H_GOT_UNWANTED_HEADER:
\r
2374 ics_getting_history = H_FALSE;
\r
2380 if (looking_at(buf, &i, "% ") ||
\r
2381 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2382 && looking_at(buf, &i, "}*"))) {
\r
2383 savingComment = FALSE;
\r
2384 switch (started) {
\r
2385 case STARTED_MOVES:
\r
2386 case STARTED_MOVES_NOHIDE:
\r
2387 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2388 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2389 ParseGameHistory(parse);
\r
2391 if (appData.zippyPlay && first.initDone) {
\r
2392 FeedMovesToProgram(&first, forwardMostMove);
\r
2393 if (gameMode == IcsPlayingWhite) {
\r
2394 if (WhiteOnMove(forwardMostMove)) {
\r
2395 if (first.sendTime) {
\r
2396 if (first.useColors) {
\r
2397 SendToProgram("black\n", &first);
\r
2399 SendTimeRemaining(&first, TRUE);
\r
2401 if (first.useColors) {
\r
2402 SendToProgram("white\ngo\n", &first);
\r
2404 SendToProgram("go\n", &first);
\r
2406 first.maybeThinking = TRUE;
\r
2408 if (first.usePlayother) {
\r
2409 if (first.sendTime) {
\r
2410 SendTimeRemaining(&first, TRUE);
\r
2412 SendToProgram("playother\n", &first);
\r
2413 firstMove = FALSE;
\r
2418 } else if (gameMode == IcsPlayingBlack) {
\r
2419 if (!WhiteOnMove(forwardMostMove)) {
\r
2420 if (first.sendTime) {
\r
2421 if (first.useColors) {
\r
2422 SendToProgram("white\n", &first);
\r
2424 SendTimeRemaining(&first, FALSE);
\r
2426 if (first.useColors) {
\r
2427 SendToProgram("black\ngo\n", &first);
\r
2429 SendToProgram("go\n", &first);
\r
2431 first.maybeThinking = TRUE;
\r
2433 if (first.usePlayother) {
\r
2434 if (first.sendTime) {
\r
2435 SendTimeRemaining(&first, FALSE);
\r
2437 SendToProgram("playother\n", &first);
\r
2438 firstMove = FALSE;
\r
2446 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2447 /* Moves came from oldmoves or moves command
\r
2448 while we weren't doing anything else.
\r
2450 currentMove = forwardMostMove;
\r
2451 ClearHighlights();/*!!could figure this out*/
\r
2452 flipView = appData.flipView;
\r
2453 DrawPosition(FALSE, boards[currentMove]);
\r
2454 DisplayBothClocks();
\r
2455 sprintf(str, "%s vs. %s",
\r
2456 gameInfo.white, gameInfo.black);
\r
2457 DisplayTitle(str);
\r
2458 gameMode = IcsIdle;
\r
2460 /* Moves were history of an active game */
\r
2461 if (gameInfo.resultDetails != NULL) {
\r
2462 free(gameInfo.resultDetails);
\r
2463 gameInfo.resultDetails = NULL;
\r
2466 HistorySet(parseList, backwardMostMove,
\r
2467 forwardMostMove, currentMove-1);
\r
2468 DisplayMove(currentMove - 1);
\r
2469 if (started == STARTED_MOVES) next_out = i;
\r
2470 started = STARTED_NONE;
\r
2471 ics_getting_history = H_FALSE;
\r
2474 case STARTED_OBSERVE:
\r
2475 started = STARTED_NONE;
\r
2476 SendToICS(ics_prefix);
\r
2477 SendToICS("refresh\n");
\r
2486 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2487 started == STARTED_HOLDINGS ||
\r
2488 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2489 /* Accumulate characters in move list or board */
\r
2490 parse[parse_pos++] = buf[i];
\r
2493 /* Start of game messages. Mostly we detect start of game
\r
2494 when the first board image arrives. On some versions
\r
2495 of the ICS, though, we need to do a "refresh" after starting
\r
2496 to observe in order to get the current board right away. */
\r
2497 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2498 started = STARTED_OBSERVE;
\r
2502 /* Handle auto-observe */
\r
2503 if (appData.autoObserve &&
\r
2504 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2505 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2507 /* Choose the player that was highlighted, if any. */
\r
2508 if (star_match[0][0] == '\033' ||
\r
2509 star_match[1][0] != '\033') {
\r
2510 player = star_match[0];
\r
2512 player = star_match[2];
\r
2514 sprintf(str, "%sobserve %s\n",
\r
2515 ics_prefix, StripHighlightAndTitle(player));
\r
2518 /* Save ratings from notify string */
\r
2519 strcpy(player1Name, star_match[0]);
\r
2520 player1Rating = string_to_rating(star_match[1]);
\r
2521 strcpy(player2Name, star_match[2]);
\r
2522 player2Rating = string_to_rating(star_match[3]);
\r
2524 if (appData.debugMode)
\r
2526 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2527 player1Name, player1Rating,
\r
2528 player2Name, player2Rating);
\r
2533 /* Deal with automatic examine mode after a game,
\r
2534 and with IcsObserving -> IcsExamining transition */
\r
2535 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2536 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2538 int gamenum = atoi(star_match[0]);
\r
2539 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2540 gamenum == ics_gamenum) {
\r
2541 /* We were already playing or observing this game;
\r
2542 no need to refetch history */
\r
2543 gameMode = IcsExamining;
\r
2545 pauseExamForwardMostMove = forwardMostMove;
\r
2546 } else if (currentMove < forwardMostMove) {
\r
2547 ForwardInner(forwardMostMove);
\r
2550 /* I don't think this case really can happen */
\r
2551 SendToICS(ics_prefix);
\r
2552 SendToICS("refresh\n");
\r
2557 /* Error messages */
\r
2558 if (ics_user_moved) {
\r
2559 if (looking_at(buf, &i, "Illegal move") ||
\r
2560 looking_at(buf, &i, "Not a legal move") ||
\r
2561 looking_at(buf, &i, "Your king is in check") ||
\r
2562 looking_at(buf, &i, "It isn't your turn") ||
\r
2563 looking_at(buf, &i, "It is not your move")) {
\r
2564 /* Illegal move */
\r
2565 ics_user_moved = 0;
\r
2566 if (forwardMostMove > backwardMostMove) {
\r
2567 currentMove = --forwardMostMove;
\r
2568 DisplayMove(currentMove - 1); /* before DMError */
\r
2569 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2570 DrawPosition(FALSE, boards[currentMove]);
\r
2572 DisplayBothClocks();
\r
2578 if (looking_at(buf, &i, "still have time") ||
\r
2579 looking_at(buf, &i, "not out of time") ||
\r
2580 looking_at(buf, &i, "either player is out of time") ||
\r
2581 looking_at(buf, &i, "has timeseal; checking")) {
\r
2582 /* We must have called his flag a little too soon */
\r
2583 whiteFlag = blackFlag = FALSE;
\r
2587 if (looking_at(buf, &i, "added * seconds to") ||
\r
2588 looking_at(buf, &i, "seconds were added to")) {
\r
2589 /* Update the clocks */
\r
2590 SendToICS(ics_prefix);
\r
2591 SendToICS("refresh\n");
\r
2595 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2596 ics_clock_paused = TRUE;
\r
2601 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2602 ics_clock_paused = FALSE;
\r
2607 /* Grab player ratings from the Creating: message.
\r
2608 Note we have to check for the special case when
\r
2609 the ICS inserts things like [white] or [black]. */
\r
2610 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2611 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2613 0 player 1 name (not necessarily white)
\r
2615 2 empty, white, or black (IGNORED)
\r
2616 3 player 2 name (not necessarily black)
\r
2619 The names/ratings are sorted out when the game
\r
2620 actually starts (below).
\r
2622 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2623 player1Rating = string_to_rating(star_match[1]);
\r
2624 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2625 player2Rating = string_to_rating(star_match[4]);
\r
2627 if (appData.debugMode)
\r
2629 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2630 player1Name, player1Rating,
\r
2631 player2Name, player2Rating);
\r
2636 /* Improved generic start/end-of-game messages */
\r
2637 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2638 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2639 /* If tkind == 0: */
\r
2640 /* star_match[0] is the game number */
\r
2641 /* [1] is the white player's name */
\r
2642 /* [2] is the black player's name */
\r
2643 /* For end-of-game: */
\r
2644 /* [3] is the reason for the game end */
\r
2645 /* [4] is a PGN end game-token, preceded by " " */
\r
2646 /* For start-of-game: */
\r
2647 /* [3] begins with "Creating" or "Continuing" */
\r
2648 /* [4] is " *" or empty (don't care). */
\r
2649 int gamenum = atoi(star_match[0]);
\r
2650 char *whitename, *blackname, *why, *endtoken;
\r
2651 ChessMove endtype = (ChessMove) 0;
\r
2654 whitename = star_match[1];
\r
2655 blackname = star_match[2];
\r
2656 why = star_match[3];
\r
2657 endtoken = star_match[4];
\r
2659 whitename = star_match[1];
\r
2660 blackname = star_match[3];
\r
2661 why = star_match[5];
\r
2662 endtoken = star_match[6];
\r
2665 /* Game start messages */
\r
2666 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2667 strncmp(why, "Continuing ", 11) == 0) {
\r
2668 gs_gamenum = gamenum;
\r
2669 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2671 if (appData.zippyPlay) {
\r
2672 ZippyGameStart(whitename, blackname);
\r
2678 /* Game end messages */
\r
2679 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2680 ics_gamenum != gamenum) {
\r
2683 while (endtoken[0] == ' ') endtoken++;
\r
2684 switch (endtoken[0]) {
\r
2687 endtype = GameUnfinished;
\r
2690 endtype = BlackWins;
\r
2693 if (endtoken[1] == '/')
\r
2694 endtype = GameIsDrawn;
\r
2696 endtype = WhiteWins;
\r
2699 GameEnds(endtype, why, GE_ICS);
\r
2701 if (appData.zippyPlay && first.initDone) {
\r
2702 ZippyGameEnd(endtype, why);
\r
2703 if (first.pr == NULL) {
\r
2704 /* Start the next process early so that we'll
\r
2705 be ready for the next challenge */
\r
2706 StartChessProgram(&first);
\r
2708 /* Send "new" early, in case this command takes
\r
2709 a long time to finish, so that we'll be ready
\r
2710 for the next challenge. */
\r
2711 Reset(TRUE, TRUE);
\r
2717 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2718 looking_at(buf, &i, "no longer observing game *") ||
\r
2719 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2720 if (gameMode == IcsObserving &&
\r
2721 atoi(star_match[0]) == ics_gamenum)
\r
2724 gameMode = IcsIdle;
\r
2726 ics_user_moved = FALSE;
\r
2731 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2732 if (gameMode == IcsExamining &&
\r
2733 atoi(star_match[0]) == ics_gamenum)
\r
2735 gameMode = IcsIdle;
\r
2737 ics_user_moved = FALSE;
\r
2742 /* Advance leftover_start past any newlines we find,
\r
2743 so only partial lines can get reparsed */
\r
2744 if (looking_at(buf, &i, "\n")) {
\r
2745 prevColor = curColor;
\r
2746 if (curColor != ColorNormal) {
\r
2747 if (oldi > next_out) {
\r
2748 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2751 Colorize(ColorNormal, FALSE);
\r
2752 curColor = ColorNormal;
\r
2754 if (started == STARTED_BOARD) {
\r
2755 started = STARTED_NONE;
\r
2756 parse[parse_pos] = NULLCHAR;
\r
2757 ParseBoard12(parse);
\r
2758 ics_user_moved = 0;
\r
2760 /* Send premove here */
\r
2761 if (appData.premove) {
\r
2762 char str[MSG_SIZ];
\r
2763 if (currentMove == 0 &&
\r
2764 gameMode == IcsPlayingWhite &&
\r
2765 appData.premoveWhite) {
\r
2766 sprintf(str, "%s%s\n", ics_prefix,
\r
2767 appData.premoveWhiteText);
\r
2768 if (appData.debugMode)
\r
2769 fprintf(debugFP, "Sending premove:\n");
\r
2771 } else if (currentMove == 1 &&
\r
2772 gameMode == IcsPlayingBlack &&
\r
2773 appData.premoveBlack) {
\r
2774 sprintf(str, "%s%s\n", ics_prefix,
\r
2775 appData.premoveBlackText);
\r
2776 if (appData.debugMode)
\r
2777 fprintf(debugFP, "Sending premove:\n");
\r
2779 } else if (gotPremove) {
\r
2781 ClearPremoveHighlights();
\r
2782 if (appData.debugMode)
\r
2783 fprintf(debugFP, "Sending premove:\n");
\r
2784 UserMoveEvent(premoveFromX, premoveFromY,
\r
2785 premoveToX, premoveToY,
\r
2786 premovePromoChar);
\r
2790 /* Usually suppress following prompt */
\r
2791 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2792 if (looking_at(buf, &i, "*% ")) {
\r
2793 savingComment = FALSE;
\r
2797 } else if (started == STARTED_HOLDINGS) {
\r
2799 char new_piece[MSG_SIZ];
\r
2800 started = STARTED_NONE;
\r
2801 parse[parse_pos] = NULLCHAR;
\r
2802 if (appData.debugMode)
\r
2803 fprintf(debugFP, "Parsing holdings: %s\n", parse);
\r
2804 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2805 gamenum == ics_gamenum) {
\r
2806 if (gameInfo.variant == VariantNormal) {
\r
2807 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
\r
2808 /* Get a move list just to see the header, which
\r
2809 will tell us whether this is really bug or zh */
\r
2810 if (ics_getting_history == H_FALSE) {
\r
2811 ics_getting_history = H_REQUESTED;
\r
2812 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2816 new_piece[0] = NULLCHAR;
\r
2817 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2818 &gamenum, white_holding, black_holding,
\r
2820 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2821 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2823 if (appData.zippyPlay && first.initDone) {
\r
2824 ZippyHoldings(white_holding, black_holding,
\r
2828 if (tinyLayout || smallLayout) {
\r
2829 char wh[16], bh[16];
\r
2830 PackHolding(wh, white_holding);
\r
2831 PackHolding(bh, black_holding);
\r
2832 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2833 gameInfo.white, gameInfo.black);
\r
2835 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2836 gameInfo.white, white_holding,
\r
2837 gameInfo.black, black_holding);
\r
2840 /* [HGM] copy holdings to board holdings area */
\r
2841 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2842 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2843 DrawPosition(FALSE, NULL);
\r
2844 DisplayTitle(str);
\r
2846 /* Suppress following prompt */
\r
2847 if (looking_at(buf, &i, "*% ")) {
\r
2848 savingComment = FALSE;
\r
2855 i++; /* skip unparsed character and loop back */
\r
2858 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2859 started != STARTED_HOLDINGS && i > next_out) {
\r
2860 SendToPlayer(&buf[next_out], i - next_out);
\r
2864 leftover_len = buf_len - leftover_start;
\r
2865 /* if buffer ends with something we couldn't parse,
\r
2866 reparse it after appending the next read */
\r
2868 } else if (count == 0) {
\r
2869 RemoveInputSource(isr);
\r
2870 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2872 DisplayFatalError("Error reading from ICS", error, 1);
\r
2877 /* Board style 12 looks like this:
\r
2879 <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
2881 * The "<12> " is stripped before it gets to this routine. The two
\r
2882 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2883 * some chess servers may not have them, or may have only the first.
\r
2884 * Additional trailing fields may be added in the future.
\r
2887 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
\r
2889 #define RELATION_OBSERVING_PLAYED 0
\r
2890 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2891 #define RELATION_PLAYING_MYMOVE 1
\r
2892 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2893 #define RELATION_EXAMINING 2
\r
2894 #define RELATION_ISOLATED_BOARD -3
\r
2895 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2898 ParseBoard12(string)
\r
2901 GameMode newGameMode;
\r
2902 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2903 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2904 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2905 char to_play, board_chars[72];
\r
2906 char move_str[500], str[500], elapsed_time[500];
\r
2907 char black[32], white[32];
\r
2909 int prevMove = currentMove;
\r
2911 ChessMove moveType;
\r
2912 int fromX, fromY, toX, toY;
\r
2915 fromX = fromY = toX = toY = -1;
\r
2919 if (appData.debugMode)
\r
2920 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2922 move_str[0] = NULLCHAR;
\r
2923 elapsed_time[0] = NULLCHAR;
\r
2924 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2925 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2926 &gamenum, white, black, &relation, &basetime, &increment,
\r
2927 &white_stren, &black_stren, &white_time, &black_time,
\r
2928 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2932 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2933 DisplayError(str, 0);
\r
2937 /* Convert the move number to internal form */
\r
2938 moveNum = (moveNum - 1) * 2;
\r
2939 if (to_play == 'B') moveNum++;
\r
2940 if (moveNum >= MAX_MOVES) {
\r
2941 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
2946 switch (relation) {
\r
2947 case RELATION_OBSERVING_PLAYED:
\r
2948 case RELATION_OBSERVING_STATIC:
\r
2949 if (gamenum == -1) {
\r
2950 /* Old ICC buglet */
\r
2951 relation = RELATION_OBSERVING_STATIC;
\r
2953 newGameMode = IcsObserving;
\r
2955 case RELATION_PLAYING_MYMOVE:
\r
2956 case RELATION_PLAYING_NOTMYMOVE:
\r
2958 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
2959 IcsPlayingWhite : IcsPlayingBlack;
\r
2961 case RELATION_EXAMINING:
\r
2962 newGameMode = IcsExamining;
\r
2964 case RELATION_ISOLATED_BOARD:
\r
2966 /* Just display this board. If user was doing something else,
\r
2967 we will forget about it until the next board comes. */
\r
2968 newGameMode = IcsIdle;
\r
2970 case RELATION_STARTING_POSITION:
\r
2971 newGameMode = gameMode;
\r
2975 /* Modify behavior for initial board display on move listing
\r
2978 switch (ics_getting_history) {
\r
2982 case H_GOT_REQ_HEADER:
\r
2983 case H_GOT_UNREQ_HEADER:
\r
2984 /* This is the initial position of the current game */
\r
2985 gamenum = ics_gamenum;
\r
2986 moveNum = 0; /* old ICS bug workaround */
\r
2987 if (to_play == 'B') {
\r
2988 startedFromSetupPosition = TRUE;
\r
2989 blackPlaysFirst = TRUE;
\r
2991 if (forwardMostMove == 0) forwardMostMove = 1;
\r
2992 if (backwardMostMove == 0) backwardMostMove = 1;
\r
2993 if (currentMove == 0) currentMove = 1;
\r
2995 newGameMode = gameMode;
\r
2996 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
2998 case H_GOT_UNWANTED_HEADER:
\r
2999 /* This is an initial board that we don't want */
\r
3001 case H_GETTING_MOVES:
\r
3002 /* Should not happen */
\r
3003 DisplayError("Error gathering move list: extra board", 0);
\r
3004 ics_getting_history = H_FALSE;
\r
3008 /* Take action if this is the first board of a new game, or of a
\r
3009 different game than is currently being displayed. */
\r
3010 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3011 relation == RELATION_ISOLATED_BOARD) {
\r
3013 /* Forget the old game and get the history (if any) of the new one */
\r
3014 if (gameMode != BeginningOfGame) {
\r
3015 Reset(FALSE, TRUE);
\r
3018 if (appData.autoRaiseBoard) BoardToTop();
\r
3020 if (gamenum == -1) {
\r
3021 newGameMode = IcsIdle;
\r
3022 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3023 appData.getMoveList) {
\r
3024 /* Need to get game history */
\r
3025 ics_getting_history = H_REQUESTED;
\r
3026 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3030 /* Initially flip the board to have black on the bottom if playing
\r
3031 black or if the ICS flip flag is set, but let the user change
\r
3032 it with the Flip View button. */
\r
3033 flipView = appData.autoFlipView ?
\r
3034 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3037 /* Done with values from previous mode; copy in new ones */
\r
3038 gameMode = newGameMode;
\r
3040 ics_gamenum = gamenum;
\r
3041 if (gamenum == gs_gamenum) {
\r
3042 int klen = strlen(gs_kind);
\r
3043 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3044 sprintf(str, "ICS %s", gs_kind);
\r
3045 gameInfo.event = StrSave(str);
\r
3047 gameInfo.event = StrSave("ICS game");
\r
3049 gameInfo.site = StrSave(appData.icsHost);
\r
3050 gameInfo.date = PGNDate();
\r
3051 gameInfo.round = StrSave("-");
\r
3052 gameInfo.white = StrSave(white);
\r
3053 gameInfo.black = StrSave(black);
\r
3054 timeControl = basetime * 60 * 1000;
\r
3055 timeControl_2 = 0;
\r
3056 timeIncrement = increment * 1000;
\r
3057 movesPerSession = 0;
\r
3058 gameInfo.timeControl = TimeControlTagValue();
\r
3059 gameInfo.variant = StringToVariant(gameInfo.event);
\r
3060 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
3061 switch(gameInfo.variant) {
\r
3062 case VariantShogi:
\r
3063 case VariantShowgi:
\r
3064 gameInfo.boardWidth = gameInfo.boardHeight = 9;
\r
3065 gameInfo.holdingsSize += 2;
\r
3066 case VariantBughouse:
\r
3067 case VariantCrazyhouse:
\r
3068 gameInfo.boardWidth = gameInfo.boardHeight = 8;
\r
3069 gameInfo.holdingsWidth = 2; break;
\r
3071 gameInfo.boardWidth = gameInfo.boardHeight = 8;
\r
3072 gameInfo.holdingsWidth = 0;
\r
3074 gameInfo.outOfBook = NULL;
\r
3076 /* Do we have the ratings? */
\r
3077 if (strcmp(player1Name, white) == 0 &&
\r
3078 strcmp(player2Name, black) == 0) {
\r
3079 if (appData.debugMode)
\r
3080 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3081 player1Rating, player2Rating);
\r
3082 gameInfo.whiteRating = player1Rating;
\r
3083 gameInfo.blackRating = player2Rating;
\r
3084 } else if (strcmp(player2Name, white) == 0 &&
\r
3085 strcmp(player1Name, black) == 0) {
\r
3086 if (appData.debugMode)
\r
3087 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3088 player2Rating, player1Rating);
\r
3089 gameInfo.whiteRating = player2Rating;
\r
3090 gameInfo.blackRating = player1Rating;
\r
3092 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3094 /* Silence shouts if requested */
\r
3095 if (appData.quietPlay &&
\r
3096 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3097 SendToICS(ics_prefix);
\r
3098 SendToICS("set shout 0\n");
\r
3102 /* Deal with midgame name changes */
\r
3104 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3105 if (gameInfo.white) free(gameInfo.white);
\r
3106 gameInfo.white = StrSave(white);
\r
3108 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3109 if (gameInfo.black) free(gameInfo.black);
\r
3110 gameInfo.black = StrSave(black);
\r
3114 /* Throw away game result if anything actually changes in examine mode */
\r
3115 if (gameMode == IcsExamining && !newGame) {
\r
3116 gameInfo.result = GameUnfinished;
\r
3117 if (gameInfo.resultDetails != NULL) {
\r
3118 free(gameInfo.resultDetails);
\r
3119 gameInfo.resultDetails = NULL;
\r
3123 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3124 in if they are in a different variation than we are. */
\r
3125 if (pauseExamInvalid) return;
\r
3126 if (pausing && gameMode == IcsExamining) {
\r
3127 if (moveNum <= pauseExamForwardMostMove) {
\r
3128 pauseExamInvalid = TRUE;
\r
3129 forwardMostMove = pauseExamForwardMostMove;
\r
3134 /* Parse the board */
\r
3135 for (k = 0; k < 8; k++)
\r
3136 for (j = 0; j < 8; j++)
\r
3137 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3138 CopyBoard(boards[moveNum], board);
\r
3139 if (moveNum == 0) {
\r
3140 startedFromSetupPosition =
\r
3141 !CompareBoards(board, initialPosition);
\r
3144 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3145 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3146 /* This was an initial position from a move list, not
\r
3147 the current position */
\r
3151 /* Update currentMove and known move number limits */
\r
3152 newMove = newGame || moveNum > forwardMostMove;
\r
3154 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3155 if (gameMode == IcsExamining && moveNum == 0) {
\r
3156 /* Workaround for ICS limitation: we are not told the wild
\r
3157 type when starting to examine a game. But if we ask for
\r
3158 the move list, the move list header will tell us */
\r
3159 ics_getting_history = H_REQUESTED;
\r
3160 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3163 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3164 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3165 forwardMostMove = moveNum;
\r
3166 if (!pausing || currentMove > forwardMostMove)
\r
3167 currentMove = forwardMostMove;
\r
3169 /* New part of history that is not contiguous with old part */
\r
3170 if (pausing && gameMode == IcsExamining) {
\r
3171 pauseExamInvalid = TRUE;
\r
3172 forwardMostMove = pauseExamForwardMostMove;
\r
3175 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3176 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3177 ics_getting_history = H_REQUESTED;
\r
3178 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3183 /* Update the clocks */
\r
3184 if (strchr(elapsed_time, '.')) {
\r
3185 /* Time is in ms */
\r
3186 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3187 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3189 /* Time is in seconds */
\r
3190 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3191 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3196 if (appData.zippyPlay && newGame &&
\r
3197 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3198 gameMode != IcsExamining)
\r
3199 ZippyFirstBoard(moveNum, basetime, increment);
\r
3202 /* Put the move on the move list, first converting
\r
3203 to canonical algebraic form. */
\r
3204 if (moveNum > 0) {
\r
3205 if (moveNum <= backwardMostMove) {
\r
3206 /* We don't know what the board looked like before
\r
3207 this move. Punt. */
\r
3208 strcpy(parseList[moveNum - 1], move_str);
\r
3209 strcat(parseList[moveNum - 1], " ");
\r
3210 strcat(parseList[moveNum - 1], elapsed_time);
\r
3211 moveList[moveNum - 1][0] = NULLCHAR;
\r
3212 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3213 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3214 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3215 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3216 fromY, fromX, toY, toX, promoChar,
\r
3217 parseList[moveNum-1]);
\r
3218 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3219 castlingRights[moveNum]) ) {
\r
3221 case MT_STALEMATE:
\r
3225 strcat(parseList[moveNum - 1], "+");
\r
3227 case MT_CHECKMATE:
\r
3228 strcat(parseList[moveNum - 1], "#");
\r
3231 strcat(parseList[moveNum - 1], " ");
\r
3232 strcat(parseList[moveNum - 1], elapsed_time);
\r
3233 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3234 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3235 strcat(moveList[moveNum - 1], "\n");
\r
3236 } else if (strcmp(move_str, "none") == 0) {
\r
3237 /* Again, we don't know what the board looked like;
\r
3238 this is really the start of the game. */
\r
3239 parseList[moveNum - 1][0] = NULLCHAR;
\r
3240 moveList[moveNum - 1][0] = NULLCHAR;
\r
3241 backwardMostMove = moveNum;
\r
3242 startedFromSetupPosition = TRUE;
\r
3243 fromX = fromY = toX = toY = -1;
\r
3245 /* Move from ICS was illegal!? Punt. */
\r
3247 if (appData.testLegality && appData.debugMode) {
\r
3248 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3249 DisplayError(str, 0);
\r
3252 strcpy(parseList[moveNum - 1], move_str);
\r
3253 strcat(parseList[moveNum - 1], " ");
\r
3254 strcat(parseList[moveNum - 1], elapsed_time);
\r
3255 moveList[moveNum - 1][0] = NULLCHAR;
\r
3256 fromX = fromY = toX = toY = -1;
\r
3260 /* Send move to chess program (BEFORE animating it). */
\r
3261 if (appData.zippyPlay && !newGame && newMove &&
\r
3262 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3264 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3265 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3266 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3267 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3269 DisplayError(str, 0);
\r
3271 if (first.sendTime) {
\r
3272 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3274 SendMoveToProgram(moveNum - 1, &first);
\r
3276 firstMove = FALSE;
\r
3277 if (first.useColors) {
\r
3278 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3280 "black\ngo\n", &first);
\r
3282 SendToProgram("go\n", &first);
\r
3284 first.maybeThinking = TRUE;
\r
3287 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3288 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3289 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3290 DisplayError(str, 0);
\r
3292 SendMoveToProgram(moveNum - 1, &first);
\r
3299 if (moveNum > 0 && !gotPremove) {
\r
3300 /* If move comes from a remote source, animate it. If it
\r
3301 isn't remote, it will have already been animated. */
\r
3302 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3303 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3305 if (!pausing && appData.highlightLastMove) {
\r
3306 SetHighlights(fromX, fromY, toX, toY);
\r
3310 /* Start the clocks */
\r
3311 whiteFlag = blackFlag = FALSE;
\r
3312 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3313 if (ticking == 0) {
\r
3314 ics_clock_paused = TRUE;
\r
3316 } else if (ticking == 1) {
\r
3317 ics_clock_paused = FALSE;
\r
3319 if (gameMode == IcsIdle ||
\r
3320 relation == RELATION_OBSERVING_STATIC ||
\r
3321 relation == RELATION_EXAMINING ||
\r
3323 DisplayBothClocks();
\r
3327 /* Display opponents and material strengths */
\r
3328 if (gameInfo.variant != VariantBughouse &&
\r
3329 gameInfo.variant != VariantCrazyhouse) {
\r
3330 if (tinyLayout || smallLayout) {
\r
3331 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3332 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3333 basetime, increment);
\r
3335 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3336 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3337 basetime, increment);
\r
3339 DisplayTitle(str);
\r
3343 /* Display the board */
\r
3346 if (appData.premove)
\r
3347 if (!gotPremove ||
\r
3348 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3349 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3350 ClearPremoveHighlights();
\r
3352 DrawPosition(FALSE, boards[currentMove]);
\r
3353 DisplayMove(moveNum - 1);
\r
3354 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3358 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3362 GetMoveListEvent()
\r
3364 char buf[MSG_SIZ];
\r
3365 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3366 ics_getting_history = H_REQUESTED;
\r
3367 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3373 AnalysisPeriodicEvent(force)
\r
3376 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3377 && !force) || !appData.periodicUpdates)
\r
3380 /* Send . command to Crafty to collect stats */
\r
3381 SendToProgram(".\n", &first);
\r
3383 /* Don't send another until we get a response (this makes
\r
3384 us stop sending to old Crafty's which don't understand
\r
3385 the "." command (sending illegal cmds resets node count & time,
\r
3386 which looks bad)) */
\r
3387 programStats.ok_to_send = 0;
\r
3391 SendMoveToProgram(moveNum, cps)
\r
3393 ChessProgramState *cps;
\r
3395 char buf[MSG_SIZ];
\r
3396 if (cps->useUsermove) {
\r
3397 SendToProgram("usermove ", cps);
\r
3399 if (cps->useSAN) {
\r
3401 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3402 int len = space - parseList[moveNum];
\r
3403 memcpy(buf, parseList[moveNum], len);
\r
3404 buf[len++] = '\n';
\r
3405 buf[len] = NULLCHAR;
\r
3407 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3409 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3410 if(BOARD_HEIGHT>8) {
\r
3412 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3414 SendToProgram(buf, cps);
\r
3416 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3417 * the engine. It would be nice to have a better way to identify castle
\r
3419 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3420 int fromX = moveList[moveNum][0] - AAA;
\r
3421 int fromY = moveList[moveNum][1] - ONE;
\r
3422 int toX = moveList[moveNum][2] - AAA;
\r
3423 int toY = moveList[moveNum][3] - ONE;
\r
3424 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3425 && boards[currentMove][toY][toX] == WhiteRook)
\r
3426 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3427 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3428 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3429 else SendToProgram("O-O-O\n", cps);
\r
3431 else SendToProgram(moveList[moveNum], cps);
\r
3433 else SendToProgram(moveList[moveNum], cps);
\r
3434 /* End of additions by Tord */
\r
3439 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3440 ChessMove moveType;
\r
3441 int fromX, fromY, toX, toY;
\r
3443 char user_move[MSG_SIZ];
\r
3445 switch (moveType) {
\r
3447 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3448 (int)moveType, fromX, fromY, toX, toY);
\r
3449 DisplayError(user_move + strlen("say "), 0);
\r
3451 case WhiteKingSideCastle:
\r
3452 case BlackKingSideCastle:
\r
3453 case WhiteQueenSideCastleWild:
\r
3454 case BlackQueenSideCastleWild:
\r
3456 case WhiteHSideCastleFR:
\r
3457 case BlackHSideCastleFR:
\r
3459 sprintf(user_move, "o-o\n");
\r
3461 case WhiteQueenSideCastle:
\r
3462 case BlackQueenSideCastle:
\r
3463 case WhiteKingSideCastleWild:
\r
3464 case BlackKingSideCastleWild:
\r
3466 case WhiteASideCastleFR:
\r
3467 case BlackASideCastleFR:
\r
3469 sprintf(user_move, "o-o-o\n");
\r
3471 case WhitePromotionQueen:
\r
3472 case BlackPromotionQueen:
\r
3473 case WhitePromotionRook:
\r
3474 case BlackPromotionRook:
\r
3475 case WhitePromotionBishop:
\r
3476 case BlackPromotionBishop:
\r
3477 case WhitePromotionKnight:
\r
3478 case BlackPromotionKnight:
\r
3479 case WhitePromotionKing:
\r
3480 case BlackPromotionKing:
\r
3482 case WhitePromotionChancellor:
\r
3483 case BlackPromotionChancellor:
\r
3484 case WhitePromotionArchbishop:
\r
3485 case BlackPromotionArchbishop:
\r
3487 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3488 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3489 PieceToChar(PromoPiece(moveType)));
\r
3493 sprintf(user_move, "%c@%c%c\n",
\r
3494 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3495 AAA + toX, ONE + toY);
\r
3498 case WhiteCapturesEnPassant:
\r
3499 case BlackCapturesEnPassant:
\r
3500 case IllegalMove: /* could be a variant we don't quite understand */
\r
3501 sprintf(user_move, "%c%c%c%c\n",
\r
3502 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3505 SendToICS(user_move);
\r
3509 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3510 int rf, ff, rt, ft;
\r
3514 if (rf == DROP_RANK) {
\r
3515 sprintf(move, "%c@%c%c\n",
\r
3516 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3518 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3519 sprintf(move, "%c%c%c%c\n",
\r
3520 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3522 sprintf(move, "%c%c%c%c%c\n",
\r
3523 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3526 AlphaRank(move, 4);
\r
3530 ProcessICSInitScript(f)
\r
3533 char buf[MSG_SIZ];
\r
3535 while (fgets(buf, MSG_SIZ, f)) {
\r
3536 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3543 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3545 AlphaRank(char *move, int n)
\r
3547 char *p = move, c;
\r
3549 if( !appData.alphaRank ) return;
\r
3552 if(c>='0' && c<='9') *p += 'a'-'0'; else
\r
3553 if(c>='a' && c<='z') *p -= 'a'-'0';
\r
3555 if(--n < 1) break;
\r
3559 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3561 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3564 ChessMove *moveType;
\r
3565 int *fromX, *fromY, *toX, *toY;
\r
3568 if (appData.debugMode) {
\r
3569 fprintf(debugFP, "move to parse: %s\n", move);
\r
3571 AlphaRank(move, 10);
\r
3572 *moveType = yylexstr(moveNum, move);
\r
3574 switch (*moveType) {
\r
3576 case WhitePromotionChancellor:
\r
3577 case BlackPromotionChancellor:
\r
3578 case WhitePromotionArchbishop:
\r
3579 case BlackPromotionArchbishop:
\r
3581 case WhitePromotionQueen:
\r
3582 case BlackPromotionQueen:
\r
3583 case WhitePromotionRook:
\r
3584 case BlackPromotionRook:
\r
3585 case WhitePromotionBishop:
\r
3586 case BlackPromotionBishop:
\r
3587 case WhitePromotionKnight:
\r
3588 case BlackPromotionKnight:
\r
3589 case WhitePromotionKing:
\r
3590 case BlackPromotionKing:
\r
3592 case WhiteCapturesEnPassant:
\r
3593 case BlackCapturesEnPassant:
\r
3594 case WhiteKingSideCastle:
\r
3595 case WhiteQueenSideCastle:
\r
3596 case BlackKingSideCastle:
\r
3597 case BlackQueenSideCastle:
\r
3598 case WhiteKingSideCastleWild:
\r
3599 case WhiteQueenSideCastleWild:
\r
3600 case BlackKingSideCastleWild:
\r
3601 case BlackQueenSideCastleWild:
\r
3602 /* Code added by Tord: */
\r
3603 case WhiteHSideCastleFR:
\r
3604 case WhiteASideCastleFR:
\r
3605 case BlackHSideCastleFR:
\r
3606 case BlackASideCastleFR:
\r
3607 /* End of code added by Tord */
\r
3608 case IllegalMove: /* bug or odd chess variant */
\r
3609 *fromX = currentMoveString[0] - AAA;
\r
3610 *fromY = currentMoveString[1] - ONE;
\r
3611 *toX = currentMoveString[2] - AAA;
\r
3612 *toY = currentMoveString[3] - ONE;
\r
3613 *promoChar = currentMoveString[4];
\r
3614 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3615 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3616 *fromX = *fromY = *toX = *toY = 0;
\r
3619 if (appData.testLegality) {
\r
3620 return (*moveType != IllegalMove);
\r
3622 return !(fromX == fromY && toX == toY);
\r
3627 *fromX = *moveType == WhiteDrop ?
\r
3628 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3629 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3630 *fromY = DROP_RANK;
\r
3631 *toX = currentMoveString[2] - AAA;
\r
3632 *toY = currentMoveString[3] - ONE;
\r
3633 *promoChar = NULLCHAR;
\r
3636 case AmbiguousMove:
\r
3637 case ImpossibleMove:
\r
3638 case (ChessMove) 0: /* end of file */
\r
3648 *fromX = *fromY = *toX = *toY = 0;
\r
3649 *promoChar = NULLCHAR;
\r
3654 /* [AS] FRC game initialization */
\r
3655 static int FindEmptySquare( Board board, int n )
\r
3660 while( board[0][i] != EmptySquare ) i++;
\r
3670 static void ShuffleFRC( Board board )
\r
3676 for( i=0; i<8; i++ ) {
\r
3677 board[0][i] = EmptySquare;
\r
3680 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3681 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
3682 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
3683 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
3684 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
3685 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3686 board[0][FindEmptySquare(board, 0)] = WhiteKing;
\r
3687 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3689 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3690 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3694 static unsigned char FRC_KnightTable[10] = {
\r
3695 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
3698 static void SetupFRC( Board board, int pos_index )
\r
3701 unsigned char knights;
\r
3703 /* Bring the position index into a safe range (just in case...) */
\r
3704 if( pos_index < 0 ) pos_index = 0;
\r
3708 /* Clear the board */
\r
3709 for( i=0; i<8; i++ ) {
\r
3710 board[0][i] = EmptySquare;
\r
3713 /* Place bishops and queen */
\r
3714 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
3717 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3720 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
3723 /* Place knigths */
\r
3724 knights = FRC_KnightTable[ pos_index ];
\r
3726 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
3727 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
3729 /* Place rooks and king */
\r
3730 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3731 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
\r
3732 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3734 /* Mirror piece placement for black */
\r
3735 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3736 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3741 InitPosition(redraw)
\r
3744 ChessSquare (* pieces)[BOARD_SIZE];
\r
3745 int i, j, pawnRow, overrule,
\r
3746 oldx = gameInfo.boardWidth,
\r
3747 oldy = gameInfo.boardHeight,
\r
3748 oldh = gameInfo.holdingsWidth;
\r
3750 currentMove = forwardMostMove = backwardMostMove = 0;
\r
3752 /* [AS] Initialize pv info list [HGM] and game status */
\r
3754 for( i=0; i<MAX_MOVES; i++ ) {
\r
3755 pvInfoList[i].depth = 0;
\r
3756 epStatus[i]=EP_NONE;
\r
3757 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
3760 /* [HGM] Build normal castling rights */
\r
3761 for( j=0; j<BOARD_SIZE; j++ ) initialRights[j] = -1;
\r
3762 nrCastlingRights = 6;
\r
3763 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
3764 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
3765 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
3766 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
3767 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
3768 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
3770 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
3771 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
3773 initialRulePlies = 0; /* 50-move counter start */
\r
3777 /* [HGM] logic here is completely changed. In stead of full positions */
\r
3778 /* the initialized data only consist of the two backranks. The switch */
\r
3779 /* selects which one we will use, which is than copied to the Board */
\r
3780 /* initialPosition, which for the rest is initialized by Pawns and */
\r
3781 /* empty squares. This initial position is then copied to boards[0], */
\r
3782 /* possibly after shuffling, so that it remains available. */
\r
3784 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
3785 gameInfo.boardWidth = 8;
\r
3786 gameInfo.boardHeight = 8;
\r
3787 gameInfo.holdingsSize = 0;
\r
3789 switch (gameInfo.variant) {
\r
3791 pieces = FIDEArray;
\r
3793 case VariantShatranj:
\r
3794 pieces = ShatranjArray;
\r
3795 nrCastlingRights = 0;
\r
3796 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3798 case VariantTwoKings:
\r
3799 pieces = twoKingsArray;
\r
3800 nrCastlingRights = 8; /* add rights for second King */
\r
3801 castlingRights[0][6] = initialRights[2] = 5;
\r
3802 castlingRights[0][7] = initialRights[5] = 5;
\r
3803 castlingRank[6] = 0;
\r
3804 castlingRank[6] = BOARD_HEIGHT-1;
\r
3805 startedFromSetupPosition = TRUE;
\r
3807 case VariantCapablanca:
\r
3808 pieces = CapablancaArray;
\r
3809 gameInfo.boardWidth = 10;
\r
3811 case VariantGothic:
\r
3812 pieces = GothicArray;
\r
3813 gameInfo.boardWidth = 10;
\r
3815 case VariantXiangqi:
\r
3816 pieces = XiangqiArray;
\r
3817 gameInfo.boardWidth = 9;
\r
3818 gameInfo.boardHeight = 10;
\r
3819 nrCastlingRights = 0;
\r
3820 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3821 strcpy(pieceToChar, "PN.R.MKE...C....pn.r.mke...c....");
\r
3823 case VariantShogi:
\r
3824 pieces = ShogiArray;
\r
3825 gameInfo.boardWidth = 9;
\r
3826 gameInfo.boardHeight = 9;
\r
3827 gameInfo.holdingsSize = 7;
\r
3828 nrCastlingRights = 0;
\r
3829 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3830 strcpy(pieceToChar, "PNBRLSGPNBRLS..Kpnbrlsgpnbrls..k");
\r
3832 case VariantShowgi:
\r
3833 pieces = ShogiArray;
\r
3834 gameInfo.boardWidth = 9;
\r
3835 gameInfo.boardHeight = 9;
\r
3836 gameInfo.holdingsSize = 7;
\r
3837 nrCastlingRights = 0;
\r
3838 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3839 strcpy(pieceToChar, "PNBRQFWEHACGOUMKpnbrlsgpnbrls..k");
\r
3841 case VariantCourier:
\r
3842 pieces = CourierArray;
\r
3843 gameInfo.boardWidth = 12;
\r
3844 nrCastlingRights = 0;
\r
3845 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3847 case VariantKnightmate:
\r
3848 pieces = KnightmateArray;
\r
3849 strcpy(pieceToChar, "PNBRQFWEHACGOMK.pnbrqfwehacgomK.");
\r
3851 case VariantFairy:
\r
3852 pieces = fairyArray;
\r
3853 strcpy(pieceToChar, "PNBRQFWEHACGOMUKpnbrqfwehacgomuk");
\r
3854 startedFromSetupPosition = TRUE;
\r
3856 case VariantCrazyhouse:
\r
3857 case VariantBughouse:
\r
3858 pieces = FIDEArray;
\r
3859 gameInfo.holdingsSize = 5;
\r
3860 strcpy(pieceToChar, "PNBRQ...NBRQ...Kpnbrq...nbrq...k");
\r
3862 case VariantWildCastle:
\r
3863 pieces = FIDEArray;
\r
3864 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
3866 case VariantNoCastle:
\r
3867 pieces = FIDEArray;
\r
3868 nrCastlingRights = 0;
\r
3869 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3870 /* !!?unconstrained back-rank shuffle */
\r
3875 if(appData.NrFiles >= 0) {
\r
3876 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
3877 gameInfo.boardWidth = appData.NrFiles;
\r
3879 if(appData.NrRanks >= 0) {
\r
3880 if(gameInfo.boardHeight != appData.NrRanks) overrule++;
\r
3881 gameInfo.boardHeight = appData.NrRanks;
\r
3883 if(appData.holdingsSize >= 0) {
\r
3884 i = appData.holdingsSize;
\r
3885 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
3886 gameInfo.holdingsSize = i;
\r
3888 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
3889 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
3890 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
3892 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all variants */
\r
3894 /* User pieceToChar list overrules defaults */
\r
3895 if(appData.pieceToCharTable != NULL)
\r
3896 strcpy(pieceToChar, appData.pieceToCharTable);
\r
3898 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
3900 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
3901 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
3902 for( i=0; i<BOARD_HEIGHT; i++ )
\r
3903 initialPosition[i][j] = s;
\r
3905 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
3906 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
3907 initialPosition[pawnRow][j] = WhitePawn;
\r
3908 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
3909 if(gameInfo.variant == VariantXiangqi) {
\r
3911 initialPosition[pawnRow][j] =
\r
3912 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
3913 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
3914 initialPosition[2][j] = WhiteCannon;
\r
3915 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
3919 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
3921 if( (gameInfo.variant == VariantShogi
\r
3922 ||gameInfo.variant == VariantShowgi
\r
3923 ) && !overrule ) {
\r
3925 initialPosition[1][j] = WhiteBishop;
\r
3926 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
3928 initialPosition[1][j] = WhiteRook;
\r
3929 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
3932 if(gameInfo.variant == VariantFischeRandom) {
\r
3933 if( appData.defaultFrcPosition < 0 ) {
\r
3934 ShuffleFRC( initialPosition );
\r
3937 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
3941 CopyBoard(boards[0], initialPosition);
\r
3943 if(oldx != gameInfo.boardWidth ||
\r
3944 oldy != gameInfo.boardHeight ||
\r
3945 oldh != gameInfo.holdingsWidth )
\r
3946 InitDrawingSizes(-1 ,0);
\r
3949 DrawPosition(TRUE, boards[currentMove]);
\r
3953 SendBoard(cps, moveNum)
\r
3954 ChessProgramState *cps;
\r
3957 char message[MSG_SIZ];
\r
3959 if (cps->useSetboard) {
\r
3960 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
3961 sprintf(message, "setboard %s\n", fen);
\r
3962 SendToProgram(message, cps);
\r
3968 /* Kludge to set black to move, avoiding the troublesome and now
\r
3969 * deprecated "black" command.
\r
3971 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
3973 SendToProgram("edit\n", cps);
\r
3974 SendToProgram("#\n", cps);
\r
3975 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
3976 bp = &boards[moveNum][i][0];
\r
3977 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
3978 if ((int) *bp < (int) BlackPawn) {
\r
3979 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
3980 AAA + j, ONE + i);
\r
3981 SendToProgram(message, cps);
\r
3986 SendToProgram("c\n", cps);
\r
3987 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
3988 bp = &boards[moveNum][i][0];
\r
3989 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
3990 if (((int) *bp != (int) EmptySquare)
\r
3991 && ((int) *bp >= (int) BlackPawn)) {
\r
3992 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
3993 AAA + j, ONE + i);
\r
3994 SendToProgram(message, cps);
\r
3999 SendToProgram(".\n", cps);
\r
4004 IsPromotion(fromX, fromY, toX, toY)
\r
4005 int fromX, fromY, toX, toY;
\r
4007 /* [HGM] add Shogi promotions */
\r
4008 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4009 ChessSquare piece;
\r
4011 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4012 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4013 /* [HGM] Note to self: line above also weeds out drops */
\r
4014 piece = boards[currentMove][fromY][fromX];
\r
4015 if(gameInfo.variant == VariantShogi) {
\r
4016 promotionZoneSize = 3;
\r
4017 highestPromotingPiece = (int)WhiteFerz; /* Silver */
\r
4019 if((int)piece >= BlackPawn) {
\r
4020 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4022 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4024 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4025 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4027 return ( (int)piece <= highestPromotingPiece );
\r
4031 InPalace(row, column)
\r
4033 { /* [HGM] for Xiangqi */
\r
4034 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4035 column < (BOARD_WIDTH + 4)/2 &&
\r
4036 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4041 PieceForSquare (x, y)
\r
4045 if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)
\r
4048 return boards[currentMove][y][x];
\r
4052 OKToStartUserMove(x, y)
\r
4055 ChessSquare from_piece;
\r
4058 if (matchMode) return FALSE;
\r
4059 if (gameMode == EditPosition) return TRUE;
\r
4061 if (x >= 0 && y >= 0)
\r
4062 from_piece = boards[currentMove][y][x];
\r
4064 from_piece = EmptySquare;
\r
4066 if (from_piece == EmptySquare) return FALSE;
\r
4068 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4069 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4071 switch (gameMode) {
\r
4072 case PlayFromGameFile:
\r
4074 case TwoMachinesPlay:
\r
4078 case IcsObserving:
\r
4082 case MachinePlaysWhite:
\r
4083 case IcsPlayingBlack:
\r
4084 if (appData.zippyPlay) return FALSE;
\r
4085 if (white_piece) {
\r
4086 DisplayMoveError("You are playing Black");
\r
4091 case MachinePlaysBlack:
\r
4092 case IcsPlayingWhite:
\r
4093 if (appData.zippyPlay) return FALSE;
\r
4094 if (!white_piece) {
\r
4095 DisplayMoveError("You are playing White");
\r
4101 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4102 DisplayMoveError("It is White's turn");
\r
4105 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4106 DisplayMoveError("It is Black's turn");
\r
4109 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4110 /* Editing correspondence game history */
\r
4111 /* Could disallow this or prompt for confirmation */
\r
4112 cmailOldMove = -1;
\r
4114 if (currentMove < forwardMostMove) {
\r
4115 /* Discarding moves */
\r
4116 /* Could prompt for confirmation here,
\r
4117 but I don't think that's such a good idea */
\r
4118 forwardMostMove = currentMove;
\r
4122 case BeginningOfGame:
\r
4123 if (appData.icsActive) return FALSE;
\r
4124 if (!appData.noChessProgram) {
\r
4125 if (!white_piece) {
\r
4126 DisplayMoveError("You are playing White");
\r
4133 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4134 DisplayMoveError("It is White's turn");
\r
4137 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4138 DisplayMoveError("It is Black's turn");
\r
4144 case IcsExamining:
\r
4147 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4148 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4149 DisplayMoveError("Displayed position is not current");
\r
4155 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4156 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4157 int lastLoadGameUseList = FALSE;
\r
4158 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4159 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4163 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4164 int fromX, fromY, toX, toY;
\r
4167 ChessMove moveType;
\r
4169 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4170 if ((fromX == toX) && (fromY == toY)) {
\r
4171 return ImpossibleMove;
\r
4173 /* [HGM] suppress all moves into holdings area and guard band */
\r
4174 if( toX < BOARD_LEFT || toX >= BOARD_RGHT ) return ImpossibleMove;
\r
4176 /* Check if the user is playing in turn. This is complicated because we
\r
4177 let the user "pick up" a piece before it is his turn. So the piece he
\r
4178 tried to pick up may have been captured by the time he puts it down!
\r
4179 Therefore we use the color the user is supposed to be playing in this
\r
4180 test, not the color of the piece that is currently on the starting
\r
4181 square---except in EditGame mode, where the user is playing both
\r
4182 sides; fortunately there the capture race can't happen. (It can
\r
4183 now happen in IcsExamining mode, but that's just too bad. The user
\r
4184 will get a somewhat confusing message in that case.)
\r
4187 switch (gameMode) {
\r
4188 case PlayFromGameFile:
\r
4190 case TwoMachinesPlay:
\r
4192 case IcsObserving:
\r
4194 /* We switched into a game mode where moves are not accepted,
\r
4195 perhaps while the mouse button was down. */
\r
4196 return ImpossibleMove;
\r
4198 case MachinePlaysWhite:
\r
4199 /* User is moving for Black */
\r
4200 if (WhiteOnMove(currentMove)) {
\r
4201 DisplayMoveError("It is White's turn");
\r
4202 return ImpossibleMove;
\r
4206 case MachinePlaysBlack:
\r
4207 /* User is moving for White */
\r
4208 if (!WhiteOnMove(currentMove)) {
\r
4209 DisplayMoveError("It is Black's turn");
\r
4210 return ImpossibleMove;
\r
4215 case IcsExamining:
\r
4216 case BeginningOfGame:
\r
4219 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4220 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4221 /* User is moving for Black */
\r
4222 if (WhiteOnMove(currentMove)) {
\r
4223 DisplayMoveError("It is White's turn");
\r
4224 return ImpossibleMove;
\r
4227 /* User is moving for White */
\r
4228 if (!WhiteOnMove(currentMove)) {
\r
4229 DisplayMoveError("It is Black's turn");
\r
4230 return ImpossibleMove;
\r
4235 case IcsPlayingBlack:
\r
4236 /* User is moving for Black */
\r
4237 if (WhiteOnMove(currentMove)) {
\r
4238 if (!appData.premove) {
\r
4239 DisplayMoveError("It is White's turn");
\r
4240 } else if (toX >= 0 && toY >= 0) {
\r
4243 premoveFromX = fromX;
\r
4244 premoveFromY = fromY;
\r
4245 premovePromoChar = promoChar;
\r
4247 if (appData.debugMode)
\r
4248 fprintf(debugFP, "Got premove: fromX %d,"
\r
4249 "fromY %d, toX %d, toY %d\n",
\r
4250 fromX, fromY, toX, toY);
\r
4252 return ImpossibleMove;
\r
4256 case IcsPlayingWhite:
\r
4257 /* User is moving for White */
\r
4258 if (!WhiteOnMove(currentMove)) {
\r
4259 if (!appData.premove) {
\r
4260 DisplayMoveError("It is Black's turn");
\r
4261 } else if (toX >= 0 && toY >= 0) {
\r
4264 premoveFromX = fromX;
\r
4265 premoveFromY = fromY;
\r
4266 premovePromoChar = promoChar;
\r
4268 if (appData.debugMode)
\r
4269 fprintf(debugFP, "Got premove: fromX %d,"
\r
4270 "fromY %d, toX %d, toY %d\n",
\r
4271 fromX, fromY, toX, toY);
\r
4273 return ImpossibleMove;
\r
4280 case EditPosition:
\r
4281 if (toX == -2 || toY == -2) {
\r
4282 boards[0][fromY][fromX] = EmptySquare;
\r
4283 DrawPosition(FALSE, boards[currentMove]);
\r
4284 } else if (toX >= 0 && toY >= 0) {
\r
4285 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4286 boards[0][fromY][fromX] = EmptySquare;
\r
4287 DrawPosition(FALSE, boards[currentMove]);
\r
4289 return ImpossibleMove;
\r
4292 if (toX < 0 || toY < 0) return ImpossibleMove;
\r
4294 /* [HGM] If move started in holdings, it means a drop */
\r
4295 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4296 if( boards[currentMove][toY][toX] != EmptySquare ) return ImpossibleMove;
\r
4297 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4300 userOfferedDraw = FALSE;
\r
4302 if (appData.testLegality) {
\r
4303 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4304 EP_UNKNOWN, castlingRights[currentMove],
\r
4305 fromY, fromX, toY, toX, promoChar);
\r
4306 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4307 DisplayMoveError("Illegal move");
\r
4308 return ImpossibleMove;
\r
4311 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4315 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4316 function is made into one that returns an OK move type if FinishMove
\r
4317 should be called. This to give the calling driver routine the
\r
4318 opportunity to finish the userMove input with a promotion popup,
\r
4319 without bothering the user with this for invalid or illegal moves */
\r
4321 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4324 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4327 ChessMove moveType;
\r
4328 int fromX, fromY, toX, toY;
\r
4329 /*char*/int promoChar;
\r
4331 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4332 move type in caller when we know the move is a legal promotion */
\r
4333 if(moveType == NormalMove)
\r
4334 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4336 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4337 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4338 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4339 fromX = boards[currentMove][fromY][fromX];
\r
4340 fromY = DROP_RANK;
\r
4343 /* [HGM] <popupFix> The following if has been moved here from
\r
4344 UserMoveEnevt(). Because it seemed to belon here (why not allow
\r
4345 piece drops in training games?), and because it can only be
\r
4346 performed after it is known to what we promote. */
\r
4347 if (gameMode == Training) {
\r
4348 /* compare the move played on the board to the next move in the
\r
4349 * game. If they match, display the move and the opponent's response.
\r
4350 * If they don't match, display an error message.
\r
4354 CopyBoard(testBoard, boards[currentMove]);
\r
4355 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4357 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4358 ForwardInner(currentMove+1);
\r
4360 /* Autoplay the opponent's response.
\r
4361 * if appData.animate was TRUE when Training mode was entered,
\r
4362 * the response will be animated.
\r
4364 saveAnimate = appData.animate;
\r
4365 appData.animate = animateTraining;
\r
4366 ForwardInner(currentMove+1);
\r
4367 appData.animate = saveAnimate;
\r
4369 /* check for the end of the game */
\r
4370 if (currentMove >= forwardMostMove) {
\r
4371 gameMode = PlayFromGameFile;
\r
4373 SetTrainingModeOff();
\r
4374 DisplayInformation("End of game");
\r
4377 DisplayError("Incorrect move", 0);
\r
4382 /* Ok, now we know that the move is good, so we can kill
\r
4383 the previous line in Analysis Mode */
\r
4384 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4385 forwardMostMove = currentMove;
\r
4388 /* If we need the chess program but it's dead, restart it */
\r
4389 ResurrectChessProgram();
\r
4391 /* A user move restarts a paused game*/
\r
4395 thinkOutput[0] = NULLCHAR;
\r
4397 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4399 if (gameMode == BeginningOfGame) {
\r
4400 if (appData.noChessProgram) {
\r
4401 gameMode = EditGame;
\r
4404 char buf[MSG_SIZ];
\r
4405 gameMode = MachinePlaysBlack;
\r
4407 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4408 DisplayTitle(buf);
\r
4409 if (first.sendName) {
\r
4410 sprintf(buf, "name %s\n", gameInfo.white);
\r
4411 SendToProgram(buf, &first);
\r
4417 /* Relay move to ICS or chess engine */
\r
4418 if (appData.icsActive) {
\r
4419 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4420 gameMode == IcsExamining) {
\r
4421 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4422 ics_user_moved = 1;
\r
4425 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4426 gameMode == MachinePlaysWhite ||
\r
4427 gameMode == MachinePlaysBlack)) {
\r
4428 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4430 SendMoveToProgram(forwardMostMove-1, &first);
\r
4431 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4432 first.maybeThinking = TRUE;
\r
4434 if (currentMove == cmailOldMove + 1) {
\r
4435 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4439 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4441 switch (gameMode) {
\r
4443 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4444 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4448 case MT_CHECKMATE:
\r
4449 if (WhiteOnMove(currentMove)) {
\r
4450 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4452 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4455 case MT_STALEMATE:
\r
4456 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4461 case MachinePlaysBlack:
\r
4462 case MachinePlaysWhite:
\r
4463 /* disable certain menu options while machine is thinking */
\r
4464 SetMachineThinkingEnables();
\r
4473 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
4474 int fromX, fromY, toX, toY;
\r
4477 /* [HGM] This routine was added to allow calling of its two logical
\r
4478 parts from other modules in the old way. Before, UserMoveEvent()
\r
4479 automatically called FinishMove() if the move was OK, and returned
\r
4480 otherwise. I separated the two, in order to make it possible to
\r
4481 slip a promotion popup in between. But that it always needs two
\r
4482 calls, to the first part, (now called UserMoveTest() ), and to
\r
4483 FinishMove if the first part succeeded. Calls that do not need
\r
4484 to do anything in between, can call this routine the old way.
\r
4486 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
4488 if(moveType != ImpossibleMove)
\r
4489 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4492 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4494 char * hint = lastHint;
\r
4495 FrontEndProgramStats stats;
\r
4497 stats.which = cps == &first ? 0 : 1;
\r
4498 stats.depth = cpstats->depth;
\r
4499 stats.nodes = cpstats->nodes;
\r
4500 stats.score = cpstats->score;
\r
4501 stats.time = cpstats->time;
\r
4502 stats.pv = cpstats->movelist;
\r
4503 stats.hint = lastHint;
\r
4504 stats.an_move_index = 0;
\r
4505 stats.an_move_count = 0;
\r
4507 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4508 stats.hint = cpstats->move_name;
\r
4509 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4510 stats.an_move_count = cpstats->nr_moves;
\r
4513 SetProgramStats( &stats );
\r
4517 HandleMachineMove(message, cps)
\r
4519 ChessProgramState *cps;
\r
4521 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4522 char realname[MSG_SIZ];
\r
4523 int fromX, fromY, toX, toY;
\r
4524 ChessMove moveType;
\r
4530 * Kludge to ignore BEL characters
\r
4532 while (*message == '\007') message++;
\r
4535 * Look for book output
\r
4537 if (cps == &first && bookRequested) {
\r
4538 if (message[0] == '\t' || message[0] == ' ') {
\r
4539 /* Part of the book output is here; append it */
\r
4540 strcat(bookOutput, message);
\r
4541 strcat(bookOutput, " \n");
\r
4543 } else if (bookOutput[0] != NULLCHAR) {
\r
4544 /* All of book output has arrived; display it */
\r
4545 char *p = bookOutput;
\r
4546 while (*p != NULLCHAR) {
\r
4547 if (*p == '\t') *p = ' ';
\r
4550 DisplayInformation(bookOutput);
\r
4551 bookRequested = FALSE;
\r
4552 /* Fall through to parse the current output */
\r
4557 * Look for machine move.
\r
4559 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4560 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4562 /* This method is only useful on engines that support ping */
\r
4563 if (cps->lastPing != cps->lastPong) {
\r
4564 if (gameMode == BeginningOfGame) {
\r
4565 /* Extra move from before last new; ignore */
\r
4566 if (appData.debugMode) {
\r
4567 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4570 if (appData.debugMode) {
\r
4571 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4572 cps->which, gameMode);
\r
4575 SendToProgram("undo\n", cps);
\r
4580 switch (gameMode) {
\r
4581 case BeginningOfGame:
\r
4582 /* Extra move from before last reset; ignore */
\r
4583 if (appData.debugMode) {
\r
4584 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4591 /* Extra move after we tried to stop. The mode test is
\r
4592 not a reliable way of detecting this problem, but it's
\r
4593 the best we can do on engines that don't support ping.
\r
4595 if (appData.debugMode) {
\r
4596 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4597 cps->which, gameMode);
\r
4599 SendToProgram("undo\n", cps);
\r
4602 case MachinePlaysWhite:
\r
4603 case IcsPlayingWhite:
\r
4604 machineWhite = TRUE;
\r
4607 case MachinePlaysBlack:
\r
4608 case IcsPlayingBlack:
\r
4609 machineWhite = FALSE;
\r
4612 case TwoMachinesPlay:
\r
4613 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
4616 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
4617 if (appData.debugMode) {
\r
4619 "Ignoring move out of turn by %s, gameMode %d"
\r
4620 ", forwardMost %d\n",
\r
4621 cps->which, gameMode, forwardMostMove);
\r
4626 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
4627 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4628 /* Machine move could not be parsed; ignore it. */
\r
4629 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
4630 machineMove, cps->which);
\r
4631 DisplayError(buf1, 0);
\r
4632 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
4633 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4634 if (gameMode == TwoMachinesPlay) {
\r
4635 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4641 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
4642 /* So we have to redo legality test with true e.p. status here, */
\r
4643 /* to make sure an illegal e.p. capture does not slip through, */
\r
4644 /* to cause a forfeit on a justified illegal-move complaint */
\r
4645 /* of the opponent. */
\r
4646 if(gameMode==TwoMachinesPlay && appData.testLegality &&
\r
4647 fromY != DROP_RANK && /* [HGM] temporary; should still add legality test for drops */
\r
4648 LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
4649 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
4650 fromY, fromX, toY, toX, promoChar) == IllegalMove)
\r
4652 if (appData.debugMode) {
\r
4654 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
4655 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
4656 fprintf(debugFP, "castling rights\n");
\r
4658 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
4659 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4660 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4663 hintRequested = FALSE;
\r
4664 lastHint[0] = NULLCHAR;
\r
4665 bookRequested = FALSE;
\r
4666 /* Program may be pondering now */
\r
4667 cps->maybeThinking = TRUE;
\r
4668 if (cps->sendTime == 2) cps->sendTime = 1;
\r
4669 if (cps->offeredDraw) cps->offeredDraw--;
\r
4672 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
4674 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4675 ics_user_moved = 1;
\r
4678 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
4679 strcpy(machineMove, currentMoveString);
\r
4680 strcat(machineMove, "\n");
\r
4681 strcpy(moveList[forwardMostMove], machineMove);
\r
4683 /* [AS] Save move info and clear stats for next move */
\r
4684 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
4685 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
4686 pvInfoList[ forwardMostMove ].time = -1;
\r
4687 ClearProgramStats();
\r
4688 thinkOutput[0] = NULLCHAR;
\r
4689 hiddenThinkOutputState = 0;
\r
4691 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
4693 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
4694 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
4697 while( count < adjudicateLossPlies ) {
\r
4698 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
4701 score = -score; /* Flip score for winning side */
\r
4704 if( score > adjudicateLossThreshold ) {
\r
4711 if( count >= adjudicateLossPlies ) {
\r
4712 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4714 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
4715 "Xboard adjudication",
\r
4722 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
4724 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
4725 int count = 0, epFile = epStatus[forwardMostMove];
\r
4727 if(appData.testLegality && appData.checkMates)
\r
4728 // don't wait for engine to announce game end if we can judge ourselves
\r
4729 switch (MateTest(boards[forwardMostMove],
\r
4730 PosFlags(forwardMostMove), epFile,
\r
4731 castlingRights[forwardMostMove]) ) {
\r
4736 case MT_STALEMATE:
\r
4737 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4738 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
4741 case MT_CHECKMATE:
\r
4742 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4743 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
4744 "Xboard adjudication: Checkmate",
\r
4749 if( appData.testLegality )
\r
4750 { /* [HGM] Some more adjudications for obstinate engines */
\r
4751 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
4753 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
4754 static int moveCount;
\r
4756 /* First absolutely insufficient mating material. Count what is on board. */
\r
4757 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
4758 { ChessSquare p = boards[forwardMostMove][i][j];
\r
4762 { /* count B,N,R and other of each side */
\r
4779 case EmptySquare:
\r
4784 PawnAdvance += m; NrPawns++;
\r
4786 NrPieces += (p != EmptySquare);
\r
4789 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )
\r
4790 { /* KBK, KNK or KK */
\r
4792 /* always flag draws, for judging claims */
\r
4793 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
4795 if(appData.materialDraws) {
\r
4796 /* but only adjudicate them if adjudication enabled */
\r
4797 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4798 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
4803 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
4804 if(NrPieces == 4 &&
\r
4805 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
4806 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
4807 || NrWN==2 || NrBN==2 /* KNNK */
\r
4808 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
4810 if(--moveCount < 0 && appData.trivialDraws)
\r
4811 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
4812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4813 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
4816 } else moveCount = 6;
\r
4818 if (appData.debugMode) { int i;
\r
4819 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
4820 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
4821 appData.drawRepeats);
\r
4822 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
4823 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
4826 /* Check for rep-draws */
\r
4828 for(k = forwardMostMove-2;
\r
4829 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
4830 epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
4833 if (appData.debugMode) {
\r
4834 fprintf(debugFP, " loop\n");
\r
4836 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
4837 if (appData.debugMode) {
\r
4838 fprintf(debugFP, "match\n");
\r
4840 /* compare castling rights */
\r
4841 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
4842 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
4843 rights++; /* King lost rights, while rook still had them */
\r
4844 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
4845 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
4846 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
4847 rights++; /* but at least one rook lost them */
\r
4849 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
4850 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
4852 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
4853 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
4854 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
4857 if (appData.debugMode) {
\r
4858 for(i=0; i<nrCastlingRights; i++)
\r
4859 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
4862 if (appData.debugMode) {
\r
4863 fprintf(debugFP, " %d %d\n", rights, k);
\r
4865 if( rights == 0 && ++count > appData.drawRepeats-2
\r
4866 && appData.drawRepeats > 1) {
\r
4867 /* adjudicate after user-specified nr of repeats */
\r
4868 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4869 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
4872 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
4873 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
4877 /* Now we test for 50-move draws. Determine ply count */
\r
4878 count = forwardMostMove;
\r
4879 /* look for last irreversble move */
\r
4880 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
4882 /* if we hit starting position, add initial plies */
\r
4883 if( count == backwardMostMove )
\r
4884 count -= initialRulePlies;
\r
4885 count = forwardMostMove - count;
\r
4887 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
4888 /* this is used to judge if draw claims are legal */
\r
4889 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
4890 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4891 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
4899 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
4900 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4902 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
4907 if (gameMode == TwoMachinesPlay) {
\r
4908 if (cps->other->sendTime) {
\r
4909 SendTimeRemaining(cps->other,
\r
4910 cps->other->twoMachinesColor[0] == 'w');
\r
4912 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
4914 firstMove = FALSE;
\r
4915 if (cps->other->useColors) {
\r
4916 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
4918 SendToProgram("go\n", cps->other);
\r
4920 cps->other->maybeThinking = TRUE;
\r
4923 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4925 if (!pausing && appData.ringBellAfterMoves) {
\r
4930 * Reenable menu items that were disabled while
\r
4931 * machine was thinking
\r
4933 if (gameMode != TwoMachinesPlay)
\r
4934 SetUserThinkingEnables();
\r
4939 /* Set special modes for chess engines. Later something general
\r
4940 * could be added here; for now there is just one kludge feature,
\r
4941 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
4942 * when "xboard" is given as an interactive command.
\r
4944 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
4945 cps->useSigint = FALSE;
\r
4946 cps->useSigterm = FALSE;
\r
4950 * Look for communication commands
\r
4952 if (!strncmp(message, "telluser ", 9)) {
\r
4953 DisplayNote(message + 9);
\r
4956 if (!strncmp(message, "tellusererror ", 14)) {
\r
4957 DisplayError(message + 14, 0);
\r
4960 if (!strncmp(message, "tellopponent ", 13)) {
\r
4961 if (appData.icsActive) {
\r
4963 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
4967 DisplayNote(message + 13);
\r
4971 if (!strncmp(message, "tellothers ", 11)) {
\r
4972 if (appData.icsActive) {
\r
4974 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
4980 if (!strncmp(message, "tellall ", 8)) {
\r
4981 if (appData.icsActive) {
\r
4983 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
4987 DisplayNote(message + 8);
\r
4991 if (strncmp(message, "warning", 7) == 0) {
\r
4992 /* Undocumented feature, use tellusererror in new code */
\r
4993 DisplayError(message, 0);
\r
4996 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
4997 strcpy(realname, cps->tidy);
\r
4998 strcat(realname, " query");
\r
4999 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5002 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5003 * sent until we are logged on. Crafty kibitzes have been known to
\r
5004 * interfere with the login process.
\r
5007 if (!strncmp(message, "tellics ", 8)) {
\r
5008 SendToICS(message + 8);
\r
5012 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5013 SendToICS(ics_prefix);
\r
5014 SendToICS(message + 15);
\r
5018 /* The following are for backward compatibility only */
\r
5019 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5020 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5021 SendToICS(ics_prefix);
\r
5022 SendToICS(message);
\r
5027 if (strncmp(message, "feature ", 8) == 0) {
\r
5028 ParseFeatures(message+8, cps);
\r
5030 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5034 * If the move is illegal, cancel it and redraw the board.
\r
5035 * Also deal with other error cases. Matching is rather loose
\r
5036 * here to accommodate engines written before the spec.
\r
5038 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5039 strncmp(message, "Error", 5) == 0) {
\r
5040 if (StrStr(message, "name") ||
\r
5041 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5042 StrStr(message, "result") || StrStr(message, "board") ||
\r
5043 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5044 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5045 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5046 StrStr(message, "accepted")) {
\r
5049 if (StrStr(message, "protover")) {
\r
5050 /* Program is responding to input, so it's apparently done
\r
5051 initializing, and this error message indicates it is
\r
5052 protocol version 1. So we don't need to wait any longer
\r
5053 for it to initialize and send feature commands. */
\r
5054 FeatureDone(cps, 1);
\r
5055 cps->protocolVersion = 1;
\r
5058 cps->maybeThinking = FALSE;
\r
5060 if (StrStr(message, "draw")) {
\r
5061 /* Program doesn't have "draw" command */
\r
5062 cps->sendDrawOffers = 0;
\r
5065 if (cps->sendTime != 1 &&
\r
5066 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5067 /* Program apparently doesn't have "time" or "otim" command */
\r
5068 cps->sendTime = 0;
\r
5071 if (StrStr(message, "analyze")) {
\r
5072 cps->analysisSupport = FALSE;
\r
5073 cps->analyzing = FALSE;
\r
5074 Reset(FALSE, TRUE);
\r
5075 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5076 DisplayError(buf2, 0);
\r
5079 if (StrStr(message, "(no matching move)st")) {
\r
5080 /* Special kludge for GNU Chess 4 only */
\r
5081 cps->stKludge = TRUE;
\r
5082 SendTimeControl(cps, movesPerSession, timeControl,
\r
5083 timeIncrement, appData.searchDepth,
\r
5087 if (StrStr(message, "(no matching move)sd")) {
\r
5088 /* Special kludge for GNU Chess 4 only */
\r
5089 cps->sdKludge = TRUE;
\r
5090 SendTimeControl(cps, movesPerSession, timeControl,
\r
5091 timeIncrement, appData.searchDepth,
\r
5095 if (!StrStr(message, "llegal")) {
\r
5098 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5099 gameMode == IcsIdle) return;
\r
5100 if (forwardMostMove <= backwardMostMove) return;
\r
5102 /* Following removed: it caused a bug where a real illegal move
\r
5103 message in analyze mored would be ignored. */
\r
5104 if (cps == &first && programStats.ok_to_send == 0) {
\r
5105 /* Bogus message from Crafty responding to "." This filtering
\r
5106 can miss some of the bad messages, but fortunately the bug
\r
5107 is fixed in current Crafty versions, so it doesn't matter. */
\r
5111 if (pausing) PauseEvent();
\r
5112 if (gameMode == PlayFromGameFile) {
\r
5113 /* Stop reading this game file */
\r
5114 gameMode = EditGame;
\r
5117 currentMove = --forwardMostMove;
\r
5118 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5120 DisplayBothClocks();
\r
5121 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5122 parseList[currentMove], cps->which);
\r
5123 DisplayMoveError(buf1);
\r
5124 DrawPosition(FALSE, boards[currentMove]);
\r
5126 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5127 /* only passes fully legal moves */
\r
5128 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5129 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5130 "False illegal-move claim", GE_XBOARD );
\r
5134 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5135 /* Program has a broken "time" command that
\r
5136 outputs a string not ending in newline.
\r
5138 cps->sendTime = 0;
\r
5142 * If chess program startup fails, exit with an error message.
\r
5143 * Attempts to recover here are futile.
\r
5145 if ((StrStr(message, "unknown host") != NULL)
\r
5146 || (StrStr(message, "No remote directory") != NULL)
\r
5147 || (StrStr(message, "not found") != NULL)
\r
5148 || (StrStr(message, "No such file") != NULL)
\r
5149 || (StrStr(message, "can't alloc") != NULL)
\r
5150 || (StrStr(message, "Permission denied") != NULL)) {
\r
5152 cps->maybeThinking = FALSE;
\r
5153 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5154 cps->which, cps->program, cps->host, message);
\r
5155 RemoveInputSource(cps->isr);
\r
5156 DisplayFatalError(buf1, 0, 1);
\r
5161 * Look for hint output
\r
5163 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5164 if (cps == &first && hintRequested) {
\r
5165 hintRequested = FALSE;
\r
5166 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5167 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5168 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5169 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5170 fromY, fromX, toY, toX, promoChar, buf1);
\r
5171 sprintf(buf2, "Hint: %s", buf1);
\r
5172 DisplayInformation(buf2);
\r
5174 /* Hint move could not be parsed!? */
\r
5176 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5177 buf1, cps->which);
\r
5178 DisplayError(buf2, 0);
\r
5181 strcpy(lastHint, buf1);
\r
5187 * Ignore other messages if game is not in progress
\r
5189 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5190 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5193 * look for win, lose, draw, or draw offer
\r
5195 if (strncmp(message, "1-0", 3) == 0) {
\r
5196 char *p, *q, *r = "";
\r
5197 p = strchr(message, '{');
\r
5199 q = strchr(p, '}');
\r
5205 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5207 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5208 char *p, *q, *r = "";
\r
5209 p = strchr(message, '{');
\r
5211 q = strchr(p, '}');
\r
5217 /* Kludge for Arasan 4.1 bug */
\r
5218 if (strcmp(r, "Black resigns") == 0) {
\r
5219 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5222 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5224 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5225 char *p, *q, *r = "";
\r
5226 p = strchr(message, '{');
\r
5228 q = strchr(p, '}');
\r
5235 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
5238 } else if (strncmp(message, "White resign", 12) == 0) {
\r
5239 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5241 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
5242 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5244 } else if (strncmp(message, "White", 5) == 0 &&
\r
5245 message[5] != '(' &&
\r
5246 StrStr(message, "Black") == NULL) {
\r
5247 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5249 } else if (strncmp(message, "Black", 5) == 0 &&
\r
5250 message[5] != '(') {
\r
5251 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5253 } else if (strcmp(message, "resign") == 0 ||
\r
5254 strcmp(message, "computer resigns") == 0) {
\r
5255 switch (gameMode) {
\r
5256 case MachinePlaysBlack:
\r
5257 case IcsPlayingBlack:
\r
5258 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5260 case MachinePlaysWhite:
\r
5261 case IcsPlayingWhite:
\r
5262 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5264 case TwoMachinesPlay:
\r
5265 if (cps->twoMachinesColor[0] == 'w')
\r
5266 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5268 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5271 /* can't happen */
\r
5275 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5276 switch (gameMode) {
\r
5277 case MachinePlaysBlack:
\r
5278 case IcsPlayingBlack:
\r
5279 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5281 case MachinePlaysWhite:
\r
5282 case IcsPlayingWhite:
\r
5283 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5285 case TwoMachinesPlay:
\r
5286 if (cps->twoMachinesColor[0] == 'w')
\r
5287 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5289 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5292 /* can't happen */
\r
5296 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5297 switch (gameMode) {
\r
5298 case MachinePlaysBlack:
\r
5299 case IcsPlayingBlack:
\r
5300 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5302 case MachinePlaysWhite:
\r
5303 case IcsPlayingWhite:
\r
5304 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5306 case TwoMachinesPlay:
\r
5307 if (cps->twoMachinesColor[0] == 'w')
\r
5308 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5310 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5313 /* can't happen */
\r
5317 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5318 if (WhiteOnMove(forwardMostMove)) {
\r
5319 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5321 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5324 } else if (strstr(message, "Draw") != NULL ||
\r
5325 strstr(message, "game is a draw") != NULL) {
\r
5326 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5328 } else if (strstr(message, "offer") != NULL &&
\r
5329 strstr(message, "draw") != NULL) {
\r
5331 if (appData.zippyPlay && first.initDone) {
\r
5332 /* Relay offer to ICS */
\r
5333 SendToICS(ics_prefix);
\r
5334 SendToICS("draw\n");
\r
5337 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5338 if (gameMode == TwoMachinesPlay) {
\r
5339 if (cps->other->offeredDraw) {
\r
5340 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5342 if (cps->other->sendDrawOffers) {
\r
5343 SendToProgram("draw\n", cps->other);
\r
5346 } else if (gameMode == MachinePlaysWhite ||
\r
5347 gameMode == MachinePlaysBlack) {
\r
5348 if (userOfferedDraw) {
\r
5349 DisplayInformation("Machine accepts your draw offer");
\r
5350 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5352 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5359 * Look for thinking output
\r
5361 if ( appData.showThinking) {
\r
5362 int plylev, mvleft, mvtot, curscore, time;
\r
5363 char mvname[MOVE_LEN];
\r
5364 unsigned long nodes;
\r
5366 int ignore = FALSE;
\r
5367 int prefixHint = FALSE;
\r
5368 mvname[0] = NULLCHAR;
\r
5370 switch (gameMode) {
\r
5371 case MachinePlaysBlack:
\r
5372 case IcsPlayingBlack:
\r
5373 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5375 case MachinePlaysWhite:
\r
5376 case IcsPlayingWhite:
\r
5377 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5382 case TwoMachinesPlay:
\r
5383 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5393 buf1[0] = NULLCHAR;
\r
5394 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5395 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5397 if (plyext != ' ' && plyext != '\t') {
\r
5401 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5402 if( cps->scoreIsAbsolute &&
\r
5403 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5405 curscore = -curscore;
\r
5409 programStats.depth = plylev;
\r
5410 programStats.nodes = nodes;
\r
5411 programStats.time = time;
\r
5412 programStats.score = curscore;
\r
5413 programStats.got_only_move = 0;
\r
5415 /* Buffer overflow protection */
\r
5416 if (buf1[0] != NULLCHAR) {
\r
5417 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5418 && appData.debugMode) {
\r
5420 "PV is too long; using the first %d bytes.\n",
\r
5421 sizeof(programStats.movelist) - 1);
\r
5424 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5426 sprintf(programStats.movelist, " no PV\n");
\r
5429 if (programStats.seen_stat) {
\r
5430 programStats.ok_to_send = 1;
\r
5433 if (strchr(programStats.movelist, '(') != NULL) {
\r
5434 programStats.line_is_book = 1;
\r
5435 programStats.nr_moves = 0;
\r
5436 programStats.moves_left = 0;
\r
5438 programStats.line_is_book = 0;
\r
5441 SendProgramStatsToFrontend( cps, &programStats );
\r
5444 [AS] Protect the thinkOutput buffer from overflow... this
\r
5445 is only useful if buf1 hasn't overflowed first!
\r
5447 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5449 (gameMode == TwoMachinesPlay ?
\r
5450 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5451 ((double) curscore) / 100.0,
\r
5452 prefixHint ? lastHint : "",
\r
5453 prefixHint ? " " : "" );
\r
5455 if( buf1[0] != NULLCHAR ) {
\r
5456 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5458 if( strlen(buf1) > max_len ) {
\r
5459 if( appData.debugMode) {
\r
5460 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5462 buf1[max_len+1] = '\0';
\r
5465 strcat( thinkOutput, buf1 );
\r
5468 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5469 DisplayMove(currentMove - 1);
\r
5470 DisplayAnalysis();
\r
5474 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
5475 /* crafty (9.25+) says "(only move) <move>"
\r
5476 * if there is only 1 legal move
\r
5478 sscanf(p, "(only move) %s", buf1);
\r
5479 sprintf(thinkOutput, "%s (only move)", buf1);
\r
5480 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
5481 programStats.depth = 1;
\r
5482 programStats.nr_moves = 1;
\r
5483 programStats.moves_left = 1;
\r
5484 programStats.nodes = 1;
\r
5485 programStats.time = 1;
\r
5486 programStats.got_only_move = 1;
\r
5488 /* Not really, but we also use this member to
\r
5489 mean "line isn't going to change" (Crafty
\r
5490 isn't searching, so stats won't change) */
\r
5491 programStats.line_is_book = 1;
\r
5493 SendProgramStatsToFrontend( cps, &programStats );
\r
5495 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5496 DisplayMove(currentMove - 1);
\r
5497 DisplayAnalysis();
\r
5500 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
5501 &time, &nodes, &plylev, &mvleft,
\r
5502 &mvtot, mvname) >= 5) {
\r
5503 /* The stat01: line is from Crafty (9.29+) in response
\r
5504 to the "." command */
\r
5505 programStats.seen_stat = 1;
\r
5506 cps->maybeThinking = TRUE;
\r
5508 if (programStats.got_only_move || !appData.periodicUpdates)
\r
5511 programStats.depth = plylev;
\r
5512 programStats.time = time;
\r
5513 programStats.nodes = nodes;
\r
5514 programStats.moves_left = mvleft;
\r
5515 programStats.nr_moves = mvtot;
\r
5516 strcpy(programStats.move_name, mvname);
\r
5517 programStats.ok_to_send = 1;
\r
5518 programStats.movelist[0] = '\0';
\r
5520 SendProgramStatsToFrontend( cps, &programStats );
\r
5522 DisplayAnalysis();
\r
5525 } else if (strncmp(message,"++",2) == 0) {
\r
5526 /* Crafty 9.29+ outputs this */
\r
5527 programStats.got_fail = 2;
\r
5530 } else if (strncmp(message,"--",2) == 0) {
\r
5531 /* Crafty 9.29+ outputs this */
\r
5532 programStats.got_fail = 1;
\r
5535 } else if (thinkOutput[0] != NULLCHAR &&
\r
5536 strncmp(message, " ", 4) == 0) {
\r
5537 unsigned message_len;
\r
5540 while (*p && *p == ' ') p++;
\r
5542 message_len = strlen( p );
\r
5544 /* [AS] Avoid buffer overflow */
\r
5545 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
5546 strcat(thinkOutput, " ");
\r
5547 strcat(thinkOutput, p);
\r
5550 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
5551 strcat(programStats.movelist, " ");
\r
5552 strcat(programStats.movelist, p);
\r
5555 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5556 DisplayMove(currentMove - 1);
\r
5557 DisplayAnalysis();
\r
5563 buf1[0] = NULLCHAR;
\r
5565 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5566 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
5568 ChessProgramStats cpstats;
\r
5570 if (plyext != ' ' && plyext != '\t') {
\r
5574 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5575 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
5576 curscore = -curscore;
\r
5579 cpstats.depth = plylev;
\r
5580 cpstats.nodes = nodes;
\r
5581 cpstats.time = time;
\r
5582 cpstats.score = curscore;
\r
5583 cpstats.got_only_move = 0;
\r
5584 cpstats.movelist[0] = '\0';
\r
5586 if (buf1[0] != NULLCHAR) {
\r
5587 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
5590 cpstats.ok_to_send = 0;
\r
5591 cpstats.line_is_book = 0;
\r
5592 cpstats.nr_moves = 0;
\r
5593 cpstats.moves_left = 0;
\r
5595 SendProgramStatsToFrontend( cps, &cpstats );
\r
5602 /* Parse a game score from the character string "game", and
\r
5603 record it as the history of the current game. The game
\r
5604 score is NOT assumed to start from the standard position.
\r
5605 The display is not updated in any way.
\r
5608 ParseGameHistory(game)
\r
5611 ChessMove moveType;
\r
5612 int fromX, fromY, toX, toY, boardIndex;
\r
5615 char buf[MSG_SIZ];
\r
5617 if (appData.debugMode)
\r
5618 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
5620 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
5621 gameInfo.site = StrSave(appData.icsHost);
\r
5622 gameInfo.date = PGNDate();
\r
5623 gameInfo.round = StrSave("-");
\r
5625 /* Parse out names of players */
\r
5626 while (*game == ' ') game++;
\r
5628 while (*game != ' ') *p++ = *game++;
\r
5630 gameInfo.white = StrSave(buf);
\r
5631 while (*game == ' ') game++;
\r
5633 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
5635 gameInfo.black = StrSave(buf);
\r
5638 boardIndex = blackPlaysFirst ? 1 : 0;
\r
5641 yyboardindex = boardIndex;
\r
5642 moveType = (ChessMove) yylex();
\r
5643 switch (moveType) {
\r
5645 case WhitePromotionChancellor:
\r
5646 case BlackPromotionChancellor:
\r
5647 case WhitePromotionArchbishop:
\r
5648 case BlackPromotionArchbishop:
\r
5650 case WhitePromotionQueen:
\r
5651 case BlackPromotionQueen:
\r
5652 case WhitePromotionRook:
\r
5653 case BlackPromotionRook:
\r
5654 case WhitePromotionBishop:
\r
5655 case BlackPromotionBishop:
\r
5656 case WhitePromotionKnight:
\r
5657 case BlackPromotionKnight:
\r
5658 case WhitePromotionKing:
\r
5659 case BlackPromotionKing:
\r
5661 case WhiteCapturesEnPassant:
\r
5662 case BlackCapturesEnPassant:
\r
5663 case WhiteKingSideCastle:
\r
5664 case WhiteQueenSideCastle:
\r
5665 case BlackKingSideCastle:
\r
5666 case BlackQueenSideCastle:
\r
5667 case WhiteKingSideCastleWild:
\r
5668 case WhiteQueenSideCastleWild:
\r
5669 case BlackKingSideCastleWild:
\r
5670 case BlackQueenSideCastleWild:
\r
5672 case WhiteHSideCastleFR:
\r
5673 case WhiteASideCastleFR:
\r
5674 case BlackHSideCastleFR:
\r
5675 case BlackASideCastleFR:
\r
5677 case IllegalMove: /* maybe suicide chess, etc. */
\r
5678 fromX = currentMoveString[0] - AAA;
\r
5679 fromY = currentMoveString[1] - ONE;
\r
5680 toX = currentMoveString[2] - AAA;
\r
5681 toY = currentMoveString[3] - ONE;
\r
5682 promoChar = currentMoveString[4];
\r
5686 fromX = moveType == WhiteDrop ?
\r
5687 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
5688 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
5689 fromY = DROP_RANK;
\r
5690 toX = currentMoveString[2] - AAA;
\r
5691 toY = currentMoveString[3] - ONE;
\r
5692 promoChar = NULLCHAR;
\r
5694 case AmbiguousMove:
\r
5696 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
5697 DisplayError(buf, 0);
\r
5699 case ImpossibleMove:
\r
5701 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
5702 DisplayError(buf, 0);
\r
5704 case (ChessMove) 0: /* end of file */
\r
5705 if (boardIndex < backwardMostMove) {
\r
5706 /* Oops, gap. How did that happen? */
\r
5707 DisplayError("Gap in move list", 0);
\r
5710 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5711 if (boardIndex > forwardMostMove) {
\r
5712 forwardMostMove = boardIndex;
\r
5716 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
5717 strcat(parseList[boardIndex-1], " ");
\r
5718 strcat(parseList[boardIndex-1], yy_text);
\r
5730 case GameUnfinished:
\r
5731 if (gameMode == IcsExamining) {
\r
5732 if (boardIndex < backwardMostMove) {
\r
5733 /* Oops, gap. How did that happen? */
\r
5736 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5739 gameInfo.result = moveType;
\r
5740 p = strchr(yy_text, '{');
\r
5741 if (p == NULL) p = strchr(yy_text, '(');
\r
5744 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
5746 q = strchr(p, *p == '{' ? '}' : ')');
\r
5747 if (q != NULL) *q = NULLCHAR;
\r
5750 gameInfo.resultDetails = StrSave(p);
\r
5753 if (boardIndex >= forwardMostMove &&
\r
5754 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
5755 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5758 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
5759 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
5760 parseList[boardIndex]);
\r
5761 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
5762 /* currentMoveString is set as a side-effect of yylex */
\r
5763 strcpy(moveList[boardIndex], currentMoveString);
\r
5764 strcat(moveList[boardIndex], "\n");
\r
5766 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
5767 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
5768 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
5770 case MT_STALEMATE:
\r
5774 strcat(parseList[boardIndex - 1], "+");
\r
5776 case MT_CHECKMATE:
\r
5777 strcat(parseList[boardIndex - 1], "#");
\r
5784 /* Apply a move to the given board */
\r
5786 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
5787 int fromX, fromY, toX, toY;
\r
5791 ChessSquare captured = board[toY][toX], piece; int p;
\r
5793 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
5794 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
5795 && promoChar != 0) promoChar = 'F';
\r
5797 if (fromY == DROP_RANK) {
\r
5798 /* must be first */
\r
5799 board[toY][toX] = (ChessSquare) fromX;
\r
5800 } else if (fromX == toX && fromY == toY) {
\r
5804 piece = board[fromY][fromX];
\r
5806 /* Code added by Tord: */
\r
5807 /* FRC castling assumed when king captures friendly rook. */
\r
5808 else if (board[fromY][fromX] == WhiteKing &&
\r
5809 board[toY][toX] == WhiteRook) {
\r
5810 board[fromY][fromX] = EmptySquare;
\r
5811 board[toY][toX] = EmptySquare;
\r
5813 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
5815 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
5817 } else if (board[fromY][fromX] == BlackKing &&
\r
5818 board[toY][toX] == BlackRook) {
\r
5819 board[fromY][fromX] = EmptySquare;
\r
5820 board[toY][toX] = EmptySquare;
\r
5822 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
5824 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
5826 /* End of code added by Tord */
\r
5828 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5829 && board[fromY][fromX] == WhiteKing
\r
5830 && toY == fromY && toX > fromX+1) {
\r
5831 board[fromY][fromX] = EmptySquare;
\r
5832 board[toY][toX] = WhiteKing;
\r
5833 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
5834 board[toY][toX-1] = WhiteRook;
\r
5835 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5836 && board[fromY][fromX] == WhiteKing
\r
5837 && toY == fromY && toX < fromX-1) {
\r
5838 board[fromY][fromX] = EmptySquare;
\r
5839 board[toY][toX] = WhiteKing;
\r
5840 board[fromY][BOARD_LEFT] = EmptySquare;
\r
5841 board[toY][toX+1] = WhiteRook;
\r
5842 } else if (fromY == 0 && fromX == 3
\r
5843 && board[fromY][fromX] == WhiteKing
\r
5844 && toY == 0 && toX == 5) {
\r
5845 board[fromY][fromX] = EmptySquare;
\r
5846 board[toY][toX] = WhiteKing;
\r
5847 board[fromY][7] = EmptySquare;
\r
5848 board[toY][4] = WhiteRook;
\r
5849 } else if (fromY == 0 && fromX == 3
\r
5850 && board[fromY][fromX] == WhiteKing
\r
5851 && toY == 0 && toX == 1) {
\r
5852 board[fromY][fromX] = EmptySquare;
\r
5853 board[toY][toX] = WhiteKing;
\r
5854 board[fromY][0] = EmptySquare;
\r
5855 board[toY][2] = WhiteRook;
\r
5856 } else if (board[fromY][fromX] == WhitePawn
\r
5857 && toY == BOARD_HEIGHT-1
\r
5859 && gameInfo.variant != VariantXiangqi
\r
5862 /* white pawn promotion */
\r
5863 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
5864 if (board[toY][toX] == EmptySquare) {
\r
5865 board[toY][toX] = WhiteQueen;
\r
5867 if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
5868 board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn;
\r
5869 board[fromY][fromX] = EmptySquare;
\r
5870 } else if ((fromY == BOARD_HEIGHT-4)
\r
5872 && (board[fromY][fromX] == WhitePawn)
\r
5873 && (board[toY][toX] == EmptySquare)) {
\r
5874 board[fromY][fromX] = EmptySquare;
\r
5875 board[toY][toX] = WhitePawn;
\r
5876 captured = board[toY - 1][toX];
\r
5877 board[toY - 1][toX] = EmptySquare;
\r
5878 } else if (initialPosition[fromY][fromX] == BlackKing
\r
5879 && board[fromY][fromX] == BlackKing
\r
5880 && toY == fromY && toX > fromX+1) {
\r
5881 board[fromY][fromX] = EmptySquare;
\r
5882 board[toY][toX] = BlackKing;
\r
5883 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
5884 board[toY][toX-1] = BlackRook;
\r
5885 } else if (initialPosition[fromY][fromX] == BlackKing
\r
5886 && board[fromY][fromX] == BlackKing
\r
5887 && toY == fromY && toX < fromX-1) {
\r
5888 board[fromY][fromX] = EmptySquare;
\r
5889 board[toY][toX] = BlackKing;
\r
5890 board[fromY][BOARD_LEFT] = EmptySquare;
\r
5891 board[toY][toX+1] = BlackRook;
\r
5892 } else if (fromY == 7 && fromX == 3
\r
5893 && board[fromY][fromX] == BlackKing
\r
5894 && toY == 7 && toX == 5) {
\r
5895 board[fromY][fromX] = EmptySquare;
\r
5896 board[toY][toX] = BlackKing;
\r
5897 board[fromY][7] = EmptySquare;
\r
5898 board[toY][4] = BlackRook;
\r
5899 } else if (fromY == 7 && fromX == 3
\r
5900 && board[fromY][fromX] == BlackKing
\r
5901 && toY == 7 && toX == 1) {
\r
5902 board[fromY][fromX] = EmptySquare;
\r
5903 board[toY][toX] = BlackKing;
\r
5904 board[fromY][0] = EmptySquare;
\r
5905 board[toY][2] = BlackRook;
\r
5906 } else if (board[fromY][fromX] == BlackPawn
\r
5909 && gameInfo.variant != VariantXiangqi
\r
5912 /* black pawn promotion */
\r
5913 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
5914 if (board[0][toX] == EmptySquare) {
\r
5915 board[0][toX] = BlackQueen;
\r
5917 if(gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
5918 board[toY][toX] += (int) WhiteAlfil - (int) WhitePawn;
\r
5919 board[fromY][fromX] = EmptySquare;
\r
5920 } else if ((fromY == 3)
\r
5922 && (board[fromY][fromX] == BlackPawn)
\r
5923 && (board[toY][toX] == EmptySquare)) {
\r
5924 board[fromY][fromX] = EmptySquare;
\r
5925 board[toY][toX] = BlackPawn;
\r
5926 captured = board[toY + 1][toX];
\r
5927 board[toY + 1][toX] = EmptySquare;
\r
5929 board[toY][toX] = board[fromY][fromX];
\r
5930 board[fromY][fromX] = EmptySquare;
\r
5932 if (gameInfo.holdingsWidth != 0) {
\r
5934 /* !!A lot more code needs to be written to support holdings */
\r
5935 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
5936 /* penultimate board files, so they are automaticlly stored */
\r
5937 /* in the game history. */
\r
5938 if (fromY == DROP_RANK) {
\r
5939 /* Delete from holdings, by decreasing count */
\r
5940 /* and erasing image if necessary */
\r
5942 if(p < (int) BlackPawn) { /* white drop */
\r
5943 p -= (int)WhitePawn;
\r
5944 if(p >= gameInfo.holdingsSize) p = 0;
\r
5945 if(--board[p][BOARD_WIDTH-2] == 0)
\r
5946 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
5947 } else { /* black drop */
\r
5948 p -= (int)BlackPawn;
\r
5949 if(p >= gameInfo.holdingsSize) p = 0;
\r
5950 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
5951 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
5954 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
5955 && gameInfo.variant != VariantBughouse ) {
\r
5956 /* Add to holdings, if holdings exist */
\r
5957 p = (int) captured;
\r
5958 if (p >= (int) BlackPawn) {
\r
5959 p -= (int)BlackPawn;
\r
5960 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
5961 /* in Shogi restore piece to its original first */
\r
5962 captured = (ChessSquare) (DEMOTED captured);
\r
5965 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
5966 board[p][BOARD_WIDTH-2]++;
\r
5967 board[p][BOARD_WIDTH-1] =
\r
5968 BLACK_TO_WHITE captured;
\r
5970 p -= (int)WhitePawn;
\r
5971 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
5972 captured = (ChessSquare) (DEMOTED captured);
\r
5975 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
5976 board[BOARD_HEIGHT-1-p][1]++;
\r
5977 board[BOARD_HEIGHT-1-p][0] =
\r
5978 WHITE_TO_BLACK captured;
\r
5982 } else if (gameInfo.variant == VariantAtomic) {
\r
5983 if (captured != EmptySquare) {
\r
5985 for (y = toY-1; y <= toY+1; y++) {
\r
5986 for (x = toX-1; x <= toX+1; x++) {
\r
5987 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
5988 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
5989 board[y][x] = EmptySquare;
\r
5993 board[toY][toX] = EmptySquare;
\r
5996 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR) {
\r
5997 /* [HGM] Shogi promotions */
\r
5998 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6003 /* Updates forwardMostMove */
\r
6005 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6006 int fromX, fromY, toX, toY;
\r
6009 forwardMostMove++;
\r
6011 if (forwardMostMove >= MAX_MOVES) {
\r
6012 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6017 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6018 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6019 if (commentList[forwardMostMove] != NULL) {
\r
6020 free(commentList[forwardMostMove]);
\r
6021 commentList[forwardMostMove] = NULL;
\r
6023 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6024 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6027 epStatus[forwardMostMove] = EP_NONE;
\r
6029 if( boards[forwardMostMove][toY][toX] != EmptySquare )
\r
6030 epStatus[forwardMostMove] = EP_CAPTURE;
\r
6032 if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {
\r
6033 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6034 if( toY-fromY==2 &&
\r
6035 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||
\r
6036 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )
\r
6037 epStatus[forwardMostMove] = toX;
\r
6039 if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {
\r
6040 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6041 if( toY-fromY== -2 &&
\r
6042 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||
\r
6043 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )
\r
6044 epStatus[forwardMostMove] = toX;
\r
6047 for(i=0; i<nrCastlingRights; i++) {
\r
6048 castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];
\r
6049 if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||
\r
6050 castlingRights[forwardMostMove][i] == toX && castlingRank[i] == toY
\r
6051 ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece
\r
6056 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6057 gameInfo.result = GameUnfinished;
\r
6058 if (gameInfo.resultDetails != NULL) {
\r
6059 free(gameInfo.resultDetails);
\r
6060 gameInfo.resultDetails = NULL;
\r
6062 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6063 moveList[forwardMostMove - 1]);
\r
6064 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6065 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6066 fromY, fromX, toY, toX, promoChar,
\r
6067 parseList[forwardMostMove - 1]);
\r
6068 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6069 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6070 castlingRights[forwardMostMove]) ) {
\r
6072 case MT_STALEMATE:
\r
6076 strcat(parseList[forwardMostMove - 1], "+");
\r
6078 case MT_CHECKMATE:
\r
6079 strcat(parseList[forwardMostMove - 1], "#");
\r
6084 /* Updates currentMove if not pausing */
\r
6086 ShowMove(fromX, fromY, toX, toY)
\r
6088 int instant = (gameMode == PlayFromGameFile) ?
\r
6089 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6090 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6092 if (forwardMostMove == currentMove + 1) {
\r
6093 AnimateMove(boards[forwardMostMove - 1],
\r
6094 fromX, fromY, toX, toY);
\r
6096 if (appData.highlightLastMove) {
\r
6097 SetHighlights(fromX, fromY, toX, toY);
\r
6100 currentMove = forwardMostMove;
\r
6103 if (instant) return;
\r
6105 DisplayMove(currentMove - 1);
\r
6106 DrawPosition(FALSE, boards[currentMove]);
\r
6107 DisplayBothClocks();
\r
6108 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6113 InitChessProgram(cps)
\r
6114 ChessProgramState *cps;
\r
6116 char buf[MSG_SIZ], *b; int overruled;
\r
6117 if (appData.noChessProgram) return;
\r
6118 hintRequested = FALSE;
\r
6119 bookRequested = FALSE;
\r
6120 SendToProgram(cps->initString, cps);
\r
6121 if (gameInfo.variant != VariantNormal &&
\r
6122 gameInfo.variant != VariantLoadable
\r
6123 /* [HGM] also send variant if board size non-standard */
\r
6124 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8
\r
6126 char *v = VariantName(gameInfo.variant);
\r
6127 if (StrStr(cps->variants, v) == NULL) {
\r
6128 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
6129 DisplayFatalError(buf, 0, 1);
\r
6133 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
6134 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6135 if( gameInfo.variant == VariantXiangqi )
\r
6136 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
6137 if( gameInfo.variant == VariantShogi )
\r
6138 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
6139 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
6140 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
6141 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )
\r
6142 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6143 if( gameInfo.variant == VariantCourier )
\r
6144 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6148 // doesn't work in protocol 1
\r
6149 if (StrStr(cps->variants, "boardsize") == NULL,) {
\r
6150 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
6151 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
6152 DisplayFatalError(buf, 0, 1);
\r
6156 sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,
\r
6157 gameInfo.boardHeight, gameInfo.holdingsSize );
\r
6158 while(*b++ != '_');
\r
6160 sprintf(b, "variant %s\n", VariantName(gameInfo.variant));
\r
6161 SendToProgram(buf, cps);
\r
6163 if (cps->sendICS) {
\r
6164 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
6165 SendToProgram(buf, cps);
\r
6167 cps->maybeThinking = FALSE;
\r
6168 cps->offeredDraw = 0;
\r
6169 if (!appData.icsActive) {
\r
6170 SendTimeControl(cps, movesPerSession, timeControl,
\r
6171 timeIncrement, appData.searchDepth,
\r
6174 if (appData.showThinking) {
\r
6175 SendToProgram("post\n", cps);
\r
6177 SendToProgram("hard\n", cps);
\r
6178 if (!appData.ponderNextMove) {
\r
6179 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
6180 it without being sure what state we are in first. "hard"
\r
6181 is not a toggle, so that one is OK.
\r
6183 SendToProgram("easy\n", cps);
\r
6185 if (cps->usePing) {
\r
6186 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
6187 SendToProgram(buf, cps);
\r
6189 cps->initDone = TRUE;
\r
6194 StartChessProgram(cps)
\r
6195 ChessProgramState *cps;
\r
6197 char buf[MSG_SIZ];
\r
6200 if (appData.noChessProgram) return;
\r
6201 cps->initDone = FALSE;
\r
6203 if (strcmp(cps->host, "localhost") == 0) {
\r
6204 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
6205 } else if (*appData.remoteShell == NULLCHAR) {
\r
6206 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
6208 if (*appData.remoteUser == NULLCHAR) {
\r
6209 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
6212 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
6213 cps->host, appData.remoteUser, cps->program);
\r
6215 err = StartChildProcess(buf, "", &cps->pr);
\r
6219 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
6220 DisplayFatalError(buf, err, 1);
\r
6226 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
6227 if (cps->protocolVersion > 1) {
\r
6228 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
6229 SendToProgram(buf, cps);
\r
6231 SendToProgram("xboard\n", cps);
\r
6237 TwoMachinesEventIfReady P((void))
\r
6239 if (first.lastPing != first.lastPong) {
\r
6240 DisplayMessage("", "Waiting for first chess program");
\r
6241 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6244 if (second.lastPing != second.lastPong) {
\r
6245 DisplayMessage("", "Waiting for second chess program");
\r
6246 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6250 TwoMachinesEvent();
\r
6254 NextMatchGame P((void))
\r
6256 Reset(FALSE, TRUE);
\r
6257 if (*appData.loadGameFile != NULLCHAR) {
\r
6258 LoadGameFromFile(appData.loadGameFile,
\r
6259 appData.loadGameIndex,
\r
6260 appData.loadGameFile, FALSE);
\r
6261 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
6262 LoadPositionFromFile(appData.loadPositionFile,
\r
6263 appData.loadPositionIndex,
\r
6264 appData.loadPositionFile);
\r
6266 TwoMachinesEventIfReady();
\r
6269 void UserAdjudicationEvent( int result )
\r
6271 ChessMove gameResult = GameIsDrawn;
\r
6273 if( result > 0 ) {
\r
6274 gameResult = WhiteWins;
\r
6276 else if( result < 0 ) {
\r
6277 gameResult = BlackWins;
\r
6280 if( gameMode == TwoMachinesPlay ) {
\r
6281 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
6287 GameEnds(result, resultDetails, whosays)
\r
6289 char *resultDetails;
\r
6292 GameMode nextGameMode;
\r
6294 char buf[MSG_SIZ];
\r
6296 if (appData.debugMode) {
\r
6297 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
6298 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6301 if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
6302 /* If we are playing on ICS, the server decides when the
\r
6303 game is over, but the engine can offer to draw, claim
\r
6304 a draw, or resign.
\r
6307 if (appData.zippyPlay && first.initDone) {
\r
6308 if (result == GameIsDrawn) {
\r
6309 /* In case draw still needs to be claimed */
\r
6310 SendToICS(ics_prefix);
\r
6311 SendToICS("draw\n");
\r
6312 } else if (StrCaseStr(resultDetails, "resign")) {
\r
6313 SendToICS(ics_prefix);
\r
6314 SendToICS("resign\n");
\r
6321 /* If we're loading the game from a file, stop */
\r
6322 if (whosays == GE_FILE) {
\r
6323 (void) StopLoadGameTimer();
\r
6324 gameFileFP = NULL;
\r
6327 /* Cancel draw offers */
\r
6328 first.offeredDraw = second.offeredDraw = 0;
\r
6330 /* If this is an ICS game, only ICS can really say it's done;
\r
6331 if not, anyone can. */
\r
6332 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6333 gameMode == IcsPlayingBlack ||
\r
6334 gameMode == IcsObserving ||
\r
6335 gameMode == IcsExamining);
\r
6337 if (!isIcsGame || whosays == GE_ICS) {
\r
6338 /* OK -- not an ICS game, or ICS said it was done */
\r
6340 if (appData.debugMode) {
\r
6341 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6342 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6344 if (!isIcsGame && !appData.noChessProgram)
\r
6345 SetUserThinkingEnables();
\r
6347 /* [HGM] if a machine claims the game end we verify this claim */
\r
6348 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6349 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6352 if (appData.debugMode) {
\r
6353 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6354 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6356 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6357 first.twoMachinesColor[0] :
\r
6358 second.twoMachinesColor[0] ;
\r
6359 if( result == WhiteWins && claimer == 'w' ||
\r
6360 result == BlackWins && claimer == 'b' ) {
\r
6361 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6362 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6363 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6364 resultDetails = buf;
\r
6366 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {
\r
6367 /* Draw that was not flagged by Xboard is false */
\r
6368 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6369 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6370 resultDetails = buf;
\r
6372 /* (Claiming a loss is accepted no questions asked!) */
\r
6375 if (appData.debugMode) {
\r
6376 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6377 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6379 if (resultDetails != NULL) {
\r
6380 gameInfo.result = result;
\r
6381 gameInfo.resultDetails = StrSave(resultDetails);
\r
6383 /* Tell program how game ended in case it is learning */
\r
6384 if (gameMode == MachinePlaysWhite ||
\r
6385 gameMode == MachinePlaysBlack ||
\r
6386 gameMode == TwoMachinesPlay ||
\r
6387 gameMode == IcsPlayingWhite ||
\r
6388 gameMode == IcsPlayingBlack ||
\r
6389 gameMode == BeginningOfGame) {
\r
6390 char buf[MSG_SIZ];
\r
6391 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
6393 if (first.pr != NoProc) {
\r
6394 SendToProgram(buf, &first);
\r
6396 if (second.pr != NoProc &&
\r
6397 gameMode == TwoMachinesPlay) {
\r
6398 SendToProgram(buf, &second);
\r
6402 /* display last move only if game was not loaded from file */
\r
6403 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
6404 DisplayMove(currentMove - 1);
\r
6406 if (forwardMostMove != 0) {
\r
6407 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
6408 if (*appData.saveGameFile != NULLCHAR) {
\r
6409 SaveGameToFile(appData.saveGameFile, TRUE);
\r
6410 } else if (appData.autoSaveGames) {
\r
6413 if (*appData.savePositionFile != NULLCHAR) {
\r
6414 SavePositionToFile(appData.savePositionFile);
\r
6420 if (appData.icsActive) {
\r
6421 if (appData.quietPlay &&
\r
6422 (gameMode == IcsPlayingWhite ||
\r
6423 gameMode == IcsPlayingBlack)) {
\r
6424 SendToICS(ics_prefix);
\r
6425 SendToICS("set shout 1\n");
\r
6427 nextGameMode = IcsIdle;
\r
6428 ics_user_moved = FALSE;
\r
6429 /* clean up premove. It's ugly when the game has ended and the
\r
6430 * premove highlights are still on the board.
\r
6433 gotPremove = FALSE;
\r
6434 ClearPremoveHighlights();
\r
6435 DrawPosition(FALSE, boards[currentMove]);
\r
6437 if (whosays == GE_ICS) {
\r
6440 if (gameMode == IcsPlayingWhite)
\r
6441 PlayIcsWinSound();
\r
6442 else if(gameMode == IcsPlayingBlack)
\r
6443 PlayIcsLossSound();
\r
6446 if (gameMode == IcsPlayingBlack)
\r
6447 PlayIcsWinSound();
\r
6448 else if(gameMode == IcsPlayingWhite)
\r
6449 PlayIcsLossSound();
\r
6452 PlayIcsDrawSound();
\r
6455 PlayIcsUnfinishedSound();
\r
6458 } else if (gameMode == EditGame ||
\r
6459 gameMode == PlayFromGameFile ||
\r
6460 gameMode == AnalyzeMode ||
\r
6461 gameMode == AnalyzeFile) {
\r
6462 nextGameMode = gameMode;
\r
6464 nextGameMode = EndOfGame;
\r
6469 nextGameMode = gameMode;
\r
6472 if (appData.noChessProgram) {
\r
6473 gameMode = nextGameMode;
\r
6478 if (first.reuse) {
\r
6479 /* Put first chess program into idle state */
\r
6480 if (first.pr != NoProc &&
\r
6481 (gameMode == MachinePlaysWhite ||
\r
6482 gameMode == MachinePlaysBlack ||
\r
6483 gameMode == TwoMachinesPlay ||
\r
6484 gameMode == IcsPlayingWhite ||
\r
6485 gameMode == IcsPlayingBlack ||
\r
6486 gameMode == BeginningOfGame)) {
\r
6487 SendToProgram("force\n", &first);
\r
6488 if (first.usePing) {
\r
6489 char buf[MSG_SIZ];
\r
6490 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
6491 SendToProgram(buf, &first);
\r
6494 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6495 /* Kill off first chess program */
\r
6496 if (first.isr != NULL)
\r
6497 RemoveInputSource(first.isr);
\r
6500 if (first.pr != NoProc) {
\r
6501 ExitAnalyzeMode();
\r
6502 DoSleep( appData.delayBeforeQuit );
\r
6503 SendToProgram("quit\n", &first);
\r
6504 DoSleep( appData.delayAfterQuit );
\r
6505 DestroyChildProcess(first.pr, first.useSigterm);
\r
6507 first.pr = NoProc;
\r
6509 if (second.reuse) {
\r
6510 /* Put second chess program into idle state */
\r
6511 if (second.pr != NoProc &&
\r
6512 gameMode == TwoMachinesPlay) {
\r
6513 SendToProgram("force\n", &second);
\r
6514 if (second.usePing) {
\r
6515 char buf[MSG_SIZ];
\r
6516 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
6517 SendToProgram(buf, &second);
\r
6520 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6521 /* Kill off second chess program */
\r
6522 if (second.isr != NULL)
\r
6523 RemoveInputSource(second.isr);
\r
6524 second.isr = NULL;
\r
6526 if (second.pr != NoProc) {
\r
6527 DoSleep( appData.delayBeforeQuit );
\r
6528 SendToProgram("quit\n", &second);
\r
6529 DoSleep( appData.delayAfterQuit );
\r
6530 DestroyChildProcess(second.pr, second.useSigterm);
\r
6532 second.pr = NoProc;
\r
6535 if (matchMode && gameMode == TwoMachinesPlay) {
\r
6538 if (first.twoMachinesColor[0] == 'w') {
\r
6539 first.matchWins++;
\r
6541 second.matchWins++;
\r
6545 if (first.twoMachinesColor[0] == 'b') {
\r
6546 first.matchWins++;
\r
6548 second.matchWins++;
\r
6554 if (matchGame < appData.matchGames) {
\r
6556 tmp = first.twoMachinesColor;
\r
6557 first.twoMachinesColor = second.twoMachinesColor;
\r
6558 second.twoMachinesColor = tmp;
\r
6559 gameMode = nextGameMode;
\r
6561 if(appData.matchPause>10000 || appData.matchPause<10)
\r
6562 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
6563 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
6566 char buf[MSG_SIZ];
\r
6567 gameMode = nextGameMode;
\r
6568 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
6569 first.tidy, second.tidy,
\r
6570 first.matchWins, second.matchWins,
\r
6571 appData.matchGames - (first.matchWins + second.matchWins));
\r
6572 DisplayFatalError(buf, 0, 0);
\r
6575 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6576 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
6577 ExitAnalyzeMode();
\r
6578 gameMode = nextGameMode;
\r
6582 /* Assumes program was just initialized (initString sent).
\r
6583 Leaves program in force mode. */
\r
6585 FeedMovesToProgram(cps, upto)
\r
6586 ChessProgramState *cps;
\r
6591 if (appData.debugMode)
\r
6592 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
6593 startedFromSetupPosition ? "position and " : "",
\r
6594 backwardMostMove, upto, cps->which);
\r
6595 SendToProgram("force\n", cps);
\r
6596 if (startedFromSetupPosition) {
\r
6597 SendBoard(cps, backwardMostMove);
\r
6599 for (i = backwardMostMove; i < upto; i++) {
\r
6600 SendMoveToProgram(i, cps);
\r
6606 ResurrectChessProgram()
\r
6608 /* The chess program may have exited.
\r
6609 If so, restart it and feed it all the moves made so far. */
\r
6611 if (appData.noChessProgram || first.pr != NoProc) return;
\r
6613 StartChessProgram(&first);
\r
6614 InitChessProgram(&first);
\r
6615 FeedMovesToProgram(&first, currentMove);
\r
6617 if (!first.sendTime) {
\r
6618 /* can't tell gnuchess what its clock should read,
\r
6619 so we bow to its notion. */
\r
6621 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
6622 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
6625 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6626 first.analysisSupport) {
\r
6627 SendToProgram("analyze\n", &first);
\r
6628 first.analyzing = TRUE;
\r
6633 * Button procedures
\r
6636 Reset(redraw, init)
\r
6641 if (appData.debugMode) {
\r
6642 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
6643 redraw, init, gameMode);
\r
6646 pausing = pauseExamInvalid = FALSE;
\r
6647 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
6649 whiteFlag = blackFlag = FALSE;
\r
6650 userOfferedDraw = FALSE;
\r
6651 hintRequested = bookRequested = FALSE;
\r
6652 first.maybeThinking = FALSE;
\r
6653 second.maybeThinking = FALSE;
\r
6654 thinkOutput[0] = NULLCHAR;
\r
6655 lastHint[0] = NULLCHAR;
\r
6656 ClearGameInfo(&gameInfo);
\r
6657 gameInfo.variant = StringToVariant(appData.variant);
\r
6658 ics_user_moved = ics_clock_paused = FALSE;
\r
6659 ics_getting_history = H_FALSE;
\r
6661 white_holding[0] = black_holding[0] = NULLCHAR;
\r
6662 ClearProgramStats();
\r
6665 ClearHighlights();
\r
6666 flipView = appData.flipView;
\r
6667 ClearPremoveHighlights();
\r
6668 gotPremove = FALSE;
\r
6669 alarmSounded = FALSE;
\r
6671 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
6672 ExitAnalyzeMode();
\r
6673 gameMode = BeginningOfGame;
\r
6675 InitPosition(redraw);
\r
6676 for (i = 0; i < MAX_MOVES; i++) {
\r
6677 if (commentList[i] != NULL) {
\r
6678 free(commentList[i]);
\r
6679 commentList[i] = NULL;
\r
6683 timeRemaining[0][0] = whiteTimeRemaining;
\r
6684 timeRemaining[1][0] = blackTimeRemaining;
\r
6685 if (first.pr == NULL) {
\r
6686 StartChessProgram(&first);
\r
6688 if (init) InitChessProgram(&first);
\r
6690 DisplayMessage("", "");
\r
6691 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
6695 AutoPlayGameLoop()
\r
6698 if (!AutoPlayOneMove())
\r
6700 if (matchMode || appData.timeDelay == 0)
\r
6702 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
6704 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
6713 int fromX, fromY, toX, toY;
\r
6715 if (appData.debugMode) {
\r
6716 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
6719 if (gameMode != PlayFromGameFile)
\r
6722 if (currentMove >= forwardMostMove) {
\r
6723 gameMode = EditGame;
\r
6726 /* [AS] Clear current move marker at the end of a game */
\r
6727 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
6732 toX = moveList[currentMove][2] - AAA;
\r
6733 toY = moveList[currentMove][3] - ONE;
\r
6735 if (moveList[currentMove][1] == '@') {
\r
6736 if (appData.highlightLastMove) {
\r
6737 SetHighlights(-1, -1, toX, toY);
\r
6740 fromX = moveList[currentMove][0] - AAA;
\r
6741 fromY = moveList[currentMove][1] - ONE;
\r
6743 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
6745 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
6747 if (appData.highlightLastMove) {
\r
6748 SetHighlights(fromX, fromY, toX, toY);
\r
6751 DisplayMove(currentMove);
\r
6752 SendMoveToProgram(currentMove++, &first);
\r
6753 DisplayBothClocks();
\r
6754 DrawPosition(FALSE, boards[currentMove]);
\r
6755 if (commentList[currentMove] != NULL) {
\r
6756 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6763 LoadGameOneMove(readAhead)
\r
6764 ChessMove readAhead;
\r
6766 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
6767 char promoChar = NULLCHAR;
\r
6768 ChessMove moveType;
\r
6769 char move[MSG_SIZ];
\r
6772 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
6773 gameMode != AnalyzeMode && gameMode != Training) {
\r
6774 gameFileFP = NULL;
\r
6778 yyboardindex = forwardMostMove;
\r
6779 if (readAhead != (ChessMove)0) {
\r
6780 moveType = readAhead;
\r
6782 if (gameFileFP == NULL)
\r
6784 moveType = (ChessMove) yylex();
\r
6788 switch (moveType) {
\r
6790 if (appData.debugMode)
\r
6791 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
6793 if (*p == '{' || *p == '[' || *p == '(') {
\r
6794 p[strlen(p) - 1] = NULLCHAR;
\r
6798 /* append the comment but don't display it */
\r
6799 while (*p == '\n') p++;
\r
6800 AppendComment(currentMove, p);
\r
6803 case WhiteCapturesEnPassant:
\r
6804 case BlackCapturesEnPassant:
\r
6806 case WhitePromotionChancellor:
\r
6807 case BlackPromotionChancellor:
\r
6808 case WhitePromotionArchbishop:
\r
6809 case BlackPromotionArchbishop:
\r
6811 case WhitePromotionQueen:
\r
6812 case BlackPromotionQueen:
\r
6813 case WhitePromotionRook:
\r
6814 case BlackPromotionRook:
\r
6815 case WhitePromotionBishop:
\r
6816 case BlackPromotionBishop:
\r
6817 case WhitePromotionKnight:
\r
6818 case BlackPromotionKnight:
\r
6819 case WhitePromotionKing:
\r
6820 case BlackPromotionKing:
\r
6822 case WhiteKingSideCastle:
\r
6823 case WhiteQueenSideCastle:
\r
6824 case BlackKingSideCastle:
\r
6825 case BlackQueenSideCastle:
\r
6826 case WhiteKingSideCastleWild:
\r
6827 case WhiteQueenSideCastleWild:
\r
6828 case BlackKingSideCastleWild:
\r
6829 case BlackQueenSideCastleWild:
\r
6831 case WhiteHSideCastleFR:
\r
6832 case WhiteASideCastleFR:
\r
6833 case BlackHSideCastleFR:
\r
6834 case BlackASideCastleFR:
\r
6836 if (appData.debugMode)
\r
6837 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
6838 fromX = currentMoveString[0] - AAA;
\r
6839 fromY = currentMoveString[1] - ONE;
\r
6840 toX = currentMoveString[2] - AAA;
\r
6841 toY = currentMoveString[3] - ONE;
\r
6842 promoChar = currentMoveString[4];
\r
6847 if (appData.debugMode)
\r
6848 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
6849 fromX = moveType == WhiteDrop ?
\r
6850 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6851 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6852 fromY = DROP_RANK;
\r
6853 toX = currentMoveString[2] - AAA;
\r
6854 toY = currentMoveString[3] - ONE;
\r
6860 case GameUnfinished:
\r
6861 if (appData.debugMode)
\r
6862 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
6863 p = strchr(yy_text, '{');
\r
6864 if (p == NULL) p = strchr(yy_text, '(');
\r
6867 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6869 q = strchr(p, *p == '{' ? '}' : ')');
\r
6870 if (q != NULL) *q = NULLCHAR;
\r
6873 GameEnds(moveType, p, GE_FILE);
\r
6875 if (cmailMsgLoaded) {
\r
6876 ClearHighlights();
\r
6877 flipView = WhiteOnMove(currentMove);
\r
6878 if (moveType == GameUnfinished) flipView = !flipView;
\r
6879 if (appData.debugMode)
\r
6880 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
6884 case (ChessMove) 0: /* end of file */
\r
6885 if (appData.debugMode)
\r
6886 fprintf(debugFP, "Parser hit end of file\n");
\r
6887 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
6888 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
6892 case MT_CHECKMATE:
\r
6893 if (WhiteOnMove(currentMove)) {
\r
6894 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
6896 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
6899 case MT_STALEMATE:
\r
6900 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
6906 case MoveNumberOne:
\r
6907 if (lastLoadGameStart == GNUChessGame) {
\r
6908 /* GNUChessGames have numbers, but they aren't move numbers */
\r
6909 if (appData.debugMode)
\r
6910 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
6911 yy_text, (int) moveType);
\r
6912 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
6914 /* else fall thru */
\r
6917 case GNUChessGame:
\r
6919 /* Reached start of next game in file */
\r
6920 if (appData.debugMode)
\r
6921 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
6922 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
6923 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
6927 case MT_CHECKMATE:
\r
6928 if (WhiteOnMove(currentMove)) {
\r
6929 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
6931 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
6934 case MT_STALEMATE:
\r
6935 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
6941 case PositionDiagram: /* should not happen; ignore */
\r
6942 case ElapsedTime: /* ignore */
\r
6943 case NAG: /* ignore */
\r
6944 if (appData.debugMode)
\r
6945 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
6946 yy_text, (int) moveType);
\r
6947 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
6950 if (appData.testLegality) {
\r
6951 if (appData.debugMode)
\r
6952 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
6953 sprintf(move, "Illegal move: %d.%s%s",
\r
6954 (forwardMostMove / 2) + 1,
\r
6955 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6956 DisplayError(move, 0);
\r
6959 if (appData.debugMode)
\r
6960 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
6961 yy_text, currentMoveString);
\r
6962 fromX = currentMoveString[0] - AAA;
\r
6963 fromY = currentMoveString[1] - ONE;
\r
6964 toX = currentMoveString[2] - AAA;
\r
6965 toY = currentMoveString[3] - ONE;
\r
6966 promoChar = currentMoveString[4];
\r
6970 case AmbiguousMove:
\r
6971 if (appData.debugMode)
\r
6972 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
6973 sprintf(move, "Ambiguous move: %d.%s%s",
\r
6974 (forwardMostMove / 2) + 1,
\r
6975 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6976 DisplayError(move, 0);
\r
6981 case ImpossibleMove:
\r
6982 if (appData.debugMode)
\r
6983 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
6984 sprintf(move, "Illegal move: %d.%s%s",
\r
6985 (forwardMostMove / 2) + 1,
\r
6986 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6987 DisplayError(move, 0);
\r
6993 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
6994 DrawPosition(FALSE, boards[currentMove]);
\r
6995 DisplayBothClocks();
\r
6996 if (!appData.matchMode && commentList[currentMove] != NULL)
\r
6997 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6999 (void) StopLoadGameTimer();
\r
7000 gameFileFP = NULL;
\r
7001 cmailOldMove = forwardMostMove;
\r
7004 /* currentMoveString is set as a side-effect of yylex */
\r
7005 strcat(currentMoveString, "\n");
\r
7006 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7008 thinkOutput[0] = NULLCHAR;
\r
7009 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7010 currentMove = forwardMostMove;
\r
7015 /* Load the nth game from the given file */
\r
7017 LoadGameFromFile(filename, n, title, useList)
\r
7021 /*Boolean*/ int useList;
\r
7024 char buf[MSG_SIZ];
\r
7026 if (strcmp(filename, "-") == 0) {
\r
7030 f = fopen(filename, "rb");
\r
7032 sprintf(buf, "Can't open \"%s\"", filename);
\r
7033 DisplayError(buf, errno);
\r
7037 if (fseek(f, 0, 0) == -1) {
\r
7038 /* f is not seekable; probably a pipe */
\r
7041 if (useList && n == 0) {
\r
7042 int error = GameListBuild(f);
\r
7044 DisplayError("Cannot build game list", error);
\r
7045 } else if (!ListEmpty(&gameList) &&
\r
7046 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7047 GameListPopUp(f, title);
\r
7050 GameListDestroy();
\r
7053 if (n == 0) n = 1;
\r
7054 return LoadGame(f, n, title, FALSE);
\r
7059 MakeRegisteredMove()
\r
7061 int fromX, fromY, toX, toY;
\r
7063 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7064 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
7067 if (appData.debugMode)
\r
7068 fprintf(debugFP, "Restoring %s for game %d\n",
\r
7069 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
7071 thinkOutput[0] = NULLCHAR;
\r
7072 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
7073 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
7074 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
7075 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
7076 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
7077 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
7078 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7079 ShowMove(fromX, fromY, toX, toY);
\r
7081 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7082 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7087 case MT_CHECKMATE:
\r
7088 if (WhiteOnMove(currentMove)) {
\r
7089 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
7091 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
7095 case MT_STALEMATE:
\r
7096 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
7102 case CMAIL_RESIGN:
\r
7103 if (WhiteOnMove(currentMove)) {
\r
7104 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
7106 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
7110 case CMAIL_ACCEPT:
\r
7111 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
7122 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
7124 CmailLoadGame(f, gameNumber, title, useList)
\r
7132 if (gameNumber > nCmailGames) {
\r
7133 DisplayError("No more games in this message", 0);
\r
7136 if (f == lastLoadGameFP) {
\r
7137 int offset = gameNumber - lastLoadGameNumber;
\r
7138 if (offset == 0) {
\r
7139 cmailMsg[0] = NULLCHAR;
\r
7140 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7141 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
7142 nCmailMovesRegistered--;
\r
7144 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
7145 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
7146 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
7149 if (! RegisterMove()) return FALSE;
\r
7153 retVal = LoadGame(f, gameNumber, title, useList);
\r
7155 /* Make move registered during previous look at this game, if any */
\r
7156 MakeRegisteredMove();
\r
7158 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
7159 commentList[currentMove]
\r
7160 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
7161 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7167 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
7169 ReloadGame(offset)
\r
7172 int gameNumber = lastLoadGameNumber + offset;
\r
7173 if (lastLoadGameFP == NULL) {
\r
7174 DisplayError("No game has been loaded yet", 0);
\r
7177 if (gameNumber <= 0) {
\r
7178 DisplayError("Can't back up any further", 0);
\r
7181 if (cmailMsgLoaded) {
\r
7182 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
7183 lastLoadGameTitle, lastLoadGameUseList);
\r
7185 return LoadGame(lastLoadGameFP, gameNumber,
\r
7186 lastLoadGameTitle, lastLoadGameUseList);
\r
7192 /* Load the nth game from open file f */
\r
7194 LoadGame(f, gameNumber, title, useList)
\r
7201 char buf[MSG_SIZ];
\r
7202 int gn = gameNumber;
\r
7203 ListGame *lg = NULL;
\r
7204 int numPGNTags = 0;
\r
7206 GameMode oldGameMode;
\r
7208 if (appData.debugMode)
\r
7209 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
7211 if (gameMode == Training )
\r
7212 SetTrainingModeOff();
\r
7214 oldGameMode = gameMode;
\r
7215 if (gameMode != BeginningOfGame) {
\r
7216 Reset(FALSE, TRUE);
\r
7220 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
7221 fclose(lastLoadGameFP);
\r
7225 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
7228 fseek(f, lg->offset, 0);
\r
7229 GameListHighlight(gameNumber);
\r
7233 DisplayError("Game number out of range", 0);
\r
7237 GameListDestroy();
\r
7238 if (fseek(f, 0, 0) == -1) {
\r
7239 if (f == lastLoadGameFP ?
\r
7240 gameNumber == lastLoadGameNumber + 1 :
\r
7241 gameNumber == 1) {
\r
7244 DisplayError("Can't seek on game file", 0);
\r
7249 lastLoadGameFP = f;
\r
7250 lastLoadGameNumber = gameNumber;
\r
7251 strcpy(lastLoadGameTitle, title);
\r
7252 lastLoadGameUseList = useList;
\r
7257 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
7258 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
7259 lg->gameInfo.black);
\r
7260 DisplayTitle(buf);
\r
7261 } else if (*title != NULLCHAR) {
\r
7262 if (gameNumber > 1) {
\r
7263 sprintf(buf, "%s %d", title, gameNumber);
\r
7264 DisplayTitle(buf);
\r
7266 DisplayTitle(title);
\r
7270 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
7271 gameMode = PlayFromGameFile;
\r
7275 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7276 CopyBoard(boards[0], initialPosition);
\r
7280 * Skip the first gn-1 games in the file.
\r
7281 * Also skip over anything that precedes an identifiable
\r
7282 * start of game marker, to avoid being confused by
\r
7283 * garbage at the start of the file. Currently
\r
7284 * recognized start of game markers are the move number "1",
\r
7285 * the pattern "gnuchess .* game", the pattern
\r
7286 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
7287 * A game that starts with one of the latter two patterns
\r
7288 * will also have a move number 1, possibly
\r
7289 * following a position diagram.
\r
7290 * 5-4-02: Let's try being more lenient and allowing a game to
\r
7291 * start with an unnumbered move. Does that break anything?
\r
7293 cm = lastLoadGameStart = (ChessMove) 0;
\r
7295 yyboardindex = forwardMostMove;
\r
7296 cm = (ChessMove) yylex();
\r
7298 case (ChessMove) 0:
\r
7299 if (cmailMsgLoaded) {
\r
7300 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
7302 Reset(TRUE, TRUE);
\r
7303 DisplayError("Game not found in file", 0);
\r
7307 case GNUChessGame:
\r
7310 lastLoadGameStart = cm;
\r
7313 case MoveNumberOne:
\r
7314 switch (lastLoadGameStart) {
\r
7315 case GNUChessGame:
\r
7319 case MoveNumberOne:
\r
7320 case (ChessMove) 0:
\r
7321 gn--; /* count this game */
\r
7322 lastLoadGameStart = cm;
\r
7331 switch (lastLoadGameStart) {
\r
7332 case GNUChessGame:
\r
7334 case MoveNumberOne:
\r
7335 case (ChessMove) 0:
\r
7336 gn--; /* count this game */
\r
7337 lastLoadGameStart = cm;
\r
7340 lastLoadGameStart = cm; /* game counted already */
\r
7348 yyboardindex = forwardMostMove;
\r
7349 cm = (ChessMove) yylex();
\r
7350 } while (cm == PGNTag || cm == Comment);
\r
7357 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
7358 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
7359 != CMAIL_OLD_RESULT) {
\r
7360 nCmailResults ++ ;
\r
7361 cmailResult[ CMAIL_MAX_GAMES
\r
7362 - gn - 1] = CMAIL_OLD_RESULT;
\r
7368 /* Only a NormalMove can be at the start of a game
\r
7369 * without a position diagram. */
\r
7370 if (lastLoadGameStart == (ChessMove) 0) {
\r
7372 lastLoadGameStart = MoveNumberOne;
\r
7381 if (appData.debugMode)
\r
7382 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
7384 if (cm == XBoardGame) {
\r
7385 /* Skip any header junk before position diagram and/or move 1 */
\r
7387 yyboardindex = forwardMostMove;
\r
7388 cm = (ChessMove) yylex();
\r
7390 if (cm == (ChessMove) 0 ||
\r
7391 cm == GNUChessGame || cm == XBoardGame) {
\r
7392 /* Empty game; pretend end-of-file and handle later */
\r
7393 cm = (ChessMove) 0;
\r
7397 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
7398 cm == PGNTag || cm == Comment)
\r
7401 } else if (cm == GNUChessGame) {
\r
7402 if (gameInfo.event != NULL) {
\r
7403 free(gameInfo.event);
\r
7405 gameInfo.event = StrSave(yy_text);
\r
7408 startedFromSetupPosition = FALSE;
\r
7409 while (cm == PGNTag) {
\r
7410 if (appData.debugMode)
\r
7411 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
7412 err = ParsePGNTag(yy_text, &gameInfo);
\r
7413 if (!err) numPGNTags++;
\r
7415 if (gameInfo.fen != NULL) {
\r
7416 Board initial_position;
\r
7417 startedFromSetupPosition = TRUE;
\r
7418 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
7419 Reset(TRUE, TRUE);
\r
7420 DisplayError("Bad FEN position in file", 0);
\r
7423 CopyBoard(boards[0], initial_position);
\r
7424 /* [HGM] copy FEN attributes as well */
\r
7426 initialRulePlies = FENrulePlies;
\r
7427 epStatus[0] = FENepStatus;
\r
7428 for( i=0; i< nrCastlingRights; i++ )
\r
7429 castlingRights[0][i] = FENcastlingRights[i];
\r
7431 if (blackPlaysFirst) {
\r
7432 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7433 CopyBoard(boards[1], initial_position);
\r
7434 strcpy(moveList[0], "");
\r
7435 strcpy(parseList[0], "");
\r
7436 timeRemaining[0][1] = whiteTimeRemaining;
\r
7437 timeRemaining[1][1] = blackTimeRemaining;
\r
7438 if (commentList[0] != NULL) {
\r
7439 commentList[1] = commentList[0];
\r
7440 commentList[0] = NULL;
\r
7443 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7445 yyboardindex = forwardMostMove;
\r
7446 free(gameInfo.fen);
\r
7447 gameInfo.fen = NULL;
\r
7450 yyboardindex = forwardMostMove;
\r
7451 cm = (ChessMove) yylex();
\r
7453 /* Handle comments interspersed among the tags */
\r
7454 while (cm == Comment) {
\r
7456 if (appData.debugMode)
\r
7457 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7459 if (*p == '{' || *p == '[' || *p == '(') {
\r
7460 p[strlen(p) - 1] = NULLCHAR;
\r
7463 while (*p == '\n') p++;
\r
7464 AppendComment(currentMove, p);
\r
7465 yyboardindex = forwardMostMove;
\r
7466 cm = (ChessMove) yylex();
\r
7470 /* don't rely on existence of Event tag since if game was
\r
7471 * pasted from clipboard the Event tag may not exist
\r
7473 if (numPGNTags > 0){
\r
7475 if (gameInfo.variant == VariantNormal) {
\r
7476 gameInfo.variant = StringToVariant(gameInfo.event);
\r
7479 if( appData.autoDisplayTags ) {
\r
7480 tags = PGNTags(&gameInfo);
\r
7481 TagsPopUp(tags, CmailMsg());
\r
7486 /* Make something up, but don't display it now */
\r
7491 if (cm == PositionDiagram) {
\r
7494 Board initial_position;
\r
7496 if (appData.debugMode)
\r
7497 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
7499 if (!startedFromSetupPosition) {
\r
7501 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
7502 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
7512 initial_position[i][j++] = CharToPiece(*p);
\r
7515 while (*p == ' ' || *p == '\t' ||
\r
7516 *p == '\n' || *p == '\r') p++;
\r
7518 if (strncmp(p, "black", strlen("black"))==0)
\r
7519 blackPlaysFirst = TRUE;
\r
7521 blackPlaysFirst = FALSE;
\r
7522 startedFromSetupPosition = TRUE;
\r
7524 CopyBoard(boards[0], initial_position);
\r
7525 if (blackPlaysFirst) {
\r
7526 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7527 CopyBoard(boards[1], initial_position);
\r
7528 strcpy(moveList[0], "");
\r
7529 strcpy(parseList[0], "");
\r
7530 timeRemaining[0][1] = whiteTimeRemaining;
\r
7531 timeRemaining[1][1] = blackTimeRemaining;
\r
7532 if (commentList[0] != NULL) {
\r
7533 commentList[1] = commentList[0];
\r
7534 commentList[0] = NULL;
\r
7537 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7540 yyboardindex = forwardMostMove;
\r
7541 cm = (ChessMove) yylex();
\r
7544 if (first.pr == NoProc) {
\r
7545 StartChessProgram(&first);
\r
7547 InitChessProgram(&first);
\r
7548 SendToProgram("force\n", &first);
\r
7549 if (startedFromSetupPosition) {
\r
7550 SendBoard(&first, forwardMostMove);
\r
7551 DisplayBothClocks();
\r
7554 while (cm == Comment) {
\r
7556 if (appData.debugMode)
\r
7557 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7559 if (*p == '{' || *p == '[' || *p == '(') {
\r
7560 p[strlen(p) - 1] = NULLCHAR;
\r
7563 while (*p == '\n') p++;
\r
7564 AppendComment(currentMove, p);
\r
7565 yyboardindex = forwardMostMove;
\r
7566 cm = (ChessMove) yylex();
\r
7569 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
7570 cm == WhiteWins || cm == BlackWins ||
\r
7571 cm == GameIsDrawn || cm == GameUnfinished) {
\r
7572 DisplayMessage("", "No moves in game");
\r
7573 if (cmailMsgLoaded) {
\r
7574 if (appData.debugMode)
\r
7575 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
7576 ClearHighlights();
\r
7579 DrawPosition(FALSE, boards[currentMove]);
\r
7580 DisplayBothClocks();
\r
7581 gameMode = EditGame;
\r
7583 gameFileFP = NULL;
\r
7588 if (commentList[currentMove] != NULL) {
\r
7589 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
7590 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7593 if (!matchMode && appData.timeDelay != 0)
\r
7594 DrawPosition(FALSE, boards[currentMove]);
\r
7596 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
7597 programStats.ok_to_send = 1;
\r
7600 /* if the first token after the PGN tags is a move
\r
7601 * and not move number 1, retrieve it from the parser
\r
7603 if (cm != MoveNumberOne)
\r
7604 LoadGameOneMove(cm);
\r
7606 /* load the remaining moves from the file */
\r
7607 while (LoadGameOneMove((ChessMove)0)) {
\r
7608 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7609 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7612 /* rewind to the start of the game */
\r
7613 currentMove = backwardMostMove;
\r
7615 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7617 if (oldGameMode == AnalyzeFile ||
\r
7618 oldGameMode == AnalyzeMode) {
\r
7619 AnalyzeFileEvent();
\r
7622 if (matchMode || appData.timeDelay == 0) {
\r
7624 gameMode = EditGame;
\r
7626 } else if (appData.timeDelay > 0) {
\r
7627 AutoPlayGameLoop();
\r
7630 if (appData.debugMode)
\r
7631 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
7635 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
7637 ReloadPosition(offset)
\r
7640 int positionNumber = lastLoadPositionNumber + offset;
\r
7641 if (lastLoadPositionFP == NULL) {
\r
7642 DisplayError("No position has been loaded yet", 0);
\r
7645 if (positionNumber <= 0) {
\r
7646 DisplayError("Can't back up any further", 0);
\r
7649 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
7650 lastLoadPositionTitle);
\r
7653 /* Load the nth position from the given file */
\r
7655 LoadPositionFromFile(filename, n, title)
\r
7661 char buf[MSG_SIZ];
\r
7663 if (strcmp(filename, "-") == 0) {
\r
7664 return LoadPosition(stdin, n, "stdin");
\r
7666 f = fopen(filename, "rb");
\r
7668 sprintf(buf, "Can't open \"%s\"", filename);
\r
7669 DisplayError(buf, errno);
\r
7672 return LoadPosition(f, n, title);
\r
7677 /* Load the nth position from the given open file, and close it */
\r
7679 LoadPosition(f, positionNumber, title)
\r
7681 int positionNumber;
\r
7684 char *p, line[MSG_SIZ];
\r
7685 Board initial_position;
\r
7686 int i, j, fenMode, pn;
\r
7688 if (gameMode == Training )
\r
7689 SetTrainingModeOff();
\r
7691 if (gameMode != BeginningOfGame) {
\r
7692 Reset(FALSE, TRUE);
\r
7694 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
7695 fclose(lastLoadPositionFP);
\r
7697 if (positionNumber == 0) positionNumber = 1;
\r
7698 lastLoadPositionFP = f;
\r
7699 lastLoadPositionNumber = positionNumber;
\r
7700 strcpy(lastLoadPositionTitle, title);
\r
7701 if (first.pr == NoProc) {
\r
7702 StartChessProgram(&first);
\r
7703 InitChessProgram(&first);
\r
7705 pn = positionNumber;
\r
7706 if (positionNumber < 0) {
\r
7707 /* Negative position number means to seek to that byte offset */
\r
7708 if (fseek(f, -positionNumber, 0) == -1) {
\r
7709 DisplayError("Can't seek on position file", 0);
\r
7714 if (fseek(f, 0, 0) == -1) {
\r
7715 if (f == lastLoadPositionFP ?
\r
7716 positionNumber == lastLoadPositionNumber + 1 :
\r
7717 positionNumber == 1) {
\r
7720 DisplayError("Can't seek on position file", 0);
\r
7725 /* See if this file is FEN or old-style xboard */
\r
7726 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7727 DisplayError("Position not found in file", 0);
\r
7730 switch (line[0]) {
\r
7731 case '#': case 'x':
\r
7735 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
7736 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
7737 case '1': case '2': case '3': case '4': case '5': case '6':
\r
7738 case '7': case '8': case '9':
\r
7740 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
7741 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
7742 case 'C': case 'W': case 'c': case 'w':
\r
7749 if (fenMode || line[0] == '#') pn--;
\r
7751 /* skip postions before number pn */
\r
7752 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7753 Reset(TRUE, TRUE);
\r
7754 DisplayError("Position not found in file", 0);
\r
7757 if (fenMode || line[0] == '#') pn--;
\r
7762 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
7763 DisplayError("Bad FEN position in file", 0);
\r
7767 (void) fgets(line, MSG_SIZ, f);
\r
7768 (void) fgets(line, MSG_SIZ, f);
\r
7770 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
7771 (void) fgets(line, MSG_SIZ, f);
\r
7772 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
7775 initial_position[i][j++] = CharToPiece(*p);
\r
7779 blackPlaysFirst = FALSE;
\r
7781 (void) fgets(line, MSG_SIZ, f);
\r
7782 if (strncmp(line, "black", strlen("black"))==0)
\r
7783 blackPlaysFirst = TRUE;
\r
7786 startedFromSetupPosition = TRUE;
\r
7788 SendToProgram("force\n", &first);
\r
7789 CopyBoard(boards[0], initial_position);
\r
7790 /* [HGM] copy FEN attributes as well */
\r
7792 initialRulePlies = FENrulePlies;
\r
7793 epStatus[0] = FENepStatus;
\r
7794 for( i=0; i< nrCastlingRights; i++ )
\r
7795 castlingRights[0][i] = FENcastlingRights[i];
\r
7797 if (blackPlaysFirst) {
\r
7798 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7799 strcpy(moveList[0], "");
\r
7800 strcpy(parseList[0], "");
\r
7801 CopyBoard(boards[1], initial_position);
\r
7802 DisplayMessage("", "Black to play");
\r
7804 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7805 DisplayMessage("", "White to play");
\r
7807 SendBoard(&first, forwardMostMove);
\r
7809 if (positionNumber > 1) {
\r
7810 sprintf(line, "%s %d", title, positionNumber);
\r
7811 DisplayTitle(line);
\r
7813 DisplayTitle(title);
\r
7815 gameMode = EditGame;
\r
7818 timeRemaining[0][1] = whiteTimeRemaining;
\r
7819 timeRemaining[1][1] = blackTimeRemaining;
\r
7820 DrawPosition(FALSE, boards[currentMove]);
\r
7827 CopyPlayerNameIntoFileName(dest, src)
\r
7828 char **dest, *src;
\r
7830 while (*src != NULLCHAR && *src != ',') {
\r
7831 if (*src == ' ') {
\r
7835 *(*dest)++ = *src++;
\r
7840 char *DefaultFileName(ext)
\r
7843 static char def[MSG_SIZ];
\r
7846 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
7848 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
7850 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
7854 def[0] = NULLCHAR;
\r
7859 /* Save the current game to the given file */
\r
7861 SaveGameToFile(filename, append)
\r
7866 char buf[MSG_SIZ];
\r
7868 if (strcmp(filename, "-") == 0) {
\r
7869 return SaveGame(stdout, 0, NULL);
\r
7871 f = fopen(filename, append ? "a" : "w");
\r
7873 sprintf(buf, "Can't open \"%s\"", filename);
\r
7874 DisplayError(buf, errno);
\r
7877 return SaveGame(f, 0, NULL);
\r
7886 static char buf[MSG_SIZ];
\r
7889 p = strchr(str, ' ');
\r
7890 if (p == NULL) return str;
\r
7891 strncpy(buf, str, p - str);
\r
7892 buf[p - str] = NULLCHAR;
\r
7896 #define PGN_MAX_LINE 75
\r
7898 #define PGN_SIDE_WHITE 0
\r
7899 #define PGN_SIDE_BLACK 1
\r
7902 static int FindFirstMoveOutOfBook( int side )
\r
7906 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
7907 int index = backwardMostMove;
\r
7908 int has_book_hit = 0;
\r
7910 if( (index % 2) != side ) {
\r
7914 while( index < forwardMostMove ) {
\r
7915 /* Check to see if engine is in book */
\r
7916 int depth = pvInfoList[index].depth;
\r
7917 int score = pvInfoList[index].score;
\r
7920 if( depth <= 2 ) {
\r
7923 else if( score == 0 && depth == 63 ) {
\r
7924 in_book = 1; /* Zappa */
\r
7926 else if( score == 2 && depth == 99 ) {
\r
7927 in_book = 1; /* Abrok */
\r
7930 has_book_hit += in_book;
\r
7946 void GetOutOfBookInfo( char * buf )
\r
7950 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
7952 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
7953 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
7957 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
7958 for( i=0; i<2; i++ ) {
\r
7962 if( i > 0 && oob[0] >= 0 ) {
\r
7963 strcat( buf, " " );
\r
7966 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
7967 sprintf( buf+strlen(buf), "%s%.2f",
\r
7968 pvInfoList[idx].score >= 0 ? "+" : "",
\r
7969 pvInfoList[idx].score / 100.0 );
\r
7975 /* Save game in PGN style and close the file */
\r
7980 int i, offset, linelen, newblock;
\r
7984 int movelen, numlen, blank;
\r
7985 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
7987 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
7989 tm = time((time_t *) NULL);
\r
7991 PrintPGNTags(f, &gameInfo);
\r
7993 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
7994 char *fen = PositionToFEN(backwardMostMove, 1);
\r
7995 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
7996 fprintf(f, "\n{--------------\n");
\r
7997 PrintPosition(f, backwardMostMove);
\r
7998 fprintf(f, "--------------}\n");
\r
8002 /* [AS] Out of book annotation */
\r
8003 if( appData.saveOutOfBookInfo ) {
\r
8006 GetOutOfBookInfo( buf );
\r
8008 if( buf[0] != '\0' ) {
\r
8009 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8016 i = backwardMostMove;
\r
8020 while (i < forwardMostMove) {
\r
8021 /* Print comments preceding this move */
\r
8022 if (commentList[i] != NULL) {
\r
8023 if (linelen > 0) fprintf(f, "\n");
\r
8024 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8029 /* Format move number */
\r
8030 if ((i % 2) == 0) {
\r
8031 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8034 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
8036 numtext[0] = NULLCHAR;
\r
8039 numlen = strlen(numtext);
\r
8042 /* Print move number */
\r
8043 blank = linelen > 0 && numlen > 0;
\r
8044 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
8053 fprintf(f, numtext);
\r
8054 linelen += numlen;
\r
8057 movetext = SavePart(parseList[i]);
\r
8059 /* [AS] Add PV info if present */
\r
8060 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
8061 /* [HGM] add time */
\r
8062 char buf[MSG_SIZ]; int seconds = 0;
\r
8064 if(i >= backwardMostMove) {
\r
8065 /* take the time that changed */
\r
8066 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
8068 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
8071 if (appData.debugMode) {
\r
8072 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
8073 timeRemaining[0][i+1], timeRemaining[0][i],
\r
8074 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
8078 if( seconds < 0 ) buf[0] = 0; else
\r
8079 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
8080 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
8082 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
8084 pvInfoList[i].score >= 0 ? "+" : "",
\r
8085 pvInfoList[i].score / 100.0,
\r
8086 pvInfoList[i].depth,
\r
8088 movetext = move_buffer;
\r
8091 movelen = strlen(movetext);
\r
8094 blank = linelen > 0 && movelen > 0;
\r
8095 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
8104 fprintf(f, movetext);
\r
8105 linelen += movelen;
\r
8110 /* Start a new line */
\r
8111 if (linelen > 0) fprintf(f, "\n");
\r
8113 /* Print comments after last move */
\r
8114 if (commentList[i] != NULL) {
\r
8115 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8118 /* Print result */
\r
8119 if (gameInfo.resultDetails != NULL &&
\r
8120 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8121 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
8122 PGNResult(gameInfo.result));
\r
8124 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8131 /* Save game in old style and close the file */
\r
8133 SaveGameOldStyle(f)
\r
8139 tm = time((time_t *) NULL);
\r
8141 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
8142 PrintOpponents(f);
\r
8144 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8145 fprintf(f, "\n[--------------\n");
\r
8146 PrintPosition(f, backwardMostMove);
\r
8147 fprintf(f, "--------------]\n");
\r
8152 i = backwardMostMove;
\r
8153 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8155 while (i < forwardMostMove) {
\r
8156 if (commentList[i] != NULL) {
\r
8157 fprintf(f, "[%s]\n", commentList[i]);
\r
8160 if ((i % 2) == 1) {
\r
8161 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
8164 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
8166 if (commentList[i] != NULL) {
\r
8170 if (i >= forwardMostMove) {
\r
8174 fprintf(f, "%s\n", parseList[i]);
\r
8179 if (commentList[i] != NULL) {
\r
8180 fprintf(f, "[%s]\n", commentList[i]);
\r
8183 /* This isn't really the old style, but it's close enough */
\r
8184 if (gameInfo.resultDetails != NULL &&
\r
8185 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8186 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
8187 gameInfo.resultDetails);
\r
8189 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8196 /* Save the current game to open file f and close the file */
\r
8198 SaveGame(f, dummy, dummy2)
\r
8203 if (gameMode == EditPosition) EditPositionDone();
\r
8204 if (appData.oldSaveStyle)
\r
8205 return SaveGameOldStyle(f);
\r
8207 return SaveGamePGN(f);
\r
8210 /* Save the current position to the given file */
\r
8212 SavePositionToFile(filename)
\r
8216 char buf[MSG_SIZ];
\r
8218 if (strcmp(filename, "-") == 0) {
\r
8219 return SavePosition(stdout, 0, NULL);
\r
8221 f = fopen(filename, "a");
\r
8223 sprintf(buf, "Can't open \"%s\"", filename);
\r
8224 DisplayError(buf, errno);
\r
8227 SavePosition(f, 0, NULL);
\r
8233 /* Save the current position to the given open file and close the file */
\r
8235 SavePosition(f, dummy, dummy2)
\r
8243 if (appData.oldSaveStyle) {
\r
8244 tm = time((time_t *) NULL);
\r
8246 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
8247 PrintOpponents(f);
\r
8248 fprintf(f, "[--------------\n");
\r
8249 PrintPosition(f, currentMove);
\r
8250 fprintf(f, "--------------]\n");
\r
8252 fen = PositionToFEN(currentMove, 1);
\r
8253 fprintf(f, "%s\n", fen);
\r
8261 ReloadCmailMsgEvent(unregister)
\r
8265 static char *inFilename = NULL;
\r
8266 static char *outFilename;
\r
8268 struct stat inbuf, outbuf;
\r
8271 /* Any registered moves are unregistered if unregister is set, */
\r
8272 /* i.e. invoked by the signal handler */
\r
8274 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8275 cmailMoveRegistered[i] = FALSE;
\r
8276 if (cmailCommentList[i] != NULL) {
\r
8277 free(cmailCommentList[i]);
\r
8278 cmailCommentList[i] = NULL;
\r
8281 nCmailMovesRegistered = 0;
\r
8284 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8285 cmailResult[i] = CMAIL_NOT_RESULT;
\r
8287 nCmailResults = 0;
\r
8289 if (inFilename == NULL) {
\r
8290 /* Because the filenames are static they only get malloced once */
\r
8291 /* and they never get freed */
\r
8292 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
8293 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
8295 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
8296 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
8299 status = stat(outFilename, &outbuf);
\r
8301 cmailMailedMove = FALSE;
\r
8303 status = stat(inFilename, &inbuf);
\r
8304 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
8307 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
8308 counts the games, notes how each one terminated, etc.
\r
8310 It would be nice to remove this kludge and instead gather all
\r
8311 the information while building the game list. (And to keep it
\r
8312 in the game list nodes instead of having a bunch of fixed-size
\r
8313 parallel arrays.) Note this will require getting each game's
\r
8314 termination from the PGN tags, as the game list builder does
\r
8315 not process the game moves. --mann
\r
8317 cmailMsgLoaded = TRUE;
\r
8318 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
8320 /* Load first game in the file or popup game menu */
\r
8321 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
8323 #endif /* !WIN32 */
\r
8331 char string[MSG_SIZ];
\r
8333 if ( cmailMailedMove
\r
8334 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
8335 return TRUE; /* Allow free viewing */
\r
8338 /* Unregister move to ensure that we don't leave RegisterMove */
\r
8339 /* with the move registered when the conditions for registering no */
\r
8341 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8342 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8343 nCmailMovesRegistered --;
\r
8345 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
8347 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
8348 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
8352 if (cmailOldMove == -1) {
\r
8353 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
8357 if (currentMove > cmailOldMove + 1) {
\r
8358 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
8362 if (currentMove < cmailOldMove) {
\r
8363 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
8367 if (forwardMostMove > currentMove) {
\r
8368 /* Silently truncate extra moves */
\r
8372 if ( (currentMove == cmailOldMove + 1)
\r
8373 || ( (currentMove == cmailOldMove)
\r
8374 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
8375 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
8376 if (gameInfo.result != GameUnfinished) {
\r
8377 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
8380 if (commentList[currentMove] != NULL) {
\r
8381 cmailCommentList[lastLoadGameNumber - 1]
\r
8382 = StrSave(commentList[currentMove]);
\r
8384 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
8386 if (appData.debugMode)
\r
8387 fprintf(debugFP, "Saving %s for game %d\n",
\r
8388 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8391 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
8393 f = fopen(string, "w");
\r
8394 if (appData.oldSaveStyle) {
\r
8395 SaveGameOldStyle(f); /* also closes the file */
\r
8397 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
8398 f = fopen(string, "w");
\r
8399 SavePosition(f, 0, NULL); /* also closes the file */
\r
8401 fprintf(f, "{--------------\n");
\r
8402 PrintPosition(f, currentMove);
\r
8403 fprintf(f, "--------------}\n\n");
\r
8405 SaveGame(f, 0, NULL); /* also closes the file*/
\r
8408 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
8409 nCmailMovesRegistered ++;
\r
8410 } else if (nCmailGames == 1) {
\r
8411 DisplayError("You have not made a move yet", 0);
\r
8422 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
8423 FILE *commandOutput;
\r
8424 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
8425 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
8431 if (! cmailMsgLoaded) {
\r
8432 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
8436 if (nCmailGames == nCmailResults) {
\r
8437 DisplayError("No unfinished games", 0);
\r
8441 #if CMAIL_PROHIBIT_REMAIL
\r
8442 if (cmailMailedMove) {
\r
8443 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
8444 DisplayError(msg, 0);
\r
8449 if (! (cmailMailedMove || RegisterMove())) return;
\r
8451 if ( cmailMailedMove
\r
8452 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
8453 sprintf(string, partCommandString,
\r
8454 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
8455 commandOutput = popen(string, "rb");
\r
8457 if (commandOutput == NULL) {
\r
8458 DisplayError("Failed to invoke cmail", 0);
\r
8460 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
8461 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
8463 if (nBuffers > 1) {
\r
8464 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
8465 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
8466 nBytes = MSG_SIZ - 1;
\r
8468 (void) memcpy(msg, buffer, nBytes);
\r
8470 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
8472 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
8473 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
8476 for (i = 0; i < nCmailGames; i ++) {
\r
8477 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
8482 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
8484 sprintf(buffer, "%s/%s.%s.archive",
\r
8486 appData.cmailGameName,
\r
8488 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
8489 cmailMsgLoaded = FALSE;
\r
8493 DisplayInformation(msg);
\r
8494 pclose(commandOutput);
\r
8497 if ((*cmailMsg) != '\0') {
\r
8498 DisplayInformation(cmailMsg);
\r
8503 #endif /* !WIN32 */
\r
8512 int prependComma = 0;
\r
8514 char string[MSG_SIZ]; /* Space for game-list */
\r
8517 if (!cmailMsgLoaded) return "";
\r
8519 if (cmailMailedMove) {
\r
8520 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
8522 /* Create a list of games left */
\r
8523 sprintf(string, "[");
\r
8524 for (i = 0; i < nCmailGames; i ++) {
\r
8525 if (! ( cmailMoveRegistered[i]
\r
8526 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
8527 if (prependComma) {
\r
8528 sprintf(number, ",%d", i + 1);
\r
8530 sprintf(number, "%d", i + 1);
\r
8534 strcat(string, number);
\r
8537 strcat(string, "]");
\r
8539 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
8540 switch (nCmailGames) {
\r
8543 "Still need to make move for game\n");
\r
8548 "Still need to make moves for both games\n");
\r
8553 "Still need to make moves for all %d games\n",
\r
8558 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
8561 "Still need to make a move for game %s\n",
\r
8566 if (nCmailResults == nCmailGames) {
\r
8567 sprintf(cmailMsg, "No unfinished games\n");
\r
8569 sprintf(cmailMsg, "Ready to send mail\n");
\r
8575 "Still need to make moves for games %s\n",
\r
8581 #endif /* WIN32 */
\r
8587 if (gameMode == Training)
\r
8588 SetTrainingModeOff();
\r
8590 Reset(TRUE, TRUE);
\r
8591 cmailMsgLoaded = FALSE;
\r
8592 if (appData.icsActive) {
\r
8593 SendToICS(ics_prefix);
\r
8594 SendToICS("refresh\n");
\r
8598 static int exiting = 0;
\r
8605 if (exiting > 2) {
\r
8606 /* Give up on clean exit */
\r
8609 if (exiting > 1) {
\r
8610 /* Keep trying for clean exit */
\r
8614 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
8616 if (telnetISR != NULL) {
\r
8617 RemoveInputSource(telnetISR);
\r
8619 if (icsPR != NoProc) {
\r
8620 DestroyChildProcess(icsPR, TRUE);
\r
8622 /* Save game if resource set and not already saved by GameEnds() */
\r
8623 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
\r
8624 if (*appData.saveGameFile != NULLCHAR) {
\r
8625 SaveGameToFile(appData.saveGameFile, TRUE);
\r
8626 } else if (appData.autoSaveGames) {
\r
8629 if (*appData.savePositionFile != NULLCHAR) {
\r
8630 SavePositionToFile(appData.savePositionFile);
\r
8633 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8635 /* Kill off chess programs */
\r
8636 if (first.pr != NoProc) {
\r
8637 ExitAnalyzeMode();
\r
8639 DoSleep( appData.delayBeforeQuit );
\r
8640 SendToProgram("quit\n", &first);
\r
8641 DoSleep( appData.delayAfterQuit );
\r
8642 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
8644 if (second.pr != NoProc) {
\r
8645 DoSleep( appData.delayBeforeQuit );
\r
8646 SendToProgram("quit\n", &second);
\r
8647 DoSleep( appData.delayAfterQuit );
\r
8648 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
8650 if (first.isr != NULL) {
\r
8651 RemoveInputSource(first.isr);
\r
8653 if (second.isr != NULL) {
\r
8654 RemoveInputSource(second.isr);
\r
8657 ShutDownFrontEnd();
\r
8664 if (appData.debugMode)
\r
8665 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
8669 if (gameMode == MachinePlaysWhite ||
\r
8670 gameMode == MachinePlaysBlack) {
\r
8673 DisplayBothClocks();
\r
8675 if (gameMode == PlayFromGameFile) {
\r
8676 if (appData.timeDelay >= 0)
\r
8677 AutoPlayGameLoop();
\r
8678 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
8679 Reset(FALSE, TRUE);
\r
8680 SendToICS(ics_prefix);
\r
8681 SendToICS("refresh\n");
\r
8682 } else if (currentMove < forwardMostMove) {
\r
8683 ForwardInner(forwardMostMove);
\r
8685 pauseExamInvalid = FALSE;
\r
8687 switch (gameMode) {
\r
8690 case IcsExamining:
\r
8691 pauseExamForwardMostMove = forwardMostMove;
\r
8692 pauseExamInvalid = FALSE;
\r
8693 /* fall through */
\r
8694 case IcsObserving:
\r
8695 case IcsPlayingWhite:
\r
8696 case IcsPlayingBlack:
\r
8700 case PlayFromGameFile:
\r
8701 (void) StopLoadGameTimer();
\r
8705 case BeginningOfGame:
\r
8706 if (appData.icsActive) return;
\r
8707 /* else fall through */
\r
8708 case MachinePlaysWhite:
\r
8709 case MachinePlaysBlack:
\r
8710 case TwoMachinesPlay:
\r
8711 if (forwardMostMove == 0)
\r
8712 return; /* don't pause if no one has moved */
\r
8713 if ((gameMode == MachinePlaysWhite &&
\r
8714 !WhiteOnMove(forwardMostMove)) ||
\r
8715 (gameMode == MachinePlaysBlack &&
\r
8716 WhiteOnMove(forwardMostMove))) {
\r
8727 EditCommentEvent()
\r
8729 char title[MSG_SIZ];
\r
8731 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
8732 strcpy(title, "Edit comment");
\r
8734 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
8735 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
8736 parseList[currentMove - 1]);
\r
8739 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
8746 char *tags = PGNTags(&gameInfo);
\r
8747 EditTagsPopUp(tags);
\r
8752 AnalyzeModeEvent()
\r
8754 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
8757 if (gameMode != AnalyzeFile) {
\r
8759 if (gameMode != EditGame) return;
\r
8760 ResurrectChessProgram();
\r
8761 SendToProgram("analyze\n", &first);
\r
8762 first.analyzing = TRUE;
\r
8763 /*first.maybeThinking = TRUE;*/
\r
8764 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8765 AnalysisPopUp("Analysis",
\r
8766 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8768 gameMode = AnalyzeMode;
\r
8773 StartAnalysisClock();
\r
8774 GetTimeMark(&lastNodeCountTime);
\r
8775 lastNodeCount = 0;
\r
8779 AnalyzeFileEvent()
\r
8781 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
8784 if (gameMode != AnalyzeMode) {
\r
8786 if (gameMode != EditGame) return;
\r
8787 ResurrectChessProgram();
\r
8788 SendToProgram("analyze\n", &first);
\r
8789 first.analyzing = TRUE;
\r
8790 /*first.maybeThinking = TRUE;*/
\r
8791 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8792 AnalysisPopUp("Analysis",
\r
8793 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8795 gameMode = AnalyzeFile;
\r
8800 StartAnalysisClock();
\r
8801 GetTimeMark(&lastNodeCountTime);
\r
8802 lastNodeCount = 0;
\r
8806 MachineWhiteEvent()
\r
8808 char buf[MSG_SIZ];
\r
8810 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
8814 if (gameMode == PlayFromGameFile ||
\r
8815 gameMode == TwoMachinesPlay ||
\r
8816 gameMode == Training ||
\r
8817 gameMode == AnalyzeMode ||
\r
8818 gameMode == EndOfGame)
\r
8821 if (gameMode == EditPosition)
\r
8822 EditPositionDone();
\r
8824 if (!WhiteOnMove(currentMove)) {
\r
8825 DisplayError("It is not White's turn", 0);
\r
8829 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
8830 ExitAnalyzeMode();
\r
8832 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
8833 gameMode == AnalyzeFile)
\r
8836 ResurrectChessProgram(); /* in case it isn't running */
\r
8837 gameMode = MachinePlaysWhite;
\r
8841 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8842 DisplayTitle(buf);
\r
8843 if (first.sendName) {
\r
8844 sprintf(buf, "name %s\n", gameInfo.black);
\r
8845 SendToProgram(buf, &first);
\r
8847 if (first.sendTime) {
\r
8848 if (first.useColors) {
\r
8849 SendToProgram("black\n", &first); /*gnu kludge*/
\r
8851 SendTimeRemaining(&first, TRUE);
\r
8853 if (first.useColors) {
\r
8854 SendToProgram("white\ngo\n", &first);
\r
8856 SendToProgram("go\n", &first);
\r
8858 SetMachineThinkingEnables();
\r
8859 first.maybeThinking = TRUE;
\r
8862 if (appData.autoFlipView && !flipView) {
\r
8863 flipView = !flipView;
\r
8864 DrawPosition(FALSE, NULL);
\r
8869 MachineBlackEvent()
\r
8871 char buf[MSG_SIZ];
\r
8873 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
8877 if (gameMode == PlayFromGameFile ||
\r
8878 gameMode == TwoMachinesPlay ||
\r
8879 gameMode == Training ||
\r
8880 gameMode == AnalyzeMode ||
\r
8881 gameMode == EndOfGame)
\r
8884 if (gameMode == EditPosition)
\r
8885 EditPositionDone();
\r
8887 if (WhiteOnMove(currentMove)) {
\r
8888 DisplayError("It is not Black's turn", 0);
\r
8892 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
8893 ExitAnalyzeMode();
\r
8895 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
8896 gameMode == AnalyzeFile)
\r
8899 ResurrectChessProgram(); /* in case it isn't running */
\r
8900 gameMode = MachinePlaysBlack;
\r
8904 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8905 DisplayTitle(buf);
\r
8906 if (first.sendName) {
\r
8907 sprintf(buf, "name %s\n", gameInfo.white);
\r
8908 SendToProgram(buf, &first);
\r
8910 if (first.sendTime) {
\r
8911 if (first.useColors) {
\r
8912 SendToProgram("white\n", &first); /*gnu kludge*/
\r
8914 SendTimeRemaining(&first, FALSE);
\r
8916 if (first.useColors) {
\r
8917 SendToProgram("black\ngo\n", &first);
\r
8919 SendToProgram("go\n", &first);
\r
8921 SetMachineThinkingEnables();
\r
8922 first.maybeThinking = TRUE;
\r
8925 if (appData.autoFlipView && flipView) {
\r
8926 flipView = !flipView;
\r
8927 DrawPosition(FALSE, NULL);
\r
8933 DisplayTwoMachinesTitle()
\r
8935 char buf[MSG_SIZ];
\r
8936 if (appData.matchGames > 0) {
\r
8937 if (first.twoMachinesColor[0] == 'w') {
\r
8938 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
8939 gameInfo.white, gameInfo.black,
\r
8940 first.matchWins, second.matchWins,
\r
8941 matchGame - 1 - (first.matchWins + second.matchWins));
\r
8943 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
8944 gameInfo.white, gameInfo.black,
\r
8945 second.matchWins, first.matchWins,
\r
8946 matchGame - 1 - (first.matchWins + second.matchWins));
\r
8949 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8951 DisplayTitle(buf);
\r
8955 TwoMachinesEvent P((void))
\r
8958 char buf[MSG_SIZ];
\r
8959 ChessProgramState *onmove;
\r
8961 if (appData.noChessProgram) return;
\r
8963 switch (gameMode) {
\r
8964 case TwoMachinesPlay:
\r
8966 case MachinePlaysWhite:
\r
8967 case MachinePlaysBlack:
\r
8968 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
8969 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
8972 /* fall through */
\r
8973 case BeginningOfGame:
\r
8974 case PlayFromGameFile:
\r
8977 if (gameMode != EditGame) return;
\r
8979 case EditPosition:
\r
8980 EditPositionDone();
\r
8984 ExitAnalyzeMode();
\r
8991 forwardMostMove = currentMove;
\r
8992 ResurrectChessProgram(); /* in case first program isn't running */
\r
8994 if (second.pr == NULL) {
\r
8995 StartChessProgram(&second);
\r
8996 if (second.protocolVersion == 1) {
\r
8997 TwoMachinesEventIfReady();
\r
8999 /* kludge: allow timeout for initial "feature" command */
\r
9001 DisplayMessage("", "Starting second chess program");
\r
9002 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9006 DisplayMessage("", "");
\r
9007 InitChessProgram(&second);
\r
9008 SendToProgram("force\n", &second);
\r
9009 if (startedFromSetupPosition) {
\r
9010 SendBoard(&second, backwardMostMove);
\r
9012 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9013 SendMoveToProgram(i, &second);
\r
9016 gameMode = TwoMachinesPlay;
\r
9020 DisplayTwoMachinesTitle();
\r
9022 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
9028 SendToProgram(first.computerString, &first);
\r
9029 if (first.sendName) {
\r
9030 sprintf(buf, "name %s\n", second.tidy);
\r
9031 SendToProgram(buf, &first);
\r
9033 SendToProgram(second.computerString, &second);
\r
9034 if (second.sendName) {
\r
9035 sprintf(buf, "name %s\n", first.tidy);
\r
9036 SendToProgram(buf, &second);
\r
9039 if (!first.sendTime || !second.sendTime) {
\r
9041 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9042 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9044 if (onmove->sendTime) {
\r
9045 if (onmove->useColors) {
\r
9046 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
9048 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
9050 if (onmove->useColors) {
\r
9051 SendToProgram(onmove->twoMachinesColor, onmove);
\r
9053 SendToProgram("go\n", onmove);
\r
9054 onmove->maybeThinking = TRUE;
\r
9055 SetMachineThinkingEnables();
\r
9063 if (gameMode == Training) {
\r
9064 SetTrainingModeOff();
\r
9065 gameMode = PlayFromGameFile;
\r
9066 DisplayMessage("", "Training mode off");
\r
9068 gameMode = Training;
\r
9069 animateTraining = appData.animate;
\r
9071 /* make sure we are not already at the end of the game */
\r
9072 if (currentMove < forwardMostMove) {
\r
9073 SetTrainingModeOn();
\r
9074 DisplayMessage("", "Training mode on");
\r
9076 gameMode = PlayFromGameFile;
\r
9077 DisplayError("Already at end of game", 0);
\r
9086 if (!appData.icsActive) return;
\r
9087 switch (gameMode) {
\r
9088 case IcsPlayingWhite:
\r
9089 case IcsPlayingBlack:
\r
9090 case IcsObserving:
\r
9092 case BeginningOfGame:
\r
9093 case IcsExamining:
\r
9099 case EditPosition:
\r
9100 EditPositionDone();
\r
9105 ExitAnalyzeMode();
\r
9113 gameMode = IcsIdle;
\r
9124 switch (gameMode) {
\r
9126 SetTrainingModeOff();
\r
9128 case MachinePlaysWhite:
\r
9129 case MachinePlaysBlack:
\r
9130 case BeginningOfGame:
\r
9131 SendToProgram("force\n", &first);
\r
9132 SetUserThinkingEnables();
\r
9134 case PlayFromGameFile:
\r
9135 (void) StopLoadGameTimer();
\r
9136 if (gameFileFP != NULL) {
\r
9137 gameFileFP = NULL;
\r
9140 case EditPosition:
\r
9141 EditPositionDone();
\r
9145 ExitAnalyzeMode();
\r
9146 SendToProgram("force\n", &first);
\r
9148 case TwoMachinesPlay:
\r
9149 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9150 ResurrectChessProgram();
\r
9151 SetUserThinkingEnables();
\r
9154 ResurrectChessProgram();
\r
9156 case IcsPlayingBlack:
\r
9157 case IcsPlayingWhite:
\r
9158 DisplayError("Warning: You are still playing a game", 0);
\r
9160 case IcsObserving:
\r
9161 DisplayError("Warning: You are still observing a game", 0);
\r
9163 case IcsExamining:
\r
9164 DisplayError("Warning: You are still examining a game", 0);
\r
9175 first.offeredDraw = second.offeredDraw = 0;
\r
9177 if (gameMode == PlayFromGameFile) {
\r
9178 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9179 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9183 if (gameMode == MachinePlaysWhite ||
\r
9184 gameMode == MachinePlaysBlack ||
\r
9185 gameMode == TwoMachinesPlay ||
\r
9186 gameMode == EndOfGame) {
\r
9187 i = forwardMostMove;
\r
9188 while (i > currentMove) {
\r
9189 SendToProgram("undo\n", &first);
\r
9192 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9193 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9194 DisplayBothClocks();
\r
9195 if (whiteFlag || blackFlag) {
\r
9196 whiteFlag = blackFlag = 0;
\r
9201 gameMode = EditGame;
\r
9208 EditPositionEvent()
\r
9210 if (gameMode == EditPosition) {
\r
9216 if (gameMode != EditGame) return;
\r
9218 gameMode = EditPosition;
\r
9221 if (currentMove > 0)
\r
9222 CopyBoard(boards[0], boards[currentMove]);
\r
9224 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
9226 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9227 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9234 if (first.analysisSupport && first.analyzing) {
\r
9235 SendToProgram("exit\n", &first);
\r
9236 first.analyzing = FALSE;
\r
9238 AnalysisPopDown();
\r
9239 thinkOutput[0] = NULLCHAR;
\r
9243 EditPositionDone()
\r
9245 startedFromSetupPosition = TRUE;
\r
9246 InitChessProgram(&first);
\r
9247 SendToProgram("force\n", &first);
\r
9248 if (blackPlaysFirst) {
\r
9249 strcpy(moveList[0], "");
\r
9250 strcpy(parseList[0], "");
\r
9251 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9252 CopyBoard(boards[1], boards[0]);
\r
9254 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9256 SendBoard(&first, forwardMostMove);
\r
9258 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9259 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9260 gameMode = EditGame;
\r
9262 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9263 ClearHighlights(); /* [AS] */
\r
9266 /* Pause for `ms' milliseconds */
\r
9267 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9277 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
9280 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9282 SendMultiLineToICS(buf)
\r
9285 char temp[MSG_SIZ+1], *p;
\r
9288 len = strlen(buf);
\r
9289 if (len > MSG_SIZ)
\r
9292 strncpy(temp, buf, len);
\r
9297 if (*p == '\n' || *p == '\r')
\r
9302 strcat(temp, "\n");
\r
9304 SendToPlayer(temp, strlen(temp));
\r
9308 SetWhiteToPlayEvent()
\r
9310 if (gameMode == EditPosition) {
\r
9311 blackPlaysFirst = FALSE;
\r
9312 DisplayBothClocks(); /* works because currentMove is 0 */
\r
9313 } else if (gameMode == IcsExamining) {
\r
9314 SendToICS(ics_prefix);
\r
9315 SendToICS("tomove white\n");
\r
9320 SetBlackToPlayEvent()
\r
9322 if (gameMode == EditPosition) {
\r
9323 blackPlaysFirst = TRUE;
\r
9324 currentMove = 1; /* kludge */
\r
9325 DisplayBothClocks();
\r
9327 } else if (gameMode == IcsExamining) {
\r
9328 SendToICS(ics_prefix);
\r
9329 SendToICS("tomove black\n");
\r
9334 EditPositionMenuEvent(selection, x, y)
\r
9335 ChessSquare selection;
\r
9338 char buf[MSG_SIZ];
\r
9340 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
9342 switch (selection) {
\r
9344 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
9345 SendToICS(ics_prefix);
\r
9346 SendToICS("bsetup clear\n");
\r
9347 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
9348 SendToICS(ics_prefix);
\r
9349 SendToICS("clearboard\n");
\r
9351 for (x = 0; x < BOARD_WIDTH; x++) {
\r
9352 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
9353 if (gameMode == IcsExamining) {
\r
9354 if (boards[currentMove][y][x] != EmptySquare) {
\r
9355 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
9356 AAA + x, ONE + y);
\r
9360 boards[0][y][x] = EmptySquare;
\r
9365 if (gameMode == EditPosition) {
\r
9366 DrawPosition(FALSE, boards[0]);
\r
9371 SetWhiteToPlayEvent();
\r
9375 SetBlackToPlayEvent();
\r
9379 if (gameMode == IcsExamining) {
\r
9380 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
9383 boards[0][y][x] = EmptySquare;
\r
9384 DrawPosition(FALSE, boards[0]);
\r
9389 if (gameMode == IcsExamining) {
\r
9390 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
9391 PieceToChar(selection), AAA + x, ONE + y);
\r
9394 boards[0][y][x] = selection;
\r
9395 DrawPosition(FALSE, boards[0]);
\r
9403 DropMenuEvent(selection, x, y)
\r
9404 ChessSquare selection;
\r
9407 ChessMove moveType;
\r
9409 switch (gameMode) {
\r
9410 case IcsPlayingWhite:
\r
9411 case MachinePlaysBlack:
\r
9412 if (!WhiteOnMove(currentMove)) {
\r
9413 DisplayMoveError("It is Black's turn");
\r
9416 moveType = WhiteDrop;
\r
9418 case IcsPlayingBlack:
\r
9419 case MachinePlaysWhite:
\r
9420 if (WhiteOnMove(currentMove)) {
\r
9421 DisplayMoveError("It is White's turn");
\r
9424 moveType = BlackDrop;
\r
9427 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
9433 if (moveType == BlackDrop && selection < BlackPawn) {
\r
9434 selection = (ChessSquare) ((int) selection
\r
9435 + (int) BlackPawn - (int) WhitePawn);
\r
9437 if (boards[currentMove][y][x] != EmptySquare) {
\r
9438 DisplayMoveError("That square is occupied");
\r
9442 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
9448 /* Accept a pending offer of any kind from opponent */
\r
9450 if (appData.icsActive) {
\r
9451 SendToICS(ics_prefix);
\r
9452 SendToICS("accept\n");
\r
9453 } else if (cmailMsgLoaded) {
\r
9454 if (currentMove == cmailOldMove &&
\r
9455 commentList[cmailOldMove] != NULL &&
\r
9456 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9457 "Black offers a draw" : "White offers a draw")) {
\r
9459 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9460 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9462 DisplayError("There is no pending offer on this move", 0);
\r
9463 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9466 /* Not used for offers from chess program */
\r
9473 /* Decline a pending offer of any kind from opponent */
\r
9475 if (appData.icsActive) {
\r
9476 SendToICS(ics_prefix);
\r
9477 SendToICS("decline\n");
\r
9478 } else if (cmailMsgLoaded) {
\r
9479 if (currentMove == cmailOldMove &&
\r
9480 commentList[cmailOldMove] != NULL &&
\r
9481 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9482 "Black offers a draw" : "White offers a draw")) {
\r
9484 AppendComment(cmailOldMove, "Draw declined");
\r
9485 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
9488 DisplayError("There is no pending offer on this move", 0);
\r
9491 /* Not used for offers from chess program */
\r
9498 /* Issue ICS rematch command */
\r
9499 if (appData.icsActive) {
\r
9500 SendToICS(ics_prefix);
\r
9501 SendToICS("rematch\n");
\r
9508 /* Call your opponent's flag (claim a win on time) */
\r
9509 if (appData.icsActive) {
\r
9510 SendToICS(ics_prefix);
\r
9511 SendToICS("flag\n");
\r
9513 switch (gameMode) {
\r
9516 case MachinePlaysWhite:
\r
9519 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9522 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
9524 DisplayError("Your opponent is not out of time", 0);
\r
9527 case MachinePlaysBlack:
\r
9530 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9533 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
9535 DisplayError("Your opponent is not out of time", 0);
\r
9545 /* Offer draw or accept pending draw offer from opponent */
\r
9547 if (appData.icsActive) {
\r
9548 /* Note: tournament rules require draw offers to be
\r
9549 made after you make your move but before you punch
\r
9550 your clock. Currently ICS doesn't let you do that;
\r
9551 instead, you immediately punch your clock after making
\r
9552 a move, but you can offer a draw at any time. */
\r
9554 SendToICS(ics_prefix);
\r
9555 SendToICS("draw\n");
\r
9556 } else if (cmailMsgLoaded) {
\r
9557 if (currentMove == cmailOldMove &&
\r
9558 commentList[cmailOldMove] != NULL &&
\r
9559 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9560 "Black offers a draw" : "White offers a draw")) {
\r
9561 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9562 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9563 } else if (currentMove == cmailOldMove + 1) {
\r
9564 char *offer = WhiteOnMove(cmailOldMove) ?
\r
9565 "White offers a draw" : "Black offers a draw";
\r
9566 AppendComment(currentMove, offer);
\r
9567 DisplayComment(currentMove - 1, offer);
\r
9568 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
9570 DisplayError("You must make your move before offering a draw", 0);
\r
9571 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9573 } else if (first.offeredDraw) {
\r
9574 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
9576 if (first.sendDrawOffers) {
\r
9577 SendToProgram("draw\n", &first);
\r
9578 userOfferedDraw = TRUE;
\r
9586 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
9588 if (appData.icsActive) {
\r
9589 SendToICS(ics_prefix);
\r
9590 SendToICS("adjourn\n");
\r
9592 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
9600 /* Offer Abort or accept pending Abort offer from opponent */
\r
9602 if (appData.icsActive) {
\r
9603 SendToICS(ics_prefix);
\r
9604 SendToICS("abort\n");
\r
9606 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
9613 /* Resign. You can do this even if it's not your turn. */
\r
9615 if (appData.icsActive) {
\r
9616 SendToICS(ics_prefix);
\r
9617 SendToICS("resign\n");
\r
9619 switch (gameMode) {
\r
9620 case MachinePlaysWhite:
\r
9621 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9623 case MachinePlaysBlack:
\r
9624 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9627 if (cmailMsgLoaded) {
\r
9629 if (WhiteOnMove(cmailOldMove)) {
\r
9630 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9632 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9634 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
9645 StopObservingEvent()
\r
9647 /* Stop observing current games */
\r
9648 SendToICS(ics_prefix);
\r
9649 SendToICS("unobserve\n");
\r
9653 StopExaminingEvent()
\r
9655 /* Stop observing current game */
\r
9656 SendToICS(ics_prefix);
\r
9657 SendToICS("unexamine\n");
\r
9661 ForwardInner(target)
\r
9666 if (appData.debugMode)
\r
9667 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
9668 target, currentMove, forwardMostMove);
\r
9670 if (gameMode == EditPosition)
\r
9673 if (gameMode == PlayFromGameFile && !pausing)
\r
9676 if (gameMode == IcsExamining && pausing)
\r
9677 limit = pauseExamForwardMostMove;
\r
9679 limit = forwardMostMove;
\r
9681 if (target > limit) target = limit;
\r
9683 if (target > 0 && moveList[target - 1][0]) {
\r
9684 int fromX, fromY, toX, toY;
\r
9685 toX = moveList[target - 1][2] - AAA;
\r
9686 toY = moveList[target - 1][3] - ONE;
\r
9687 if (moveList[target - 1][1] == '@') {
\r
9688 if (appData.highlightLastMove) {
\r
9689 SetHighlights(-1, -1, toX, toY);
\r
9692 fromX = moveList[target - 1][0] - AAA;
\r
9693 fromY = moveList[target - 1][1] - ONE;
\r
9694 if (target == currentMove + 1) {
\r
9695 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
9697 if (appData.highlightLastMove) {
\r
9698 SetHighlights(fromX, fromY, toX, toY);
\r
9702 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9703 gameMode == Training || gameMode == PlayFromGameFile ||
\r
9704 gameMode == AnalyzeFile) {
\r
9705 while (currentMove < target) {
\r
9706 SendMoveToProgram(currentMove++, &first);
\r
9709 currentMove = target;
\r
9712 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9713 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9714 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9716 DisplayBothClocks();
\r
9717 DisplayMove(currentMove - 1);
\r
9718 DrawPosition(FALSE, boards[currentMove]);
\r
9719 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9720 if (commentList[currentMove] && !matchMode && gameMode != Training) {
\r
9721 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9729 if (gameMode == IcsExamining && !pausing) {
\r
9730 SendToICS(ics_prefix);
\r
9731 SendToICS("forward\n");
\r
9733 ForwardInner(currentMove + 1);
\r
9740 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9741 /* to optimze, we temporarily turn off analysis mode while we feed
\r
9742 * the remaining moves to the engine. Otherwise we get analysis output
\r
9743 * after each move.
\r
9745 if (first.analysisSupport) {
\r
9746 SendToProgram("exit\nforce\n", &first);
\r
9747 first.analyzing = FALSE;
\r
9751 if (gameMode == IcsExamining && !pausing) {
\r
9752 SendToICS(ics_prefix);
\r
9753 SendToICS("forward 999999\n");
\r
9755 ForwardInner(forwardMostMove);
\r
9758 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9759 /* we have fed all the moves, so reactivate analysis mode */
\r
9760 SendToProgram("analyze\n", &first);
\r
9761 first.analyzing = TRUE;
\r
9762 /*first.maybeThinking = TRUE;*/
\r
9763 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9768 BackwardInner(target)
\r
9771 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
9773 if (appData.debugMode)
\r
9774 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
9775 target, currentMove, forwardMostMove);
\r
9777 if (gameMode == EditPosition) return;
\r
9778 if (currentMove <= backwardMostMove) {
\r
9779 ClearHighlights();
\r
9780 DrawPosition(full_redraw, boards[currentMove]);
\r
9783 if (gameMode == PlayFromGameFile && !pausing)
\r
9786 if (moveList[target][0]) {
\r
9787 int fromX, fromY, toX, toY;
\r
9788 toX = moveList[target][2] - AAA;
\r
9789 toY = moveList[target][3] - ONE;
\r
9790 if (moveList[target][1] == '@') {
\r
9791 if (appData.highlightLastMove) {
\r
9792 SetHighlights(-1, -1, toX, toY);
\r
9795 fromX = moveList[target][0] - AAA;
\r
9796 fromY = moveList[target][1] - ONE;
\r
9797 if (target == currentMove - 1) {
\r
9798 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
9800 if (appData.highlightLastMove) {
\r
9801 SetHighlights(fromX, fromY, toX, toY);
\r
9805 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
9806 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
9807 while (currentMove > target) {
\r
9808 SendToProgram("undo\n", &first);
\r
9812 currentMove = target;
\r
9815 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9816 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9817 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9819 DisplayBothClocks();
\r
9820 DisplayMove(currentMove - 1);
\r
9821 DrawPosition(full_redraw, boards[currentMove]);
\r
9822 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9823 if (commentList[currentMove] != NULL) {
\r
9824 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9831 if (gameMode == IcsExamining && !pausing) {
\r
9832 SendToICS(ics_prefix);
\r
9833 SendToICS("backward\n");
\r
9835 BackwardInner(currentMove - 1);
\r
9842 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9843 /* to optimze, we temporarily turn off analysis mode while we undo
\r
9844 * all the moves. Otherwise we get analysis output after each undo.
\r
9846 if (first.analysisSupport) {
\r
9847 SendToProgram("exit\nforce\n", &first);
\r
9848 first.analyzing = FALSE;
\r
9852 if (gameMode == IcsExamining && !pausing) {
\r
9853 SendToICS(ics_prefix);
\r
9854 SendToICS("backward 999999\n");
\r
9856 BackwardInner(backwardMostMove);
\r
9859 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9860 /* we have fed all the moves, so reactivate analysis mode */
\r
9861 SendToProgram("analyze\n", &first);
\r
9862 first.analyzing = TRUE;
\r
9863 /*first.maybeThinking = TRUE;*/
\r
9864 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9871 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
9872 if (to >= forwardMostMove) to = forwardMostMove;
\r
9873 if (to <= backwardMostMove) to = backwardMostMove;
\r
9874 if (to < currentMove) {
\r
9875 BackwardInner(to);
\r
9884 if (gameMode != IcsExamining) {
\r
9885 DisplayError("You are not examining a game", 0);
\r
9889 DisplayError("You can't revert while pausing", 0);
\r
9892 SendToICS(ics_prefix);
\r
9893 SendToICS("revert\n");
\r
9897 RetractMoveEvent()
\r
9899 switch (gameMode) {
\r
9900 case MachinePlaysWhite:
\r
9901 case MachinePlaysBlack:
\r
9902 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9903 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9906 if (forwardMostMove < 2) return;
\r
9907 currentMove = forwardMostMove = forwardMostMove - 2;
\r
9908 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9909 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9910 DisplayBothClocks();
\r
9911 DisplayMove(currentMove - 1);
\r
9912 ClearHighlights();/*!! could figure this out*/
\r
9913 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
9914 SendToProgram("remove\n", &first);
\r
9915 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
9918 case BeginningOfGame:
\r
9922 case IcsPlayingWhite:
\r
9923 case IcsPlayingBlack:
\r
9924 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
9925 SendToICS(ics_prefix);
\r
9926 SendToICS("takeback 2\n");
\r
9928 SendToICS(ics_prefix);
\r
9929 SendToICS("takeback 1\n");
\r
9938 ChessProgramState *cps;
\r
9940 switch (gameMode) {
\r
9941 case MachinePlaysWhite:
\r
9942 if (!WhiteOnMove(forwardMostMove)) {
\r
9943 DisplayError("It is your turn", 0);
\r
9948 case MachinePlaysBlack:
\r
9949 if (WhiteOnMove(forwardMostMove)) {
\r
9950 DisplayError("It is your turn", 0);
\r
9955 case TwoMachinesPlay:
\r
9956 if (WhiteOnMove(forwardMostMove) ==
\r
9957 (first.twoMachinesColor[0] == 'w')) {
\r
9963 case BeginningOfGame:
\r
9967 SendToProgram("?\n", cps);
\r
9971 TruncateGameEvent()
\r
9974 if (gameMode != EditGame) return;
\r
9981 if (forwardMostMove > currentMove) {
\r
9982 if (gameInfo.resultDetails != NULL) {
\r
9983 free(gameInfo.resultDetails);
\r
9984 gameInfo.resultDetails = NULL;
\r
9985 gameInfo.result = GameUnfinished;
\r
9987 forwardMostMove = currentMove;
\r
9988 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
9996 if (appData.noChessProgram) return;
\r
9997 switch (gameMode) {
\r
9998 case MachinePlaysWhite:
\r
9999 if (WhiteOnMove(forwardMostMove)) {
\r
10000 DisplayError("Wait until your turn", 0);
\r
10004 case BeginningOfGame:
\r
10005 case MachinePlaysBlack:
\r
10006 if (!WhiteOnMove(forwardMostMove)) {
\r
10007 DisplayError("Wait until your turn", 0);
\r
10012 DisplayError("No hint available", 0);
\r
10015 SendToProgram("hint\n", &first);
\r
10016 hintRequested = TRUE;
\r
10022 if (appData.noChessProgram) return;
\r
10023 switch (gameMode) {
\r
10024 case MachinePlaysWhite:
\r
10025 if (WhiteOnMove(forwardMostMove)) {
\r
10026 DisplayError("Wait until your turn", 0);
\r
10030 case BeginningOfGame:
\r
10031 case MachinePlaysBlack:
\r
10032 if (!WhiteOnMove(forwardMostMove)) {
\r
10033 DisplayError("Wait until your turn", 0);
\r
10037 case EditPosition:
\r
10038 EditPositionDone();
\r
10040 case TwoMachinesPlay:
\r
10045 SendToProgram("bk\n", &first);
\r
10046 bookOutput[0] = NULLCHAR;
\r
10047 bookRequested = TRUE;
\r
10053 char *tags = PGNTags(&gameInfo);
\r
10054 TagsPopUp(tags, CmailMsg());
\r
10058 /* end button procedures */
\r
10061 PrintPosition(fp, move)
\r
10067 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
10068 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
10069 char c = PieceToChar(boards[move][i][j]);
\r
10070 fputc(c == 'x' ? '.' : c, fp);
\r
10071 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
10074 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
10075 fprintf(fp, "white to play\n");
\r
10077 fprintf(fp, "black to play\n");
\r
10081 PrintOpponents(fp)
\r
10084 if (gameInfo.white != NULL) {
\r
10085 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
10087 fprintf(fp, "\n");
\r
10091 /* Find last component of program's own name, using some heuristics */
\r
10093 TidyProgramName(prog, host, buf)
\r
10094 char *prog, *host, buf[MSG_SIZ];
\r
10097 int local = (strcmp(host, "localhost") == 0);
\r
10098 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
10100 while (*p == ' ') p++;
\r
10103 if (*prog == '"' || *prog == '\'') {
\r
10104 q = strchr(prog + 1, *prog);
\r
10106 q = strchr(prog, ' ');
\r
10108 if (q == NULL) q = prog + strlen(prog);
\r
10110 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
10112 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
10113 memcpy(buf, p, q - p);
\r
10114 buf[q - p] = NULLCHAR;
\r
10116 strcat(buf, "@");
\r
10117 strcat(buf, host);
\r
10122 TimeControlTagValue()
\r
10124 char buf[MSG_SIZ];
\r
10125 if (!appData.clockMode) {
\r
10126 strcpy(buf, "-");
\r
10127 } else if (movesPerSession > 0) {
\r
10128 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
10129 } else if (timeIncrement == 0) {
\r
10130 sprintf(buf, "%ld", timeControl/1000);
\r
10132 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
10134 return StrSave(buf);
\r
10140 /* This routine is used only for certain modes */
\r
10141 VariantClass v = gameInfo.variant;
\r
10142 ClearGameInfo(&gameInfo);
\r
10143 gameInfo.variant = v;
\r
10145 switch (gameMode) {
\r
10146 case MachinePlaysWhite:
\r
10147 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10148 gameInfo.site = StrSave(HostName());
\r
10149 gameInfo.date = PGNDate();
\r
10150 gameInfo.round = StrSave("-");
\r
10151 gameInfo.white = StrSave(first.tidy);
\r
10152 gameInfo.black = StrSave(UserName());
\r
10153 gameInfo.timeControl = TimeControlTagValue();
\r
10156 case MachinePlaysBlack:
\r
10157 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10158 gameInfo.site = StrSave(HostName());
\r
10159 gameInfo.date = PGNDate();
\r
10160 gameInfo.round = StrSave("-");
\r
10161 gameInfo.white = StrSave(UserName());
\r
10162 gameInfo.black = StrSave(first.tidy);
\r
10163 gameInfo.timeControl = TimeControlTagValue();
\r
10166 case TwoMachinesPlay:
\r
10167 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10168 gameInfo.site = StrSave(HostName());
\r
10169 gameInfo.date = PGNDate();
\r
10170 if (matchGame > 0) {
\r
10171 char buf[MSG_SIZ];
\r
10172 sprintf(buf, "%d", matchGame);
\r
10173 gameInfo.round = StrSave(buf);
\r
10175 gameInfo.round = StrSave("-");
\r
10177 if (first.twoMachinesColor[0] == 'w') {
\r
10178 gameInfo.white = StrSave(first.tidy);
\r
10179 gameInfo.black = StrSave(second.tidy);
\r
10181 gameInfo.white = StrSave(second.tidy);
\r
10182 gameInfo.black = StrSave(first.tidy);
\r
10184 gameInfo.timeControl = TimeControlTagValue();
\r
10188 gameInfo.event = StrSave("Edited game");
\r
10189 gameInfo.site = StrSave(HostName());
\r
10190 gameInfo.date = PGNDate();
\r
10191 gameInfo.round = StrSave("-");
\r
10192 gameInfo.white = StrSave("-");
\r
10193 gameInfo.black = StrSave("-");
\r
10196 case EditPosition:
\r
10197 gameInfo.event = StrSave("Edited position");
\r
10198 gameInfo.site = StrSave(HostName());
\r
10199 gameInfo.date = PGNDate();
\r
10200 gameInfo.round = StrSave("-");
\r
10201 gameInfo.white = StrSave("-");
\r
10202 gameInfo.black = StrSave("-");
\r
10205 case IcsPlayingWhite:
\r
10206 case IcsPlayingBlack:
\r
10207 case IcsObserving:
\r
10208 case IcsExamining:
\r
10211 case PlayFromGameFile:
\r
10212 gameInfo.event = StrSave("Game from non-PGN file");
\r
10213 gameInfo.site = StrSave(HostName());
\r
10214 gameInfo.date = PGNDate();
\r
10215 gameInfo.round = StrSave("-");
\r
10216 gameInfo.white = StrSave("?");
\r
10217 gameInfo.black = StrSave("?");
\r
10226 ReplaceComment(index, text)
\r
10232 while (*text == '\n') text++;
\r
10233 len = strlen(text);
\r
10234 while (len > 0 && text[len - 1] == '\n') len--;
\r
10236 if (commentList[index] != NULL)
\r
10237 free(commentList[index]);
\r
10240 commentList[index] = NULL;
\r
10243 commentList[index] = (char *) malloc(len + 2);
\r
10244 strncpy(commentList[index], text, len);
\r
10245 commentList[index][len] = '\n';
\r
10246 commentList[index][len + 1] = NULLCHAR;
\r
10259 if (ch == '\r') continue;
\r
10261 } while (ch != '\0');
\r
10265 AppendComment(index, text)
\r
10272 GetInfoFromComment( index, text );
\r
10275 while (*text == '\n') text++;
\r
10276 len = strlen(text);
\r
10277 while (len > 0 && text[len - 1] == '\n') len--;
\r
10279 if (len == 0) return;
\r
10281 if (commentList[index] != NULL) {
\r
10282 old = commentList[index];
\r
10283 oldlen = strlen(old);
\r
10284 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
10285 strcpy(commentList[index], old);
\r
10287 strncpy(&commentList[index][oldlen], text, len);
\r
10288 commentList[index][oldlen + len] = '\n';
\r
10289 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
10291 commentList[index] = (char *) malloc(len + 2);
\r
10292 strncpy(commentList[index], text, len);
\r
10293 commentList[index][len] = '\n';
\r
10294 commentList[index][len + 1] = NULLCHAR;
\r
10298 static char * FindStr( char * text, char * sub_text )
\r
10300 char * result = strstr( text, sub_text );
\r
10302 if( result != NULL ) {
\r
10303 result += strlen( sub_text );
\r
10309 /* [AS] Try to extract PV info from PGN comment */
\r
10310 void GetInfoFromComment( int index, char * text )
\r
10312 if( text != NULL && index > 0 ) {
\r
10316 char * s_eval = FindStr( text, "[%eval " );
\r
10317 char * s_emt = FindStr( text, "[%emt " );
\r
10319 if( s_eval != NULL || s_emt != NULL ) {
\r
10323 if( s_eval != NULL ) {
\r
10324 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
10328 if( delim != ']' ) {
\r
10333 if( s_emt != NULL ) {
\r
10337 /* We expect something like: [+|-]nnn.nn/dd */
\r
10338 char * sep = strchr( text, '/' );
\r
10339 int score_lo = 0;
\r
10341 if( sep == NULL || sep < (text+4) ) {
\r
10345 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
10349 if( score_lo < 0 || score_lo >= 100 ) {
\r
10353 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
10356 if( depth <= 0 ) {
\r
10364 pvInfoList[index-1].depth = depth;
\r
10365 pvInfoList[index-1].score = score;
\r
10366 pvInfoList[index-1].time = time;
\r
10371 SendToProgram(message, cps)
\r
10373 ChessProgramState *cps;
\r
10375 int count, outCount, error;
\r
10376 char buf[MSG_SIZ];
\r
10378 if (cps->pr == NULL) return;
\r
10381 if (appData.debugMode) {
\r
10383 GetTimeMark(&now);
\r
10384 fprintf(debugFP, "%ld >%-6s: %s",
\r
10385 SubtractTimeMarks(&now, &programStartTime),
\r
10386 cps->which, message);
\r
10389 count = strlen(message);
\r
10390 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
10391 if (outCount < count && !exiting) {
\r
10392 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
10393 DisplayFatalError(buf, error, 1);
\r
10398 ReceiveFromProgram(isr, closure, message, count, error)
\r
10399 InputSourceRef isr;
\r
10400 VOIDSTAR closure;
\r
10406 char buf[MSG_SIZ];
\r
10407 ChessProgramState *cps = (ChessProgramState *)closure;
\r
10409 if (isr != cps->isr) return; /* Killed intentionally */
\r
10410 if (count <= 0) {
\r
10411 if (count == 0) {
\r
10413 "Error: %s chess program (%s) exited unexpectedly",
\r
10414 cps->which, cps->program);
\r
10415 RemoveInputSource(cps->isr);
\r
10416 DisplayFatalError(buf, 0, 1);
\r
10419 "Error reading from %s chess program (%s)",
\r
10420 cps->which, cps->program);
\r
10421 RemoveInputSource(cps->isr);
\r
10423 /* [AS] Program is misbehaving badly... kill it */
\r
10424 if( count == -2 ) {
\r
10425 DestroyChildProcess( cps->pr, 9 );
\r
10426 cps->pr = NoProc;
\r
10429 DisplayFatalError(buf, error, 1);
\r
10431 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10435 if ((end_str = strchr(message, '\r')) != NULL)
\r
10436 *end_str = NULLCHAR;
\r
10437 if ((end_str = strchr(message, '\n')) != NULL)
\r
10438 *end_str = NULLCHAR;
\r
10440 if (appData.debugMode) {
\r
10442 GetTimeMark(&now);
\r
10443 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
10444 SubtractTimeMarks(&now, &programStartTime),
\r
10445 cps->which, message);
\r
10447 HandleMachineMove(message, cps);
\r
10452 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
10453 ChessProgramState *cps;
\r
10454 int mps, inc, sd, st;
\r
10457 char buf[MSG_SIZ];
\r
10458 int seconds = (tc / 1000) % 60;
\r
10460 if( timeControl_2 > 0 ) {
\r
10461 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
10462 tc = timeControl_2;
\r
10467 /* Set exact time per move, normally using st command */
\r
10468 if (cps->stKludge) {
\r
10469 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
10470 seconds = st % 60;
\r
10471 if (seconds == 0) {
\r
10472 sprintf(buf, "level 1 %d\n", st/60);
\r
10474 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
10477 sprintf(buf, "st %d\n", st);
\r
10480 /* Set conventional or incremental time control, using level command */
\r
10481 if (seconds == 0) {
\r
10482 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
10483 Fixed in later versions, but still avoid :seconds
\r
10484 when seconds is 0. */
\r
10485 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
10487 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
10488 seconds, inc/1000);
\r
10491 SendToProgram(buf, cps);
\r
10493 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
10494 /* Orthogonally, limit search to given depth */
\r
10496 if (cps->sdKludge) {
\r
10497 sprintf(buf, "depth\n%d\n", sd);
\r
10499 sprintf(buf, "sd %d\n", sd);
\r
10501 SendToProgram(buf, cps);
\r
10506 SendTimeRemaining(cps, machineWhite)
\r
10507 ChessProgramState *cps;
\r
10508 int /*boolean*/ machineWhite;
\r
10510 char message[MSG_SIZ];
\r
10511 long time, otime;
\r
10513 /* Note: this routine must be called when the clocks are stopped
\r
10514 or when they have *just* been set or switched; otherwise
\r
10515 it will be off by the time since the current tick started.
\r
10517 if (machineWhite) {
\r
10518 time = whiteTimeRemaining / 10;
\r
10519 otime = blackTimeRemaining / 10;
\r
10521 time = blackTimeRemaining / 10;
\r
10522 otime = whiteTimeRemaining / 10;
\r
10524 if (time <= 0) time = 1;
\r
10525 if (otime <= 0) otime = 1;
\r
10527 sprintf(message, "time %ld\n", time);
\r
10528 SendToProgram(message, cps);
\r
10530 sprintf(message, "otim %ld\n", otime);
\r
10531 SendToProgram(message, cps);
\r
10535 BoolFeature(p, name, loc, cps)
\r
10539 ChessProgramState *cps;
\r
10541 char buf[MSG_SIZ];
\r
10542 int len = strlen(name);
\r
10544 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10546 sscanf(*p, "%d", &val);
\r
10547 *loc = (val != 0);
\r
10548 while (**p && **p != ' ') (*p)++;
\r
10549 sprintf(buf, "accepted %s\n", name);
\r
10550 SendToProgram(buf, cps);
\r
10557 IntFeature(p, name, loc, cps)
\r
10561 ChessProgramState *cps;
\r
10563 char buf[MSG_SIZ];
\r
10564 int len = strlen(name);
\r
10565 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10567 sscanf(*p, "%d", loc);
\r
10568 while (**p && **p != ' ') (*p)++;
\r
10569 sprintf(buf, "accepted %s\n", name);
\r
10570 SendToProgram(buf, cps);
\r
10577 StringFeature(p, name, loc, cps)
\r
10581 ChessProgramState *cps;
\r
10583 char buf[MSG_SIZ];
\r
10584 int len = strlen(name);
\r
10585 if (strncmp((*p), name, len) == 0
\r
10586 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
10588 sscanf(*p, "%[^\"]", loc);
\r
10589 while (**p && **p != '\"') (*p)++;
\r
10590 if (**p == '\"') (*p)++;
\r
10591 sprintf(buf, "accepted %s\n", name);
\r
10592 SendToProgram(buf, cps);
\r
10599 FeatureDone(cps, val)
\r
10600 ChessProgramState* cps;
\r
10603 DelayedEventCallback cb = GetDelayedEvent();
\r
10604 if ((cb == InitBackEnd3 && cps == &first) ||
\r
10605 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
10606 CancelDelayedEvent();
\r
10607 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
10609 cps->initDone = val;
\r
10612 /* Parse feature command from engine */
\r
10614 ParseFeatures(args, cps)
\r
10616 ChessProgramState *cps;
\r
10621 char buf[MSG_SIZ];
\r
10624 while (*p == ' ') p++;
\r
10625 if (*p == NULLCHAR) return;
\r
10627 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
10628 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
10629 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
10630 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
10631 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
10632 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
10633 /* Engine can disable reuse, but can't enable it if user said no */
\r
10634 if (!val) cps->reuse = FALSE;
\r
10637 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
10638 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
10639 if (gameMode == TwoMachinesPlay) {
\r
10640 DisplayTwoMachinesTitle();
\r
10642 DisplayTitle("");
\r
10646 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
10647 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
10648 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
10649 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
10650 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
10651 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
10652 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
10653 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
10654 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
10655 if (IntFeature(&p, "done", &val, cps)) {
\r
10656 FeatureDone(cps, val);
\r
10659 /* Added by Tord: */
\r
10660 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
10661 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
10662 /* End of additions by Tord */
\r
10664 /* unknown feature: complain and skip */
\r
10666 while (*q && *q != '=') q++;
\r
10667 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
10668 SendToProgram(buf, cps);
\r
10672 if (*p == '\"') {
\r
10674 while (*p && *p != '\"') p++;
\r
10675 if (*p == '\"') p++;
\r
10677 while (*p && *p != ' ') p++;
\r
10685 PeriodicUpdatesEvent(newState)
\r
10688 if (newState == appData.periodicUpdates)
\r
10691 appData.periodicUpdates=newState;
\r
10693 /* Display type changes, so update it now */
\r
10694 DisplayAnalysis();
\r
10696 /* Get the ball rolling again... */
\r
10698 AnalysisPeriodicEvent(1);
\r
10699 StartAnalysisClock();
\r
10704 PonderNextMoveEvent(newState)
\r
10707 if (newState == appData.ponderNextMove) return;
\r
10708 if (gameMode == EditPosition) EditPositionDone();
\r
10710 SendToProgram("hard\n", &first);
\r
10711 if (gameMode == TwoMachinesPlay) {
\r
10712 SendToProgram("hard\n", &second);
\r
10715 SendToProgram("easy\n", &first);
\r
10716 thinkOutput[0] = NULLCHAR;
\r
10717 if (gameMode == TwoMachinesPlay) {
\r
10718 SendToProgram("easy\n", &second);
\r
10721 appData.ponderNextMove = newState;
\r
10725 ShowThinkingEvent(newState)
\r
10728 if (newState == appData.showThinking) return;
\r
10729 if (gameMode == EditPosition) EditPositionDone();
\r
10731 SendToProgram("post\n", &first);
\r
10732 if (gameMode == TwoMachinesPlay) {
\r
10733 SendToProgram("post\n", &second);
\r
10736 SendToProgram("nopost\n", &first);
\r
10737 thinkOutput[0] = NULLCHAR;
\r
10738 if (gameMode == TwoMachinesPlay) {
\r
10739 SendToProgram("nopost\n", &second);
\r
10742 appData.showThinking = newState;
\r
10746 AskQuestionEvent(title, question, replyPrefix, which)
\r
10747 char *title; char *question; char *replyPrefix; char *which;
\r
10749 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
10750 if (pr == NoProc) return;
\r
10751 AskQuestion(title, question, replyPrefix, pr);
\r
10755 DisplayMove(moveNumber)
\r
10758 char message[MSG_SIZ];
\r
10759 char res[MSG_SIZ];
\r
10760 char cpThinkOutput[MSG_SIZ];
\r
10762 if (moveNumber == forwardMostMove - 1 ||
\r
10763 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10765 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
10767 if (strchr(cpThinkOutput, '\n')) {
\r
10768 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
10771 *cpThinkOutput = NULLCHAR;
\r
10774 /* [AS] Hide thinking from human user */
\r
10775 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
10776 *cpThinkOutput = NULLCHAR;
\r
10777 if( thinkOutput[0] != NULLCHAR ) {
\r
10780 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
10781 cpThinkOutput[i] = '.';
\r
10783 cpThinkOutput[i] = NULLCHAR;
\r
10784 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
10788 if (moveNumber == forwardMostMove - 1 &&
\r
10789 gameInfo.resultDetails != NULL) {
\r
10790 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
10791 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
10793 sprintf(res, " {%s} %s",
\r
10794 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
10797 res[0] = NULLCHAR;
\r
10800 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
10801 DisplayMessage(res, cpThinkOutput);
\r
10803 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
10804 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
10805 parseList[moveNumber], res);
\r
10806 DisplayMessage(message, cpThinkOutput);
\r
10811 DisplayAnalysisText(text)
\r
10814 char buf[MSG_SIZ];
\r
10816 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10817 sprintf(buf, "Analysis (%s)", first.tidy);
\r
10818 AnalysisPopUp(buf, text);
\r
10823 only_one_move(str)
\r
10826 while (*str && isspace(*str)) ++str;
\r
10827 while (*str && !isspace(*str)) ++str;
\r
10828 if (!*str) return 1;
\r
10829 while (*str && isspace(*str)) ++str;
\r
10830 if (!*str) return 1;
\r
10835 DisplayAnalysis()
\r
10837 char buf[MSG_SIZ];
\r
10838 char lst[MSG_SIZ / 2];
\r
10840 static char *xtra[] = { "", " (--)", " (++)" };
\r
10843 if (programStats.time == 0) {
\r
10844 programStats.time = 1;
\r
10847 if (programStats.got_only_move) {
\r
10848 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
10850 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
10852 nps = (((double)programStats.nodes) /
\r
10853 (((double)programStats.time)/100.0));
\r
10855 cs = programStats.time % 100;
\r
10856 s = programStats.time / 100;
\r
10857 h = (s / (60*60));
\r
10862 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
10863 if (programStats.move_name[0] != NULLCHAR) {
\r
10864 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10865 programStats.depth,
\r
10866 programStats.nr_moves-programStats.moves_left,
\r
10867 programStats.nr_moves, programStats.move_name,
\r
10868 ((float)programStats.score)/100.0, lst,
\r
10869 only_one_move(lst)?
\r
10870 xtra[programStats.got_fail] : "",
\r
10871 programStats.nodes, (int)nps, h, m, s, cs);
\r
10873 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10874 programStats.depth,
\r
10875 programStats.nr_moves-programStats.moves_left,
\r
10876 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
10878 only_one_move(lst)?
\r
10879 xtra[programStats.got_fail] : "",
\r
10880 programStats.nodes, (int)nps, h, m, s, cs);
\r
10883 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10884 programStats.depth,
\r
10885 ((float)programStats.score)/100.0,
\r
10887 only_one_move(lst)?
\r
10888 xtra[programStats.got_fail] : "",
\r
10889 programStats.nodes, (int)nps, h, m, s, cs);
\r
10892 DisplayAnalysisText(buf);
\r
10896 DisplayComment(moveNumber, text)
\r
10900 char title[MSG_SIZ];
\r
10902 if( appData.autoDisplayComment ) {
\r
10903 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
10904 strcpy(title, "Comment");
\r
10906 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
10907 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
10908 parseList[moveNumber]);
\r
10911 CommentPopUp(title, text);
\r
10915 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
10916 * might be busy thinking or pondering. It can be omitted if your
\r
10917 * gnuchess is configured to stop thinking immediately on any user
\r
10918 * input. However, that gnuchess feature depends on the FIONREAD
\r
10919 * ioctl, which does not work properly on some flavors of Unix.
\r
10923 ChessProgramState *cps;
\r
10926 if (!cps->useSigint) return;
\r
10927 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
10928 switch (gameMode) {
\r
10929 case MachinePlaysWhite:
\r
10930 case MachinePlaysBlack:
\r
10931 case TwoMachinesPlay:
\r
10932 case IcsPlayingWhite:
\r
10933 case IcsPlayingBlack:
\r
10934 case AnalyzeMode:
\r
10935 case AnalyzeFile:
\r
10936 /* Skip if we know it isn't thinking */
\r
10937 if (!cps->maybeThinking) return;
\r
10938 if (appData.debugMode)
\r
10939 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
10940 InterruptChildProcess(cps->pr);
\r
10941 cps->maybeThinking = FALSE;
\r
10946 #endif /*ATTENTION*/
\r
10952 if (whiteTimeRemaining <= 0) {
\r
10953 if (!whiteFlag) {
\r
10954 whiteFlag = TRUE;
\r
10955 if (appData.icsActive) {
\r
10956 if (appData.autoCallFlag &&
\r
10957 gameMode == IcsPlayingBlack && !blackFlag) {
\r
10958 SendToICS(ics_prefix);
\r
10959 SendToICS("flag\n");
\r
10963 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
10965 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
10966 if (appData.autoCallFlag) {
\r
10967 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
10974 if (blackTimeRemaining <= 0) {
\r
10975 if (!blackFlag) {
\r
10976 blackFlag = TRUE;
\r
10977 if (appData.icsActive) {
\r
10978 if (appData.autoCallFlag &&
\r
10979 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
10980 SendToICS(ics_prefix);
\r
10981 SendToICS("flag\n");
\r
10985 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
10987 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
10988 if (appData.autoCallFlag) {
\r
10989 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
11000 CheckTimeControl()
\r
11002 if (!appData.clockMode || appData.icsActive ||
\r
11003 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
11005 if (timeIncrement >= 0) {
\r
11006 if (WhiteOnMove(forwardMostMove)) {
\r
11007 blackTimeRemaining += timeIncrement;
\r
11009 whiteTimeRemaining += timeIncrement;
\r
11013 * add time to clocks when time control is achieved
\r
11015 if (movesPerSession) {
\r
11016 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
\r
11018 /* White made time control */
\r
11019 whiteTimeRemaining += GetTimeControlForWhite();
\r
11022 /* Black made time control */
\r
11023 blackTimeRemaining += GetTimeControlForBlack();
\r
11032 DisplayBothClocks()
\r
11034 int wom = gameMode == EditPosition ?
\r
11035 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
11036 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
11037 DisplayBlackClock(blackTimeRemaining, !wom);
\r
11041 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
11042 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
11043 to use other calls if you don't. Clocks will be less accurate if
\r
11044 you have neither ftime nor gettimeofday.
\r
11047 /* Get the current time as a TimeMark */
\r
11052 #if HAVE_GETTIMEOFDAY
\r
11054 struct timeval timeVal;
\r
11055 struct timezone timeZone;
\r
11057 gettimeofday(&timeVal, &timeZone);
\r
11058 tm->sec = (long) timeVal.tv_sec;
\r
11059 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
11061 #else /*!HAVE_GETTIMEOFDAY*/
\r
11064 #include <sys/timeb.h>
\r
11065 struct timeb timeB;
\r
11068 tm->sec = (long) timeB.time;
\r
11069 tm->ms = (int) timeB.millitm;
\r
11071 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
11072 tm->sec = (long) time(NULL);
\r
11078 /* Return the difference in milliseconds between two
\r
11079 time marks. We assume the difference will fit in a long!
\r
11082 SubtractTimeMarks(tm2, tm1)
\r
11083 TimeMark *tm2, *tm1;
\r
11085 return 1000L*(tm2->sec - tm1->sec) +
\r
11086 (long) (tm2->ms - tm1->ms);
\r
11091 * Code to manage the game clocks.
\r
11093 * In tournament play, black starts the clock and then white makes a move.
\r
11094 * We give the human user a slight advantage if he is playing white---the
\r
11095 * clocks don't run until he makes his first move, so it takes zero time.
\r
11096 * Also, we don't account for network lag, so we could get out of sync
\r
11097 * with GNU Chess's clock -- but then, referees are always right.
\r
11100 static TimeMark tickStartTM;
\r
11101 static long intendedTickLength;
\r
11104 NextTickLength(timeRemaining)
\r
11105 long timeRemaining;
\r
11107 long nominalTickLength, nextTickLength;
\r
11109 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
11110 nominalTickLength = 100L;
\r
11112 nominalTickLength = 1000L;
\r
11113 nextTickLength = timeRemaining % nominalTickLength;
\r
11114 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
11116 return nextTickLength;
\r
11119 /* Stop clocks and reset to a fresh time control */
\r
11123 (void) StopClockTimer();
\r
11124 if (appData.icsActive) {
\r
11125 whiteTimeRemaining = blackTimeRemaining = 0;
\r
11127 whiteTimeRemaining = GetTimeControlForWhite();
\r
11128 blackTimeRemaining = GetTimeControlForBlack();
\r
11130 if (whiteFlag || blackFlag) {
\r
11131 DisplayTitle("");
\r
11132 whiteFlag = blackFlag = FALSE;
\r
11134 DisplayBothClocks();
\r
11137 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
11139 /* Decrement running clock by amount of time that has passed */
\r
11141 DecrementClocks()
\r
11143 long timeRemaining;
\r
11144 long lastTickLength, fudge;
\r
11147 if (!appData.clockMode) return;
\r
11148 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11150 GetTimeMark(&now);
\r
11152 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11154 /* Fudge if we woke up a little too soon */
\r
11155 fudge = intendedTickLength - lastTickLength;
\r
11156 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
11158 if (WhiteOnMove(forwardMostMove)) {
\r
11159 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
11160 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
11161 WhiteOnMove(currentMove));
\r
11163 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
11164 DisplayBlackClock(blackTimeRemaining - fudge,
\r
11165 !WhiteOnMove(currentMove));
\r
11168 if (CheckFlags()) return;
\r
11170 tickStartTM = now;
\r
11171 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
11172 StartClockTimer(intendedTickLength);
\r
11174 /* if the time remaining has fallen below the alarm threshold, sound the
\r
11175 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
11176 * with increment) the time remaining has increased to a level above the
\r
11177 * threshold, reset the alarm so it can sound again.
\r
11180 if (appData.icsActive && appData.icsAlarm) {
\r
11182 /* make sure we are dealing with the user's clock */
\r
11183 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
11184 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
11187 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
11188 alarmSounded = FALSE;
\r
11189 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
11190 PlayAlarmSound();
\r
11191 alarmSounded = TRUE;
\r
11197 /* A player has just moved, so stop the previously running
\r
11198 clock and (if in clock mode) start the other one.
\r
11199 We redisplay both clocks in case we're in ICS mode, because
\r
11200 ICS gives us an update to both clocks after every move.
\r
11201 Note that this routine is called *after* forwardMostMove
\r
11202 is updated, so the last fractional tick must be subtracted
\r
11203 from the color that is *not* on move now.
\r
11208 long lastTickLength;
\r
11210 int flagged = FALSE;
\r
11212 GetTimeMark(&now);
\r
11214 if (StopClockTimer() && appData.clockMode) {
\r
11215 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11216 if (WhiteOnMove(forwardMostMove)) {
\r
11217 blackTimeRemaining -= lastTickLength;
\r
11219 whiteTimeRemaining -= lastTickLength;
\r
11221 /* [HGM] save time for PGN file if engine did not give it */
\r
11222 if(pvInfoList[forwardMostMove-1].time == -1)
\r
11223 pvInfoList[forwardMostMove-1].time = lastTickLength/100;
\r
11224 flagged = CheckFlags();
\r
11226 CheckTimeControl();
\r
11228 if (flagged || !appData.clockMode) return;
\r
11230 switch (gameMode) {
\r
11231 case MachinePlaysBlack:
\r
11232 case MachinePlaysWhite:
\r
11233 case BeginningOfGame:
\r
11234 if (pausing) return;
\r
11238 case PlayFromGameFile:
\r
11239 case IcsExamining:
\r
11246 tickStartTM = now;
\r
11247 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11248 whiteTimeRemaining : blackTimeRemaining);
\r
11249 StartClockTimer(intendedTickLength);
\r
11253 /* Stop both clocks */
\r
11257 long lastTickLength;
\r
11260 if (!StopClockTimer()) return;
\r
11261 if (!appData.clockMode) return;
\r
11263 GetTimeMark(&now);
\r
11265 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11266 if (WhiteOnMove(forwardMostMove)) {
\r
11267 whiteTimeRemaining -= lastTickLength;
\r
11268 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
11270 blackTimeRemaining -= lastTickLength;
\r
11271 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
11276 /* Start clock of player on move. Time may have been reset, so
\r
11277 if clock is already running, stop and restart it. */
\r
11281 (void) StopClockTimer(); /* in case it was running already */
\r
11282 DisplayBothClocks();
\r
11283 if (CheckFlags()) return;
\r
11285 if (!appData.clockMode) return;
\r
11286 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11288 GetTimeMark(&tickStartTM);
\r
11289 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11290 whiteTimeRemaining : blackTimeRemaining);
\r
11291 StartClockTimer(intendedTickLength);
\r
11298 long second, minute, hour, day;
\r
11300 static char buf[32];
\r
11302 if (ms > 0 && ms <= 9900) {
\r
11303 /* convert milliseconds to tenths, rounding up */
\r
11304 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
11306 sprintf(buf, " %03.1f ", tenths/10.0);
\r
11310 /* convert milliseconds to seconds, rounding up */
\r
11311 /* use floating point to avoid strangeness of integer division
\r
11312 with negative dividends on many machines */
\r
11313 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
11315 if (second < 0) {
\r
11317 second = -second;
\r
11320 day = second / (60 * 60 * 24);
\r
11321 second = second % (60 * 60 * 24);
\r
11322 hour = second / (60 * 60);
\r
11323 second = second % (60 * 60);
\r
11324 minute = second / 60;
\r
11325 second = second % 60;
\r
11328 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
11329 sign, day, hour, minute, second);
\r
11330 else if (hour > 0)
\r
11331 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
11333 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
11340 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
11343 StrStr(string, match)
\r
11344 char *string, *match;
\r
11348 length = strlen(match);
\r
11350 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
11351 if (!strncmp(match, string, length))
\r
11358 StrCaseStr(string, match)
\r
11359 char *string, *match;
\r
11361 int i, j, length;
\r
11363 length = strlen(match);
\r
11365 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
11366 for (j = 0; j < length; j++) {
\r
11367 if (ToLower(match[j]) != ToLower(string[j]))
\r
11370 if (j == length) return string;
\r
11376 #ifndef _amigados
\r
11378 StrCaseCmp(s1, s2)
\r
11384 c1 = ToLower(*s1++);
\r
11385 c2 = ToLower(*s2++);
\r
11386 if (c1 > c2) return 1;
\r
11387 if (c1 < c2) return -1;
\r
11388 if (c1 == NULLCHAR) return 0;
\r
11397 return isupper(c) ? tolower(c) : c;
\r
11405 return islower(c) ? toupper(c) : c;
\r
11407 #endif /* !_amigados */
\r
11415 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
11422 StrSavePtr(s, savePtr)
\r
11423 char *s, **savePtr;
\r
11428 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
11429 strcpy(*savePtr, s);
\r
11431 return(*savePtr);
\r
11439 char buf[MSG_SIZ];
\r
11441 clock = time((time_t *)NULL);
\r
11442 tm = localtime(&clock);
\r
11443 sprintf(buf, "%04d.%02d.%02d",
\r
11444 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
11445 return StrSave(buf);
\r
11450 PositionToFEN(move, useFEN960)
\r
11454 int i, j, fromX, fromY, toX, toY;
\r
11459 ChessSquare piece;
\r
11461 whiteToPlay = (gameMode == EditPosition) ?
\r
11462 !blackPlaysFirst : (move % 2 == 0);
\r
11465 /* Piece placement data */
\r
11466 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11468 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11469 if (boards[move][i][j] == EmptySquare) {
\r
11472 if (emptycount > 0) {
\r
11473 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11474 *p++ = '0' + emptycount;
\r
11475 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11478 *p++ = PieceToChar(boards[move][i][j]);
\r
11479 if(gameInfo.variant == VariantCrazyhouse) {
\r
11480 /* [HGM] flag Crazyhouse promoted pieces */
\r
11481 if( (int)boards[move][i][j] > (int) WhiteQueen && (int)boards[move][i][j] < (int) WhiteKing ||
\r
11482 (int)boards[move][i][j] > (int) BlackQueen && (int)boards[move][i][j] < (int) BlackKing ) {
\r
11483 p[-1] = PieceToChar((ChessSquare)((int)boards[move][i][j]-(int)WhiteAlfil+(int)WhitePawn));
\r
11489 if (emptycount > 0) {
\r
11490 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11491 *p++ = '0' + emptycount;
\r
11492 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11499 /* Active color */
\r
11500 *p++ = whiteToPlay ? 'w' : 'b';
\r
11503 /* HACK: we don't keep track of castling availability, so fake it! */
\r
11504 /* Tord! please fix with the aid of castlingRights[move][...] */
\r
11506 /* PUSH Fabien & Tord */
\r
11508 /* Declare all potential FRC castling rights (conservative) */
\r
11509 /* outermost rook on each side of the king */
\r
11511 if( gameInfo.variant == VariantFischeRandom ) {
\r
11516 /* White castling rights */
\r
11518 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11520 if (boards[move][0][fk] == WhiteKing) {
\r
11522 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11523 if (boards[move][0][fr] == WhiteRook) {
\r
11524 *p++ = useFEN960 ? 'A' + fr : 'K';
\r
11529 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11530 if (boards[move][0][fr] == WhiteRook) {
\r
11531 *p++ = useFEN960 ? 'A' + fr : 'Q';
\r
11538 /* Black castling rights */
\r
11540 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11542 if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {
\r
11544 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11545 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11546 *p++ = useFEN960 ? 'a' + fr : 'k';
\r
11551 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11552 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11553 *p++ = useFEN960 ? 'a' + fr : 'q';
\r
11560 if (q == p) *p++ = '-'; /* No castling rights */
\r
11566 #ifdef OLDCASTLINGCODE
\r
11567 if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {
\r
11568 if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';
\r
11569 if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';
\r
11571 if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {
\r
11572 if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';
\r
11573 if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';
\r
11576 /* [HGM] write true castling rights */
\r
11577 if( nrCastlingRights == 6 ) {
\r
11578 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
11579 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
11580 if(castlingRights[move][1] == BOARD_LEFT &&
\r
11581 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
11582 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
11583 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
11584 if(castlingRights[move][4] == BOARD_LEFT &&
\r
11585 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
11588 if (q == p) *p++ = '-';
\r
11592 /* POP Fabien & Tord */
\r
11594 /* En passant target square */
\r
11595 if (move > backwardMostMove) {
\r
11596 fromX = moveList[move - 1][0] - AAA;
\r
11597 fromY = moveList[move - 1][1] - ONE;
\r
11598 toX = moveList[move - 1][2] - AAA;
\r
11599 toY = moveList[move - 1][3] - ONE;
\r
11600 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
11601 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
11602 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
11604 /* 2-square pawn move just happened */
\r
11605 *p++ = toX + AAA;
\r
11606 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
11614 /* [HGM] print Crazyhouse holdings */
\r
11615 if( gameInfo.variant == VariantCrazyhouse ) {
\r
11616 *p++ = ' '; q = p;
\r
11617 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
11618 piece = boards[move][i][BOARD_WIDTH-1];
\r
11619 if( piece != EmptySquare )
\r
11620 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
11621 *p++ = PieceToChar(piece);
\r
11623 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
11624 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
11625 if( piece != EmptySquare )
\r
11626 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
11627 *p++ = PieceToChar(piece);
\r
11630 if( q == p ) *p++ = '-';
\r
11634 /* [HGM] find reversible plies */
\r
11635 { int i = 0, j=move;
\r
11637 if (appData.debugMode) { int k;
\r
11638 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
11639 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
11640 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
11644 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
11645 if( j == backwardMostMove ) i += initialRulePlies;
\r
11646 sprintf(p, " %d", i);
\r
11647 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
11649 /* Fullmove number */
\r
11650 sprintf(p, " %d", (move / 2) + 1);
\r
11652 return StrSave(buf);
\r
11656 ParseFEN(board, blackPlaysFirst, fen)
\r
11658 int *blackPlaysFirst;
\r
11664 ChessSquare piece;
\r
11668 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
11669 if(gameInfo.holdingsWidth) {
\r
11670 for(i=0; i<BOARD_HEIGHT; i++) {
\r
11671 board[i][0] = EmptySquare; /* black holdings */
\r
11672 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
11673 board[i][1] = (ChessSquare) 0; /* black counts */
\r
11674 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
11678 /* Piece placement data */
\r
11679 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11682 if (*p == '/' || *p == ' ') {
\r
11683 if (*p == '/') p++;
\r
11684 emptycount = gameInfo.boardWidth - j;
\r
11685 while (emptycount--)
\r
11686 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11688 #if(BOARD_SIZE >= 10)
\r
11689 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
11690 p++; emptycount=10;
\r
11691 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11692 while (emptycount--)
\r
11693 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11695 } else if (isdigit(*p)) {
\r
11696 emptycount = *p++ - '0';
\r
11697 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
11698 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11699 while (emptycount--)
\r
11700 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11701 } else if (isalpha(*p)) {
\r
11702 if (j >= gameInfo.boardWidth) return FALSE;
\r
11703 piece = CharToPiece(*p++);
\r
11704 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
11705 piece = (ChessSquare) ((int)piece + (int)WhiteAlfil - (int)WhitePawn);
\r
11708 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
11714 while (*p == '/' || *p == ' ') p++;
\r
11716 /* Active color */
\r
11719 *blackPlaysFirst = FALSE;
\r
11722 *blackPlaysFirst = TRUE;
\r
11728 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
11729 /* return the extra info in global variiables */
\r
11731 /* set defaults in case FEN is incomplete */
\r
11732 FENepStatus = EP_UNKNOWN;
\r
11733 for(i=0; i<nrCastlingRights; i++ ) {
\r
11734 FENcastlingRights[i] = initialRights[i];
\r
11735 } /* assume possible unless obviously impossible */
\r
11736 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
11737 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
11738 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
11739 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
11740 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
11741 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
11742 FENrulePlies = 0;
\r
11744 while(*p==' ') p++;
\r
11746 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11747 /* castling indicator present, so default is no castlings */
\r
11748 for(i=0; i<nrCastlingRights; i++ ) {
\r
11749 FENcastlingRights[i] = -1;
\r
11752 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11755 FENcastlingRights[0] = BOARD_RGHT-1;
\r
11756 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11759 FENcastlingRights[1] = BOARD_LEFT;
\r
11760 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11763 FENcastlingRights[3] = BOARD_RGHT-1;
\r
11764 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11767 FENcastlingRights[4] = BOARD_LEFT;
\r
11768 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11774 while(*p==' ') p++;
\r
11778 p++; FENepStatus = EP_NONE;
\r
11780 char c = *p++ - AAA;
\r
11782 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
11783 if(*p >= '0' && *p <='9') *p++;
\r
11787 /* [HGM] look for Crazyhouse holdings here */
\r
11788 while(*p==' ') p++;
\r
11789 if( !isdigit(*p) ) {
\r
11790 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
11791 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
11792 /* if we would allow FEN reading to set board size, we would */
\r
11793 /* have to add holdings and shift the board read so far here */
\r
11794 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
11796 if((int) piece >= (int) BlackPawn ) {
\r
11797 i = (int)piece - (int)BlackPawn;
\r
11798 if( i >= BOARD_HEIGHT ) return FALSE;
\r
11799 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
11800 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
11802 i = (int)piece - (int)WhitePawn;
\r
11803 if( i >= BOARD_HEIGHT ) return FALSE;
\r
11804 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
11805 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
11813 if(sscanf(p, "%d", &i) == 1) {
\r
11814 FENrulePlies = i; /* 50-move ply counter */
\r
11815 /* (The move number is still ignored) */
\r
11822 EditPositionPasteFEN(char *fen)
\r
11824 if (fen != NULL) {
\r
11825 Board initial_position;
\r
11827 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
11828 DisplayError("Bad FEN position in clipboard", 0);
\r
11831 int savedBlackPlaysFirst = blackPlaysFirst;
\r
11832 EditPositionEvent();
\r
11833 blackPlaysFirst = savedBlackPlaysFirst;
\r
11834 CopyBoard(boards[0], initial_position);
\r
11835 /* [HGM] copy FEN attributes as well */
\r
11837 initialRulePlies = FENrulePlies;
\r
11838 epStatus[0] = FENepStatus;
\r
11839 for( i=0; i<nrCastlingRights; i++ )
\r
11840 castlingRights[0][i] = FENcastlingRights[i];
\r
11842 EditPositionDone();
\r
11843 DisplayBothClocks();
\r
11844 DrawPosition(FALSE, boards[currentMove]);
\r