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 < 2) 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 j = PieceToNumber(piece);
\r
1728 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1729 if(j < 0) continue; /* should not happen */
\r
1730 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1731 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1732 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1737 static int loggedOn = FALSE;
\r
1739 /*-- Game start info cache: --*/
\r
1741 char gs_kind[MSG_SIZ];
\r
1742 static char player1Name[128] = "";
\r
1743 static char player2Name[128] = "";
\r
1744 static int player1Rating = -1;
\r
1745 static int player2Rating = -1;
\r
1746 /*----------------------------*/
\r
1748 ColorClass curColor = ColorNormal;
\r
1751 read_from_ics(isr, closure, data, count, error)
\r
1752 InputSourceRef isr;
\r
1758 #define BUF_SIZE 8192
\r
1759 #define STARTED_NONE 0
\r
1760 #define STARTED_MOVES 1
\r
1761 #define STARTED_BOARD 2
\r
1762 #define STARTED_OBSERVE 3
\r
1763 #define STARTED_HOLDINGS 4
\r
1764 #define STARTED_CHATTER 5
\r
1765 #define STARTED_COMMENT 6
\r
1766 #define STARTED_MOVES_NOHIDE 7
\r
1768 static int started = STARTED_NONE;
\r
1769 static char parse[20000];
\r
1770 static int parse_pos = 0;
\r
1771 static char buf[BUF_SIZE + 1];
\r
1772 static int firstTime = TRUE, intfSet = FALSE;
\r
1773 static ColorClass prevColor = ColorNormal;
\r
1774 static int savingComment = FALSE;
\r
1783 if (appData.debugMode) {
\r
1785 fprintf(debugFP, "<ICS: ");
\r
1786 show_bytes(debugFP, data, count);
\r
1787 fprintf(debugFP, "\n");
\r
1793 /* If last read ended with a partial line that we couldn't parse,
\r
1794 prepend it to the new read and try again. */
\r
1795 if (leftover_len > 0) {
\r
1796 for (i=0; i<leftover_len; i++)
\r
1797 buf[i] = buf[leftover_start + i];
\r
1800 /* Copy in new characters, removing nulls and \r's */
\r
1801 buf_len = leftover_len;
\r
1802 for (i = 0; i < count; i++) {
\r
1803 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1804 buf[buf_len++] = data[i];
\r
1807 buf[buf_len] = NULLCHAR;
\r
1808 next_out = leftover_len;
\r
1809 leftover_start = 0;
\r
1812 while (i < buf_len) {
\r
1813 /* Deal with part of the TELNET option negotiation
\r
1814 protocol. We refuse to do anything beyond the
\r
1815 defaults, except that we allow the WILL ECHO option,
\r
1816 which ICS uses to turn off password echoing when we are
\r
1817 directly connected to it. We reject this option
\r
1818 if localLineEditing mode is on (always on in xboard)
\r
1819 and we are talking to port 23, which might be a real
\r
1820 telnet server that will try to keep WILL ECHO on permanently.
\r
1822 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1823 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1824 unsigned char option;
\r
1826 switch ((unsigned char) buf[++i]) {
\r
1828 if (appData.debugMode)
\r
1829 fprintf(debugFP, "\n<WILL ");
\r
1830 switch (option = (unsigned char) buf[++i]) {
\r
1832 if (appData.debugMode)
\r
1833 fprintf(debugFP, "ECHO ");
\r
1834 /* Reply only if this is a change, according
\r
1835 to the protocol rules. */
\r
1836 if (remoteEchoOption) break;
\r
1837 if (appData.localLineEditing &&
\r
1838 atoi(appData.icsPort) == TN_PORT) {
\r
1839 TelnetRequest(TN_DONT, TN_ECHO);
\r
1842 TelnetRequest(TN_DO, TN_ECHO);
\r
1843 remoteEchoOption = TRUE;
\r
1847 if (appData.debugMode)
\r
1848 fprintf(debugFP, "%d ", option);
\r
1849 /* Whatever this is, we don't want it. */
\r
1850 TelnetRequest(TN_DONT, option);
\r
1855 if (appData.debugMode)
\r
1856 fprintf(debugFP, "\n<WONT ");
\r
1857 switch (option = (unsigned char) buf[++i]) {
\r
1859 if (appData.debugMode)
\r
1860 fprintf(debugFP, "ECHO ");
\r
1861 /* Reply only if this is a change, according
\r
1862 to the protocol rules. */
\r
1863 if (!remoteEchoOption) break;
\r
1865 TelnetRequest(TN_DONT, TN_ECHO);
\r
1866 remoteEchoOption = FALSE;
\r
1869 if (appData.debugMode)
\r
1870 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1871 /* Whatever this is, it must already be turned
\r
1872 off, because we never agree to turn on
\r
1873 anything non-default, so according to the
\r
1874 protocol rules, we don't reply. */
\r
1879 if (appData.debugMode)
\r
1880 fprintf(debugFP, "\n<DO ");
\r
1881 switch (option = (unsigned char) buf[++i]) {
\r
1883 /* Whatever this is, we refuse to do it. */
\r
1884 if (appData.debugMode)
\r
1885 fprintf(debugFP, "%d ", option);
\r
1886 TelnetRequest(TN_WONT, option);
\r
1891 if (appData.debugMode)
\r
1892 fprintf(debugFP, "\n<DONT ");
\r
1893 switch (option = (unsigned char) buf[++i]) {
\r
1895 if (appData.debugMode)
\r
1896 fprintf(debugFP, "%d ", option);
\r
1897 /* Whatever this is, we are already not doing
\r
1898 it, because we never agree to do anything
\r
1899 non-default, so according to the protocol
\r
1900 rules, we don't reply. */
\r
1905 if (appData.debugMode)
\r
1906 fprintf(debugFP, "\n<IAC ");
\r
1907 /* Doubled IAC; pass it through */
\r
1911 if (appData.debugMode)
\r
1912 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1913 /* Drop all other telnet commands on the floor */
\r
1916 if (oldi > next_out)
\r
1917 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1918 if (++i > next_out)
\r
1923 /* OK, this at least will *usually* work */
\r
1924 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1928 if (loggedOn && !intfSet) {
\r
1929 if (ics_type == ICS_ICC) {
\r
1931 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1934 } else if (ics_type == ICS_CHESSNET) {
\r
1935 sprintf(str, "/style 12\n");
\r
1937 strcpy(str, "alias $ @\n$set interface ");
\r
1938 strcat(str, programVersion);
\r
1939 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1941 strcat(str, "$iset nohighlight 1\n");
\r
1943 strcat(str, "$iset lock 1\n$style 12\n");
\r
1949 if (started == STARTED_COMMENT) {
\r
1950 /* Accumulate characters in comment */
\r
1951 parse[parse_pos++] = buf[i];
\r
1952 if (buf[i] == '\n') {
\r
1953 parse[parse_pos] = NULLCHAR;
\r
1954 AppendComment(forwardMostMove, StripHighlight(parse));
\r
1955 started = STARTED_NONE;
\r
1957 /* Don't match patterns against characters in chatter */
\r
1962 if (started == STARTED_CHATTER) {
\r
1963 if (buf[i] != '\n') {
\r
1964 /* Don't match patterns against characters in chatter */
\r
1968 started = STARTED_NONE;
\r
1971 /* Kludge to deal with rcmd protocol */
\r
1972 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
1973 DisplayFatalError(&buf[1], 0, 1);
\r
1976 firstTime = FALSE;
\r
1979 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
1980 ics_type = ICS_ICC;
\r
1982 if (appData.debugMode)
\r
1983 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1986 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
1987 ics_type = ICS_FICS;
\r
1989 if (appData.debugMode)
\r
1990 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1993 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
1994 ics_type = ICS_CHESSNET;
\r
1996 if (appData.debugMode)
\r
1997 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2002 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2003 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2004 looking_at(buf, &i, "will be \"*\""))) {
\r
2005 strcpy(ics_handle, star_match[0]);
\r
2009 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2010 char buf[MSG_SIZ];
\r
2011 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2012 DisplayIcsInteractionTitle(buf);
\r
2013 have_set_title = TRUE;
\r
2016 /* skip finger notes */
\r
2017 if (started == STARTED_NONE &&
\r
2018 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2019 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2020 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2021 started = STARTED_CHATTER;
\r
2026 /* skip formula vars */
\r
2027 if (started == STARTED_NONE &&
\r
2028 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2029 started = STARTED_CHATTER;
\r
2035 if (appData.zippyTalk || appData.zippyPlay) {
\r
2037 if (ZippyControl(buf, &i) ||
\r
2038 ZippyConverse(buf, &i) ||
\r
2039 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2045 if (/* Don't color "message" or "messages" output */
\r
2046 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2047 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2048 looking_at(buf, &i, "--* (*:*): ") ||
\r
2049 /* Regular tells and says */
\r
2050 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2051 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2052 looking_at(buf, &i, "* says: ") ||
\r
2053 /* Message notifications (same color as tells) */
\r
2054 looking_at(buf, &i, "* has left a message ") ||
\r
2055 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2056 /* Whispers and kibitzes */
\r
2057 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2058 looking_at(buf, &i, "* kibitzes: ") ||
\r
2059 /* Channel tells */
\r
2060 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2062 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2063 /* Avoid "tells you:" spoofs in channels */
\r
2066 if (star_match[0][0] == NULLCHAR ||
\r
2067 strchr(star_match[0], ' ') ||
\r
2068 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2069 /* Reject bogus matches */
\r
2072 if (appData.colorize) {
\r
2073 if (oldi > next_out) {
\r
2074 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2079 Colorize(ColorTell, FALSE);
\r
2080 curColor = ColorTell;
\r
2083 Colorize(ColorKibitz, FALSE);
\r
2084 curColor = ColorKibitz;
\r
2087 p = strrchr(star_match[1], '(');
\r
2089 p = star_match[1];
\r
2093 if (atoi(p) == 1) {
\r
2094 Colorize(ColorChannel1, FALSE);
\r
2095 curColor = ColorChannel1;
\r
2097 Colorize(ColorChannel, FALSE);
\r
2098 curColor = ColorChannel;
\r
2102 curColor = ColorNormal;
\r
2106 if (started == STARTED_NONE && appData.autoComment &&
\r
2107 (gameMode == IcsObserving ||
\r
2108 gameMode == IcsPlayingWhite ||
\r
2109 gameMode == IcsPlayingBlack)) {
\r
2110 parse_pos = i - oldi;
\r
2111 memcpy(parse, &buf[oldi], parse_pos);
\r
2112 parse[parse_pos] = NULLCHAR;
\r
2113 started = STARTED_COMMENT;
\r
2114 savingComment = TRUE;
\r
2116 started = STARTED_CHATTER;
\r
2117 savingComment = FALSE;
\r
2124 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2125 looking_at(buf, &i, "* c-shouts: ")) {
\r
2126 if (appData.colorize) {
\r
2127 if (oldi > next_out) {
\r
2128 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2131 Colorize(ColorSShout, FALSE);
\r
2132 curColor = ColorSShout;
\r
2135 started = STARTED_CHATTER;
\r
2139 if (looking_at(buf, &i, "--->")) {
\r
2144 if (looking_at(buf, &i, "* shouts: ") ||
\r
2145 looking_at(buf, &i, "--> ")) {
\r
2146 if (appData.colorize) {
\r
2147 if (oldi > next_out) {
\r
2148 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2151 Colorize(ColorShout, FALSE);
\r
2152 curColor = ColorShout;
\r
2155 started = STARTED_CHATTER;
\r
2159 if (looking_at( buf, &i, "Challenge:")) {
\r
2160 if (appData.colorize) {
\r
2161 if (oldi > next_out) {
\r
2162 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2165 Colorize(ColorChallenge, FALSE);
\r
2166 curColor = ColorChallenge;
\r
2172 if (looking_at(buf, &i, "* offers you") ||
\r
2173 looking_at(buf, &i, "* offers to be") ||
\r
2174 looking_at(buf, &i, "* would like to") ||
\r
2175 looking_at(buf, &i, "* requests to") ||
\r
2176 looking_at(buf, &i, "Your opponent offers") ||
\r
2177 looking_at(buf, &i, "Your opponent requests")) {
\r
2179 if (appData.colorize) {
\r
2180 if (oldi > next_out) {
\r
2181 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2184 Colorize(ColorRequest, FALSE);
\r
2185 curColor = ColorRequest;
\r
2190 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2191 if (appData.colorize) {
\r
2192 if (oldi > next_out) {
\r
2193 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2196 Colorize(ColorSeek, FALSE);
\r
2197 curColor = ColorSeek;
\r
2203 if (looking_at(buf, &i, "\\ ")) {
\r
2204 if (prevColor != ColorNormal) {
\r
2205 if (oldi > next_out) {
\r
2206 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2209 Colorize(prevColor, TRUE);
\r
2210 curColor = prevColor;
\r
2212 if (savingComment) {
\r
2213 parse_pos = i - oldi;
\r
2214 memcpy(parse, &buf[oldi], parse_pos);
\r
2215 parse[parse_pos] = NULLCHAR;
\r
2216 started = STARTED_COMMENT;
\r
2218 started = STARTED_CHATTER;
\r
2223 if (looking_at(buf, &i, "Black Strength :") ||
\r
2224 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2225 looking_at(buf, &i, "<10>") ||
\r
2226 looking_at(buf, &i, "#@#")) {
\r
2227 /* Wrong board style */
\r
2229 SendToICS(ics_prefix);
\r
2230 SendToICS("set style 12\n");
\r
2231 SendToICS(ics_prefix);
\r
2232 SendToICS("refresh\n");
\r
2236 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2238 have_sent_ICS_logon = 1;
\r
2242 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2243 (looking_at(buf, &i, "\n<12> ") ||
\r
2244 looking_at(buf, &i, "<12> "))) {
\r
2246 if (oldi > next_out) {
\r
2247 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2250 started = STARTED_BOARD;
\r
2255 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2256 looking_at(buf, &i, "<b1> ")) {
\r
2257 if (oldi > next_out) {
\r
2258 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2261 started = STARTED_HOLDINGS;
\r
2266 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2268 /* Header for a move list -- first line */
\r
2270 switch (ics_getting_history) {
\r
2272 switch (gameMode) {
\r
2274 case BeginningOfGame:
\r
2275 /* User typed "moves" or "oldmoves" while we
\r
2276 were idle. Pretend we asked for these
\r
2277 moves and soak them up so user can step
\r
2278 through them and/or save them.
\r
2280 Reset(FALSE, TRUE);
\r
2281 gameMode = IcsObserving;
\r
2284 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2286 case EditGame: /*?*/
\r
2287 case EditPosition: /*?*/
\r
2288 /* Should above feature work in these modes too? */
\r
2289 /* For now it doesn't */
\r
2290 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2293 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2298 /* Is this the right one? */
\r
2299 if (gameInfo.white && gameInfo.black &&
\r
2300 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2301 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2303 ics_getting_history = H_GOT_REQ_HEADER;
\r
2306 case H_GOT_REQ_HEADER:
\r
2307 case H_GOT_UNREQ_HEADER:
\r
2308 case H_GOT_UNWANTED_HEADER:
\r
2309 case H_GETTING_MOVES:
\r
2310 /* Should not happen */
\r
2311 DisplayError("Error gathering move list: two headers", 0);
\r
2312 ics_getting_history = H_FALSE;
\r
2316 /* Save player ratings into gameInfo if needed */
\r
2317 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2318 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2319 (gameInfo.whiteRating == -1 ||
\r
2320 gameInfo.blackRating == -1)) {
\r
2322 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2323 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2324 if (appData.debugMode)
\r
2325 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2326 gameInfo.whiteRating, gameInfo.blackRating);
\r
2331 if (looking_at(buf, &i,
\r
2332 "* * match, initial time: * minute*, increment: * second")) {
\r
2333 /* Header for a move list -- second line */
\r
2334 /* Initial board will follow if this is a wild game */
\r
2336 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2337 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2338 gameInfo.event = StrSave(str);
\r
2339 gameInfo.variant = StringToVariant(gameInfo.event);
\r
2340 Reset(TRUE,TRUE); /* [HGM] possibly change board or holdings size */
\r
2344 if (looking_at(buf, &i, "Move ")) {
\r
2345 /* Beginning of a move list */
\r
2346 switch (ics_getting_history) {
\r
2348 /* Normally should not happen */
\r
2349 /* Maybe user hit reset while we were parsing */
\r
2352 /* Happens if we are ignoring a move list that is not
\r
2353 * the one we just requested. Common if the user
\r
2354 * tries to observe two games without turning off
\r
2357 case H_GETTING_MOVES:
\r
2358 /* Should not happen */
\r
2359 DisplayError("Error gathering move list: nested", 0);
\r
2360 ics_getting_history = H_FALSE;
\r
2362 case H_GOT_REQ_HEADER:
\r
2363 ics_getting_history = H_GETTING_MOVES;
\r
2364 started = STARTED_MOVES;
\r
2366 if (oldi > next_out) {
\r
2367 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2370 case H_GOT_UNREQ_HEADER:
\r
2371 ics_getting_history = H_GETTING_MOVES;
\r
2372 started = STARTED_MOVES_NOHIDE;
\r
2375 case H_GOT_UNWANTED_HEADER:
\r
2376 ics_getting_history = H_FALSE;
\r
2382 if (looking_at(buf, &i, "% ") ||
\r
2383 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2384 && looking_at(buf, &i, "}*"))) {
\r
2385 savingComment = FALSE;
\r
2386 switch (started) {
\r
2387 case STARTED_MOVES:
\r
2388 case STARTED_MOVES_NOHIDE:
\r
2389 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2390 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2391 ParseGameHistory(parse);
\r
2393 if (appData.zippyPlay && first.initDone) {
\r
2394 FeedMovesToProgram(&first, forwardMostMove);
\r
2395 if (gameMode == IcsPlayingWhite) {
\r
2396 if (WhiteOnMove(forwardMostMove)) {
\r
2397 if (first.sendTime) {
\r
2398 if (first.useColors) {
\r
2399 SendToProgram("black\n", &first);
\r
2401 SendTimeRemaining(&first, TRUE);
\r
2403 if (first.useColors) {
\r
2404 SendToProgram("white\ngo\n", &first);
\r
2406 SendToProgram("go\n", &first);
\r
2408 first.maybeThinking = TRUE;
\r
2410 if (first.usePlayother) {
\r
2411 if (first.sendTime) {
\r
2412 SendTimeRemaining(&first, TRUE);
\r
2414 SendToProgram("playother\n", &first);
\r
2415 firstMove = FALSE;
\r
2420 } else if (gameMode == IcsPlayingBlack) {
\r
2421 if (!WhiteOnMove(forwardMostMove)) {
\r
2422 if (first.sendTime) {
\r
2423 if (first.useColors) {
\r
2424 SendToProgram("white\n", &first);
\r
2426 SendTimeRemaining(&first, FALSE);
\r
2428 if (first.useColors) {
\r
2429 SendToProgram("black\ngo\n", &first);
\r
2431 SendToProgram("go\n", &first);
\r
2433 first.maybeThinking = TRUE;
\r
2435 if (first.usePlayother) {
\r
2436 if (first.sendTime) {
\r
2437 SendTimeRemaining(&first, FALSE);
\r
2439 SendToProgram("playother\n", &first);
\r
2440 firstMove = FALSE;
\r
2448 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2449 /* Moves came from oldmoves or moves command
\r
2450 while we weren't doing anything else.
\r
2452 currentMove = forwardMostMove;
\r
2453 ClearHighlights();/*!!could figure this out*/
\r
2454 flipView = appData.flipView;
\r
2455 DrawPosition(FALSE, boards[currentMove]);
\r
2456 DisplayBothClocks();
\r
2457 sprintf(str, "%s vs. %s",
\r
2458 gameInfo.white, gameInfo.black);
\r
2459 DisplayTitle(str);
\r
2460 gameMode = IcsIdle;
\r
2462 /* Moves were history of an active game */
\r
2463 if (gameInfo.resultDetails != NULL) {
\r
2464 free(gameInfo.resultDetails);
\r
2465 gameInfo.resultDetails = NULL;
\r
2468 HistorySet(parseList, backwardMostMove,
\r
2469 forwardMostMove, currentMove-1);
\r
2470 DisplayMove(currentMove - 1);
\r
2471 if (started == STARTED_MOVES) next_out = i;
\r
2472 started = STARTED_NONE;
\r
2473 ics_getting_history = H_FALSE;
\r
2476 case STARTED_OBSERVE:
\r
2477 started = STARTED_NONE;
\r
2478 SendToICS(ics_prefix);
\r
2479 SendToICS("refresh\n");
\r
2488 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2489 started == STARTED_HOLDINGS ||
\r
2490 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2491 /* Accumulate characters in move list or board */
\r
2492 parse[parse_pos++] = buf[i];
\r
2495 /* Start of game messages. Mostly we detect start of game
\r
2496 when the first board image arrives. On some versions
\r
2497 of the ICS, though, we need to do a "refresh" after starting
\r
2498 to observe in order to get the current board right away. */
\r
2499 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2500 started = STARTED_OBSERVE;
\r
2504 /* Handle auto-observe */
\r
2505 if (appData.autoObserve &&
\r
2506 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2507 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2509 /* Choose the player that was highlighted, if any. */
\r
2510 if (star_match[0][0] == '\033' ||
\r
2511 star_match[1][0] != '\033') {
\r
2512 player = star_match[0];
\r
2514 player = star_match[2];
\r
2516 sprintf(str, "%sobserve %s\n",
\r
2517 ics_prefix, StripHighlightAndTitle(player));
\r
2520 /* Save ratings from notify string */
\r
2521 strcpy(player1Name, star_match[0]);
\r
2522 player1Rating = string_to_rating(star_match[1]);
\r
2523 strcpy(player2Name, star_match[2]);
\r
2524 player2Rating = string_to_rating(star_match[3]);
\r
2526 if (appData.debugMode)
\r
2528 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2529 player1Name, player1Rating,
\r
2530 player2Name, player2Rating);
\r
2535 /* Deal with automatic examine mode after a game,
\r
2536 and with IcsObserving -> IcsExamining transition */
\r
2537 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2538 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2540 int gamenum = atoi(star_match[0]);
\r
2541 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2542 gamenum == ics_gamenum) {
\r
2543 /* We were already playing or observing this game;
\r
2544 no need to refetch history */
\r
2545 gameMode = IcsExamining;
\r
2547 pauseExamForwardMostMove = forwardMostMove;
\r
2548 } else if (currentMove < forwardMostMove) {
\r
2549 ForwardInner(forwardMostMove);
\r
2552 /* I don't think this case really can happen */
\r
2553 SendToICS(ics_prefix);
\r
2554 SendToICS("refresh\n");
\r
2559 /* Error messages */
\r
2560 if (ics_user_moved) {
\r
2561 if (looking_at(buf, &i, "Illegal move") ||
\r
2562 looking_at(buf, &i, "Not a legal move") ||
\r
2563 looking_at(buf, &i, "Your king is in check") ||
\r
2564 looking_at(buf, &i, "It isn't your turn") ||
\r
2565 looking_at(buf, &i, "It is not your move")) {
\r
2566 /* Illegal move */
\r
2567 ics_user_moved = 0;
\r
2568 if (forwardMostMove > backwardMostMove) {
\r
2569 currentMove = --forwardMostMove;
\r
2570 DisplayMove(currentMove - 1); /* before DMError */
\r
2571 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2572 DrawPosition(FALSE, boards[currentMove]);
\r
2574 DisplayBothClocks();
\r
2580 if (looking_at(buf, &i, "still have time") ||
\r
2581 looking_at(buf, &i, "not out of time") ||
\r
2582 looking_at(buf, &i, "either player is out of time") ||
\r
2583 looking_at(buf, &i, "has timeseal; checking")) {
\r
2584 /* We must have called his flag a little too soon */
\r
2585 whiteFlag = blackFlag = FALSE;
\r
2589 if (looking_at(buf, &i, "added * seconds to") ||
\r
2590 looking_at(buf, &i, "seconds were added to")) {
\r
2591 /* Update the clocks */
\r
2592 SendToICS(ics_prefix);
\r
2593 SendToICS("refresh\n");
\r
2597 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2598 ics_clock_paused = TRUE;
\r
2603 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2604 ics_clock_paused = FALSE;
\r
2609 /* Grab player ratings from the Creating: message.
\r
2610 Note we have to check for the special case when
\r
2611 the ICS inserts things like [white] or [black]. */
\r
2612 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2613 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2615 0 player 1 name (not necessarily white)
\r
2617 2 empty, white, or black (IGNORED)
\r
2618 3 player 2 name (not necessarily black)
\r
2621 The names/ratings are sorted out when the game
\r
2622 actually starts (below).
\r
2624 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2625 player1Rating = string_to_rating(star_match[1]);
\r
2626 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2627 player2Rating = string_to_rating(star_match[4]);
\r
2629 if (appData.debugMode)
\r
2631 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2632 player1Name, player1Rating,
\r
2633 player2Name, player2Rating);
\r
2638 /* Improved generic start/end-of-game messages */
\r
2639 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2640 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2641 /* If tkind == 0: */
\r
2642 /* star_match[0] is the game number */
\r
2643 /* [1] is the white player's name */
\r
2644 /* [2] is the black player's name */
\r
2645 /* For end-of-game: */
\r
2646 /* [3] is the reason for the game end */
\r
2647 /* [4] is a PGN end game-token, preceded by " " */
\r
2648 /* For start-of-game: */
\r
2649 /* [3] begins with "Creating" or "Continuing" */
\r
2650 /* [4] is " *" or empty (don't care). */
\r
2651 int gamenum = atoi(star_match[0]);
\r
2652 char *whitename, *blackname, *why, *endtoken;
\r
2653 ChessMove endtype = (ChessMove) 0;
\r
2656 whitename = star_match[1];
\r
2657 blackname = star_match[2];
\r
2658 why = star_match[3];
\r
2659 endtoken = star_match[4];
\r
2661 whitename = star_match[1];
\r
2662 blackname = star_match[3];
\r
2663 why = star_match[5];
\r
2664 endtoken = star_match[6];
\r
2667 /* Game start messages */
\r
2668 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2669 strncmp(why, "Continuing ", 11) == 0) {
\r
2670 gs_gamenum = gamenum;
\r
2671 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2673 if (appData.zippyPlay) {
\r
2674 ZippyGameStart(whitename, blackname);
\r
2680 /* Game end messages */
\r
2681 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2682 ics_gamenum != gamenum) {
\r
2685 while (endtoken[0] == ' ') endtoken++;
\r
2686 switch (endtoken[0]) {
\r
2689 endtype = GameUnfinished;
\r
2692 endtype = BlackWins;
\r
2695 if (endtoken[1] == '/')
\r
2696 endtype = GameIsDrawn;
\r
2698 endtype = WhiteWins;
\r
2701 GameEnds(endtype, why, GE_ICS);
\r
2703 if (appData.zippyPlay && first.initDone) {
\r
2704 ZippyGameEnd(endtype, why);
\r
2705 if (first.pr == NULL) {
\r
2706 /* Start the next process early so that we'll
\r
2707 be ready for the next challenge */
\r
2708 StartChessProgram(&first);
\r
2710 /* Send "new" early, in case this command takes
\r
2711 a long time to finish, so that we'll be ready
\r
2712 for the next challenge. */
\r
2713 Reset(TRUE, TRUE);
\r
2719 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2720 looking_at(buf, &i, "no longer observing game *") ||
\r
2721 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2722 if (gameMode == IcsObserving &&
\r
2723 atoi(star_match[0]) == ics_gamenum)
\r
2726 gameMode = IcsIdle;
\r
2728 ics_user_moved = FALSE;
\r
2733 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2734 if (gameMode == IcsExamining &&
\r
2735 atoi(star_match[0]) == ics_gamenum)
\r
2737 gameMode = IcsIdle;
\r
2739 ics_user_moved = FALSE;
\r
2744 /* Advance leftover_start past any newlines we find,
\r
2745 so only partial lines can get reparsed */
\r
2746 if (looking_at(buf, &i, "\n")) {
\r
2747 prevColor = curColor;
\r
2748 if (curColor != ColorNormal) {
\r
2749 if (oldi > next_out) {
\r
2750 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2753 Colorize(ColorNormal, FALSE);
\r
2754 curColor = ColorNormal;
\r
2756 if (started == STARTED_BOARD) {
\r
2757 started = STARTED_NONE;
\r
2758 parse[parse_pos] = NULLCHAR;
\r
2759 ParseBoard12(parse);
\r
2760 ics_user_moved = 0;
\r
2762 /* Send premove here */
\r
2763 if (appData.premove) {
\r
2764 char str[MSG_SIZ];
\r
2765 if (currentMove == 0 &&
\r
2766 gameMode == IcsPlayingWhite &&
\r
2767 appData.premoveWhite) {
\r
2768 sprintf(str, "%s%s\n", ics_prefix,
\r
2769 appData.premoveWhiteText);
\r
2770 if (appData.debugMode)
\r
2771 fprintf(debugFP, "Sending premove:\n");
\r
2773 } else if (currentMove == 1 &&
\r
2774 gameMode == IcsPlayingBlack &&
\r
2775 appData.premoveBlack) {
\r
2776 sprintf(str, "%s%s\n", ics_prefix,
\r
2777 appData.premoveBlackText);
\r
2778 if (appData.debugMode)
\r
2779 fprintf(debugFP, "Sending premove:\n");
\r
2781 } else if (gotPremove) {
\r
2783 ClearPremoveHighlights();
\r
2784 if (appData.debugMode)
\r
2785 fprintf(debugFP, "Sending premove:\n");
\r
2786 UserMoveEvent(premoveFromX, premoveFromY,
\r
2787 premoveToX, premoveToY,
\r
2788 premovePromoChar);
\r
2792 /* Usually suppress following prompt */
\r
2793 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2794 if (looking_at(buf, &i, "*% ")) {
\r
2795 savingComment = FALSE;
\r
2799 } else if (started == STARTED_HOLDINGS) {
\r
2801 char new_piece[MSG_SIZ];
\r
2802 started = STARTED_NONE;
\r
2803 parse[parse_pos] = NULLCHAR;
\r
2804 if (appData.debugMode)
\r
2805 fprintf(debugFP, "Parsing holdings: %s\n", parse);
\r
2806 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2807 gamenum == ics_gamenum) {
\r
2808 if (gameInfo.variant == VariantNormal) {
\r
2809 /* [HGM] We seem to switch variant during a game!
\r
2810 * Presumably no holdings were displayed, so we have
\r
2811 * to move the position two files to the right to
\r
2812 * create room for them!
\r
2815 if(gameInfo.holdingsWidth == 0) /* to be sure */
\r
2816 for(i=0; i<BOARD_HEIGHT; i++)
\r
2817 for(j=BOARD_RGHT-1; j>=0; j--)
\r
2818 boards[currentMove][i][j+2] = boards[currentMove][i][j];
\r
2820 if (appData.debugMode) {
\r
2821 fprintf(debugFP, "Switch board to Crazy\n");
\r
2822 setbuf(debugFP, NULL);
\r
2824 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
\r
2825 gameInfo.boardWidth = 8; /* [HGM] guess board size as well */
\r
2826 gameInfo.boardHeight = 8;
\r
2827 gameInfo.holdingsSize = 5;
\r
2828 gameInfo.holdingsWidth = 2;
\r
2829 InitDrawingSizes(-2, 0);
\r
2830 /* Get a move list just to see the header, which
\r
2831 will tell us whether this is really bug or zh */
\r
2832 if (ics_getting_history == H_FALSE) {
\r
2833 ics_getting_history = H_REQUESTED;
\r
2834 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2838 new_piece[0] = NULLCHAR;
\r
2839 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2840 &gamenum, white_holding, black_holding,
\r
2842 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2843 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2844 /* [HGM] copy holdings to board holdings area */
\r
2845 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2846 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2848 if (appData.zippyPlay && first.initDone) {
\r
2849 ZippyHoldings(white_holding, black_holding,
\r
2853 if (tinyLayout || smallLayout) {
\r
2854 char wh[16], bh[16];
\r
2855 PackHolding(wh, white_holding);
\r
2856 PackHolding(bh, black_holding);
\r
2857 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2858 gameInfo.white, gameInfo.black);
\r
2860 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2861 gameInfo.white, white_holding,
\r
2862 gameInfo.black, black_holding);
\r
2865 DrawPosition(FALSE, NULL);
\r
2866 DisplayTitle(str);
\r
2868 /* Suppress following prompt */
\r
2869 if (looking_at(buf, &i, "*% ")) {
\r
2870 savingComment = FALSE;
\r
2877 i++; /* skip unparsed character and loop back */
\r
2880 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2881 started != STARTED_HOLDINGS && i > next_out) {
\r
2882 SendToPlayer(&buf[next_out], i - next_out);
\r
2886 leftover_len = buf_len - leftover_start;
\r
2887 /* if buffer ends with something we couldn't parse,
\r
2888 reparse it after appending the next read */
\r
2890 } else if (count == 0) {
\r
2891 RemoveInputSource(isr);
\r
2892 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2894 DisplayFatalError("Error reading from ICS", error, 1);
\r
2899 /* Board style 12 looks like this:
\r
2901 <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
2903 * The "<12> " is stripped before it gets to this routine. The two
\r
2904 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2905 * some chess servers may not have them, or may have only the first.
\r
2906 * Additional trailing fields may be added in the future.
\r
2909 #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
2911 #define RELATION_OBSERVING_PLAYED 0
\r
2912 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2913 #define RELATION_PLAYING_MYMOVE 1
\r
2914 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2915 #define RELATION_EXAMINING 2
\r
2916 #define RELATION_ISOLATED_BOARD -3
\r
2917 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2920 ParseBoard12(string)
\r
2923 GameMode newGameMode;
\r
2924 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2925 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2926 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2927 char to_play, board_chars[72];
\r
2928 char move_str[500], str[500], elapsed_time[500];
\r
2929 char black[32], white[32];
\r
2931 int prevMove = currentMove;
\r
2933 ChessMove moveType;
\r
2934 int fromX, fromY, toX, toY;
\r
2937 fromX = fromY = toX = toY = -1;
\r
2941 if (appData.debugMode)
\r
2942 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2944 move_str[0] = NULLCHAR;
\r
2945 elapsed_time[0] = NULLCHAR;
\r
2946 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2947 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2948 &gamenum, white, black, &relation, &basetime, &increment,
\r
2949 &white_stren, &black_stren, &white_time, &black_time,
\r
2950 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2954 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2955 DisplayError(str, 0);
\r
2959 /* Convert the move number to internal form */
\r
2960 moveNum = (moveNum - 1) * 2;
\r
2961 if (to_play == 'B') moveNum++;
\r
2962 if (moveNum >= MAX_MOVES) {
\r
2963 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
2968 switch (relation) {
\r
2969 case RELATION_OBSERVING_PLAYED:
\r
2970 case RELATION_OBSERVING_STATIC:
\r
2971 if (gamenum == -1) {
\r
2972 /* Old ICC buglet */
\r
2973 relation = RELATION_OBSERVING_STATIC;
\r
2975 newGameMode = IcsObserving;
\r
2977 case RELATION_PLAYING_MYMOVE:
\r
2978 case RELATION_PLAYING_NOTMYMOVE:
\r
2980 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
2981 IcsPlayingWhite : IcsPlayingBlack;
\r
2983 case RELATION_EXAMINING:
\r
2984 newGameMode = IcsExamining;
\r
2986 case RELATION_ISOLATED_BOARD:
\r
2988 /* Just display this board. If user was doing something else,
\r
2989 we will forget about it until the next board comes. */
\r
2990 newGameMode = IcsIdle;
\r
2992 case RELATION_STARTING_POSITION:
\r
2993 newGameMode = gameMode;
\r
2997 /* Modify behavior for initial board display on move listing
\r
3000 switch (ics_getting_history) {
\r
3004 case H_GOT_REQ_HEADER:
\r
3005 case H_GOT_UNREQ_HEADER:
\r
3006 /* This is the initial position of the current game */
\r
3007 gamenum = ics_gamenum;
\r
3008 moveNum = 0; /* old ICS bug workaround */
\r
3009 if (to_play == 'B') {
\r
3010 startedFromSetupPosition = TRUE;
\r
3011 blackPlaysFirst = TRUE;
\r
3013 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3014 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3015 if (currentMove == 0) currentMove = 1;
\r
3017 newGameMode = gameMode;
\r
3018 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3020 case H_GOT_UNWANTED_HEADER:
\r
3021 /* This is an initial board that we don't want */
\r
3023 case H_GETTING_MOVES:
\r
3024 /* Should not happen */
\r
3025 DisplayError("Error gathering move list: extra board", 0);
\r
3026 ics_getting_history = H_FALSE;
\r
3030 /* Take action if this is the first board of a new game, or of a
\r
3031 different game than is currently being displayed. */
\r
3032 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3033 relation == RELATION_ISOLATED_BOARD) {
\r
3035 /* Forget the old game and get the history (if any) of the new one */
\r
3036 if (gameMode != BeginningOfGame) {
\r
3037 Reset(FALSE, TRUE);
\r
3040 if (appData.autoRaiseBoard) BoardToTop();
\r
3042 if (gamenum == -1) {
\r
3043 newGameMode = IcsIdle;
\r
3044 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3045 appData.getMoveList) {
\r
3046 /* Need to get game history */
\r
3047 ics_getting_history = H_REQUESTED;
\r
3048 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3052 /* Initially flip the board to have black on the bottom if playing
\r
3053 black or if the ICS flip flag is set, but let the user change
\r
3054 it with the Flip View button. */
\r
3055 flipView = appData.autoFlipView ?
\r
3056 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3059 /* Done with values from previous mode; copy in new ones */
\r
3060 gameMode = newGameMode;
\r
3062 ics_gamenum = gamenum;
\r
3063 if (gamenum == gs_gamenum) {
\r
3064 int klen = strlen(gs_kind);
\r
3065 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3066 sprintf(str, "ICS %s", gs_kind);
\r
3067 gameInfo.event = StrSave(str);
\r
3069 gameInfo.event = StrSave("ICS game");
\r
3071 gameInfo.site = StrSave(appData.icsHost);
\r
3072 gameInfo.date = PGNDate();
\r
3073 gameInfo.round = StrSave("-");
\r
3074 gameInfo.white = StrSave(white);
\r
3075 gameInfo.black = StrSave(black);
\r
3076 timeControl = basetime * 60 * 1000;
\r
3077 timeControl_2 = 0;
\r
3078 timeIncrement = increment * 1000;
\r
3079 movesPerSession = 0;
\r
3080 gameInfo.timeControl = TimeControlTagValue();
\r
3081 gameInfo.variant = StringToVariant(gameInfo.event);
\r
3082 if (appData.debugMode) {
\r
3083 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3084 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3085 setbuf(debugFP, NULL);
\r
3088 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
3089 gameInfo.boardWidth = gameInfo.boardHeight = 8;
\r
3090 switch(gameInfo.variant) {
\r
3091 case VariantShogi:
\r
3092 case VariantShowgi:
\r
3093 gameInfo.boardWidth = 9; gameInfo.boardHeight = 9;
\r
3094 gameInfo.holdingsSize = 7;
\r
3095 case VariantBughouse:
\r
3096 case VariantCrazyhouse:
\r
3097 gameInfo.holdingsWidth = 2; break;
\r
3099 gameInfo.holdingsWidth = gameInfo.holdingsSize = 0;
\r
3101 InitDrawingSizes(-2, 0);
\r
3102 gameInfo.outOfBook = NULL;
\r
3104 /* Do we have the ratings? */
\r
3105 if (strcmp(player1Name, white) == 0 &&
\r
3106 strcmp(player2Name, black) == 0) {
\r
3107 if (appData.debugMode)
\r
3108 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3109 player1Rating, player2Rating);
\r
3110 gameInfo.whiteRating = player1Rating;
\r
3111 gameInfo.blackRating = player2Rating;
\r
3112 } else if (strcmp(player2Name, white) == 0 &&
\r
3113 strcmp(player1Name, black) == 0) {
\r
3114 if (appData.debugMode)
\r
3115 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3116 player2Rating, player1Rating);
\r
3117 gameInfo.whiteRating = player2Rating;
\r
3118 gameInfo.blackRating = player1Rating;
\r
3120 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3122 /* Silence shouts if requested */
\r
3123 if (appData.quietPlay &&
\r
3124 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3125 SendToICS(ics_prefix);
\r
3126 SendToICS("set shout 0\n");
\r
3130 /* Deal with midgame name changes */
\r
3132 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3133 if (gameInfo.white) free(gameInfo.white);
\r
3134 gameInfo.white = StrSave(white);
\r
3136 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3137 if (gameInfo.black) free(gameInfo.black);
\r
3138 gameInfo.black = StrSave(black);
\r
3142 /* Throw away game result if anything actually changes in examine mode */
\r
3143 if (gameMode == IcsExamining && !newGame) {
\r
3144 gameInfo.result = GameUnfinished;
\r
3145 if (gameInfo.resultDetails != NULL) {
\r
3146 free(gameInfo.resultDetails);
\r
3147 gameInfo.resultDetails = NULL;
\r
3151 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3152 in if they are in a different variation than we are. */
\r
3153 if (pauseExamInvalid) return;
\r
3154 if (pausing && gameMode == IcsExamining) {
\r
3155 if (moveNum <= pauseExamForwardMostMove) {
\r
3156 pauseExamInvalid = TRUE;
\r
3157 forwardMostMove = pauseExamForwardMostMove;
\r
3162 /* Parse the board */
\r
3163 for (k = 0; k < 8; k++) {
\r
3164 for (j = 0; j < 8; j++)
\r
3165 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3166 if(gameInfo.holdingsWidth > 1) {
\r
3167 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3168 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3171 CopyBoard(boards[moveNum], board);
\r
3172 if (moveNum == 0) {
\r
3173 startedFromSetupPosition =
\r
3174 !CompareBoards(board, initialPosition);
\r
3177 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3178 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3179 /* This was an initial position from a move list, not
\r
3180 the current position */
\r
3184 /* Update currentMove and known move number limits */
\r
3185 newMove = newGame || moveNum > forwardMostMove;
\r
3187 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3188 if (gameMode == IcsExamining && moveNum == 0) {
\r
3189 /* Workaround for ICS limitation: we are not told the wild
\r
3190 type when starting to examine a game. But if we ask for
\r
3191 the move list, the move list header will tell us */
\r
3192 ics_getting_history = H_REQUESTED;
\r
3193 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3196 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3197 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3198 forwardMostMove = moveNum;
\r
3199 if (!pausing || currentMove > forwardMostMove)
\r
3200 currentMove = forwardMostMove;
\r
3202 /* New part of history that is not contiguous with old part */
\r
3203 if (pausing && gameMode == IcsExamining) {
\r
3204 pauseExamInvalid = TRUE;
\r
3205 forwardMostMove = pauseExamForwardMostMove;
\r
3208 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3209 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3210 ics_getting_history = H_REQUESTED;
\r
3211 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3216 /* Update the clocks */
\r
3217 if (strchr(elapsed_time, '.')) {
\r
3218 /* Time is in ms */
\r
3219 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3220 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3222 /* Time is in seconds */
\r
3223 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3224 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3229 if (appData.zippyPlay && newGame &&
\r
3230 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3231 gameMode != IcsExamining)
\r
3232 ZippyFirstBoard(moveNum, basetime, increment);
\r
3235 /* Put the move on the move list, first converting
\r
3236 to canonical algebraic form. */
\r
3237 if (moveNum > 0) {
\r
3238 if (appData.debugMode) {
\r
3239 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3240 fprintf(debugFP, "board = %d-d x%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3241 setbuf(debugFP, NULL);
\r
3243 if (moveNum <= backwardMostMove) {
\r
3244 /* We don't know what the board looked like before
\r
3245 this move. Punt. */
\r
3246 strcpy(parseList[moveNum - 1], move_str);
\r
3247 strcat(parseList[moveNum - 1], " ");
\r
3248 strcat(parseList[moveNum - 1], elapsed_time);
\r
3249 moveList[moveNum - 1][0] = NULLCHAR;
\r
3250 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3251 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3252 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3253 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3254 fromY, fromX, toY, toX, promoChar,
\r
3255 parseList[moveNum-1]);
\r
3256 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3257 castlingRights[moveNum]) ) {
\r
3259 case MT_STALEMATE:
\r
3263 if(gameInfo.variant != VariantShogi)
\r
3264 strcat(parseList[moveNum - 1], "+");
\r
3266 case MT_CHECKMATE:
\r
3267 strcat(parseList[moveNum - 1], "#");
\r
3270 strcat(parseList[moveNum - 1], " ");
\r
3271 strcat(parseList[moveNum - 1], elapsed_time);
\r
3272 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3273 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3274 strcat(moveList[moveNum - 1], "\n");
\r
3275 } else if (strcmp(move_str, "none") == 0) {
\r
3276 /* Again, we don't know what the board looked like;
\r
3277 this is really the start of the game. */
\r
3278 parseList[moveNum - 1][0] = NULLCHAR;
\r
3279 moveList[moveNum - 1][0] = NULLCHAR;
\r
3280 backwardMostMove = moveNum;
\r
3281 startedFromSetupPosition = TRUE;
\r
3282 fromX = fromY = toX = toY = -1;
\r
3284 /* Move from ICS was illegal!? Punt. */
\r
3286 if (appData.testLegality && appData.debugMode) {
\r
3287 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3288 DisplayError(str, 0);
\r
3291 strcpy(parseList[moveNum - 1], move_str);
\r
3292 strcat(parseList[moveNum - 1], " ");
\r
3293 strcat(parseList[moveNum - 1], elapsed_time);
\r
3294 moveList[moveNum - 1][0] = NULLCHAR;
\r
3295 fromX = fromY = toX = toY = -1;
\r
3297 if (appData.debugMode) {
\r
3298 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3299 setbuf(debugFP, NULL);
\r
3303 /* Send move to chess program (BEFORE animating it). */
\r
3304 if (appData.zippyPlay && !newGame && newMove &&
\r
3305 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3307 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3308 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3309 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3310 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3312 DisplayError(str, 0);
\r
3314 if (first.sendTime) {
\r
3315 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3317 SendMoveToProgram(moveNum - 1, &first);
\r
3319 firstMove = FALSE;
\r
3320 if (first.useColors) {
\r
3321 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3323 "black\ngo\n", &first);
\r
3325 SendToProgram("go\n", &first);
\r
3327 first.maybeThinking = TRUE;
\r
3330 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3331 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3332 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3333 DisplayError(str, 0);
\r
3335 SendMoveToProgram(moveNum - 1, &first);
\r
3342 if (moveNum > 0 && !gotPremove) {
\r
3343 /* If move comes from a remote source, animate it. If it
\r
3344 isn't remote, it will have already been animated. */
\r
3345 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3346 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3348 if (!pausing && appData.highlightLastMove) {
\r
3349 SetHighlights(fromX, fromY, toX, toY);
\r
3353 /* Start the clocks */
\r
3354 whiteFlag = blackFlag = FALSE;
\r
3355 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3356 if (ticking == 0) {
\r
3357 ics_clock_paused = TRUE;
\r
3359 } else if (ticking == 1) {
\r
3360 ics_clock_paused = FALSE;
\r
3362 if (gameMode == IcsIdle ||
\r
3363 relation == RELATION_OBSERVING_STATIC ||
\r
3364 relation == RELATION_EXAMINING ||
\r
3366 DisplayBothClocks();
\r
3370 /* Display opponents and material strengths */
\r
3371 if (gameInfo.variant != VariantBughouse &&
\r
3372 gameInfo.variant != VariantCrazyhouse) {
\r
3373 if (tinyLayout || smallLayout) {
\r
3374 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3375 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3376 basetime, increment);
\r
3378 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3379 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3380 basetime, increment);
\r
3382 DisplayTitle(str);
\r
3386 /* Display the board */
\r
3389 if (appData.premove)
\r
3390 if (!gotPremove ||
\r
3391 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3392 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3393 ClearPremoveHighlights();
\r
3395 DrawPosition(FALSE, boards[currentMove]);
\r
3396 DisplayMove(moveNum - 1);
\r
3397 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3401 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3405 GetMoveListEvent()
\r
3407 char buf[MSG_SIZ];
\r
3408 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3409 ics_getting_history = H_REQUESTED;
\r
3410 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3416 AnalysisPeriodicEvent(force)
\r
3419 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3420 && !force) || !appData.periodicUpdates)
\r
3423 /* Send . command to Crafty to collect stats */
\r
3424 SendToProgram(".\n", &first);
\r
3426 /* Don't send another until we get a response (this makes
\r
3427 us stop sending to old Crafty's which don't understand
\r
3428 the "." command (sending illegal cmds resets node count & time,
\r
3429 which looks bad)) */
\r
3430 programStats.ok_to_send = 0;
\r
3434 SendMoveToProgram(moveNum, cps)
\r
3436 ChessProgramState *cps;
\r
3438 char buf[MSG_SIZ];
\r
3439 if (cps->useUsermove) {
\r
3440 SendToProgram("usermove ", cps);
\r
3442 if (cps->useSAN) {
\r
3444 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3445 int len = space - parseList[moveNum];
\r
3446 memcpy(buf, parseList[moveNum], len);
\r
3447 buf[len++] = '\n';
\r
3448 buf[len] = NULLCHAR;
\r
3450 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3452 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3453 if(BOARD_HEIGHT>9) {
\r
3455 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3457 SendToProgram(buf, cps);
\r
3459 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3460 * the engine. It would be nice to have a better way to identify castle
\r
3462 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3463 int fromX = moveList[moveNum][0] - AAA;
\r
3464 int fromY = moveList[moveNum][1] - ONE;
\r
3465 int toX = moveList[moveNum][2] - AAA;
\r
3466 int toY = moveList[moveNum][3] - ONE;
\r
3467 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3468 && boards[currentMove][toY][toX] == WhiteRook)
\r
3469 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3470 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3471 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3472 else SendToProgram("O-O-O\n", cps);
\r
3474 else SendToProgram(moveList[moveNum], cps);
\r
3476 else SendToProgram(moveList[moveNum], cps);
\r
3477 /* End of additions by Tord */
\r
3482 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3483 ChessMove moveType;
\r
3484 int fromX, fromY, toX, toY;
\r
3486 char user_move[MSG_SIZ];
\r
3488 switch (moveType) {
\r
3490 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3491 (int)moveType, fromX, fromY, toX, toY);
\r
3492 DisplayError(user_move + strlen("say "), 0);
\r
3494 case WhiteKingSideCastle:
\r
3495 case BlackKingSideCastle:
\r
3496 case WhiteQueenSideCastleWild:
\r
3497 case BlackQueenSideCastleWild:
\r
3499 case WhiteHSideCastleFR:
\r
3500 case BlackHSideCastleFR:
\r
3502 sprintf(user_move, "o-o\n");
\r
3504 case WhiteQueenSideCastle:
\r
3505 case BlackQueenSideCastle:
\r
3506 case WhiteKingSideCastleWild:
\r
3507 case BlackKingSideCastleWild:
\r
3509 case WhiteASideCastleFR:
\r
3510 case BlackASideCastleFR:
\r
3512 sprintf(user_move, "o-o-o\n");
\r
3514 case WhitePromotionQueen:
\r
3515 case BlackPromotionQueen:
\r
3516 case WhitePromotionRook:
\r
3517 case BlackPromotionRook:
\r
3518 case WhitePromotionBishop:
\r
3519 case BlackPromotionBishop:
\r
3520 case WhitePromotionKnight:
\r
3521 case BlackPromotionKnight:
\r
3522 case WhitePromotionKing:
\r
3523 case BlackPromotionKing:
\r
3525 case WhitePromotionChancellor:
\r
3526 case BlackPromotionChancellor:
\r
3527 case WhitePromotionArchbishop:
\r
3528 case BlackPromotionArchbishop:
\r
3530 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3531 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3532 PieceToChar(PromoPiece(moveType)));
\r
3536 sprintf(user_move, "%c@%c%c\n",
\r
3537 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3538 AAA + toX, ONE + toY);
\r
3541 case WhiteCapturesEnPassant:
\r
3542 case BlackCapturesEnPassant:
\r
3543 case IllegalMove: /* could be a variant we don't quite understand */
\r
3544 sprintf(user_move, "%c%c%c%c\n",
\r
3545 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3548 SendToICS(user_move);
\r
3552 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3553 int rf, ff, rt, ft;
\r
3557 if (rf == DROP_RANK) {
\r
3558 sprintf(move, "%c@%c%c\n",
\r
3559 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3561 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3562 sprintf(move, "%c%c%c%c\n",
\r
3563 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3565 sprintf(move, "%c%c%c%c%c\n",
\r
3566 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3569 AlphaRank(move, 4);
\r
3573 ProcessICSInitScript(f)
\r
3576 char buf[MSG_SIZ];
\r
3578 while (fgets(buf, MSG_SIZ, f)) {
\r
3579 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3586 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3588 AlphaRank(char *move, int n)
\r
3590 char *p = move, c;
\r
3592 if( !appData.alphaRank ) return;
\r
3595 if(c>='0' && c<='9') *p += 'a'-'0'; else
\r
3596 if(c>='a' && c<='z') *p -= 'a'-'0';
\r
3598 if(--n < 1) break;
\r
3602 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3604 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3607 ChessMove *moveType;
\r
3608 int *fromX, *fromY, *toX, *toY;
\r
3611 if (appData.debugMode) {
\r
3612 fprintf(debugFP, "move to parse: %s\n", move);
\r
3614 AlphaRank(move, 10);
\r
3615 *moveType = yylexstr(moveNum, move);
\r
3617 switch (*moveType) {
\r
3619 case WhitePromotionChancellor:
\r
3620 case BlackPromotionChancellor:
\r
3621 case WhitePromotionArchbishop:
\r
3622 case BlackPromotionArchbishop:
\r
3624 case WhitePromotionQueen:
\r
3625 case BlackPromotionQueen:
\r
3626 case WhitePromotionRook:
\r
3627 case BlackPromotionRook:
\r
3628 case WhitePromotionBishop:
\r
3629 case BlackPromotionBishop:
\r
3630 case WhitePromotionKnight:
\r
3631 case BlackPromotionKnight:
\r
3632 case WhitePromotionKing:
\r
3633 case BlackPromotionKing:
\r
3635 case WhiteCapturesEnPassant:
\r
3636 case BlackCapturesEnPassant:
\r
3637 case WhiteKingSideCastle:
\r
3638 case WhiteQueenSideCastle:
\r
3639 case BlackKingSideCastle:
\r
3640 case BlackQueenSideCastle:
\r
3641 case WhiteKingSideCastleWild:
\r
3642 case WhiteQueenSideCastleWild:
\r
3643 case BlackKingSideCastleWild:
\r
3644 case BlackQueenSideCastleWild:
\r
3645 /* Code added by Tord: */
\r
3646 case WhiteHSideCastleFR:
\r
3647 case WhiteASideCastleFR:
\r
3648 case BlackHSideCastleFR:
\r
3649 case BlackASideCastleFR:
\r
3650 /* End of code added by Tord */
\r
3651 case IllegalMove: /* bug or odd chess variant */
\r
3652 *fromX = currentMoveString[0] - AAA;
\r
3653 *fromY = currentMoveString[1] - ONE;
\r
3654 *toX = currentMoveString[2] - AAA;
\r
3655 *toY = currentMoveString[3] - ONE;
\r
3656 *promoChar = currentMoveString[4];
\r
3657 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3658 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3659 *fromX = *fromY = *toX = *toY = 0;
\r
3662 if (appData.testLegality) {
\r
3663 return (*moveType != IllegalMove);
\r
3665 return !(fromX == fromY && toX == toY);
\r
3670 *fromX = *moveType == WhiteDrop ?
\r
3671 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3672 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3673 *fromY = DROP_RANK;
\r
3674 *toX = currentMoveString[2] - AAA;
\r
3675 *toY = currentMoveString[3] - ONE;
\r
3676 *promoChar = NULLCHAR;
\r
3679 case AmbiguousMove:
\r
3680 case ImpossibleMove:
\r
3681 case (ChessMove) 0: /* end of file */
\r
3691 *fromX = *fromY = *toX = *toY = 0;
\r
3692 *promoChar = NULLCHAR;
\r
3697 /* [AS] FRC game initialization */
\r
3698 static int FindEmptySquare( Board board, int n )
\r
3703 while( board[0][i] != EmptySquare ) i++;
\r
3713 static void ShuffleFRC( Board board )
\r
3719 for( i=0; i<8; i++ ) {
\r
3720 board[0][i] = EmptySquare;
\r
3723 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3724 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
3725 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
3726 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
3727 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
3728 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3729 board[0][FindEmptySquare(board, 0)] = WhiteKing;
\r
3730 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3732 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3733 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3737 static unsigned char FRC_KnightTable[10] = {
\r
3738 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
3741 static void SetupFRC( Board board, int pos_index )
\r
3744 unsigned char knights;
\r
3746 /* Bring the position index into a safe range (just in case...) */
\r
3747 if( pos_index < 0 ) pos_index = 0;
\r
3751 /* Clear the board */
\r
3752 for( i=0; i<8; i++ ) {
\r
3753 board[0][i] = EmptySquare;
\r
3756 /* Place bishops and queen */
\r
3757 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
3760 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3763 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
3766 /* Place knigths */
\r
3767 knights = FRC_KnightTable[ pos_index ];
\r
3769 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
3770 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
3772 /* Place rooks and king */
\r
3773 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3774 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
\r
3775 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3777 /* Mirror piece placement for black */
\r
3778 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3779 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3783 BOOL SetCharTable( char *table, const char * map )
\r
3784 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
3785 /* Basically a safe strcpy that uses the last character as King */
\r
3787 BOOL result = FALSE; int NrPieces;
\r
3789 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
3790 && NrPieces >= 12 && !(NrPieces&1)) {
\r
3791 int i; /* [HGM] Accept even length from 12 to 34 */
\r
3793 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
3794 for( i=0; i<NrPieces/2-1; i++ ) {
\r
3795 table[i] = map[i];
\r
3796 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
3798 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
3799 table[(int) BlackKing] = map[NrPieces-1];
\r
3808 InitPosition(redraw)
\r
3811 ChessSquare (* pieces)[BOARD_SIZE];
\r
3812 int i, j, pawnRow, overrule,
\r
3813 oldx = gameInfo.boardWidth,
\r
3814 oldy = gameInfo.boardHeight,
\r
3815 oldh = gameInfo.holdingsWidth,
\r
3816 oldv = gameInfo.variant;
\r
3818 currentMove = forwardMostMove = backwardMostMove = 0;
\r
3820 /* [AS] Initialize pv info list [HGM] and game status */
\r
3822 for( i=0; i<MAX_MOVES; i++ ) {
\r
3823 pvInfoList[i].depth = 0;
\r
3824 epStatus[i]=EP_NONE;
\r
3825 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
3828 initialRulePlies = 0; /* 50-move counter start */
\r
3832 /* [HGM] logic here is completely changed. In stead of full positions */
\r
3833 /* the initialized data only consist of the two backranks. The switch */
\r
3834 /* selects which one we will use, which is than copied to the Board */
\r
3835 /* initialPosition, which for the rest is initialized by Pawns and */
\r
3836 /* empty squares. This initial position is then copied to boards[0], */
\r
3837 /* possibly after shuffling, so that it remains available. */
\r
3839 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
3840 gameInfo.boardWidth = 8;
\r
3841 gameInfo.boardHeight = 8;
\r
3842 gameInfo.holdingsSize = 0;
\r
3843 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
3844 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
3845 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
3847 switch (gameInfo.variant) {
\r
3849 pieces = FIDEArray;
\r
3851 case VariantShatranj:
\r
3852 pieces = ShatranjArray;
\r
3853 nrCastlingRights = 0;
\r
3855 case VariantTwoKings:
\r
3856 pieces = twoKingsArray;
\r
3857 nrCastlingRights = 8; /* add rights for second King */
\r
3858 castlingRights[0][6] = initialRights[2] = 5;
\r
3859 castlingRights[0][7] = initialRights[5] = 5;
\r
3860 castlingRank[6] = 0;
\r
3861 castlingRank[6] = BOARD_HEIGHT-1;
\r
3862 startedFromSetupPosition = TRUE;
\r
3864 case VariantCapablanca:
\r
3865 pieces = CapablancaArray;
\r
3866 gameInfo.boardWidth = 10;
\r
3867 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3869 case VariantGothic:
\r
3870 pieces = GothicArray;
\r
3871 gameInfo.boardWidth = 10;
\r
3872 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3874 case VariantXiangqi:
\r
3875 pieces = XiangqiArray;
\r
3876 gameInfo.boardWidth = 9;
\r
3877 gameInfo.boardHeight = 10;
\r
3878 nrCastlingRights = 0;
\r
3879 SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c.......");
\r
3881 case VariantShogi:
\r
3882 pieces = ShogiArray;
\r
3883 gameInfo.boardWidth = 9;
\r
3884 gameInfo.boardHeight = 9;
\r
3885 gameInfo.holdingsSize = 7;
\r
3886 nrCastlingRights = 0;
\r
3887 SetCharTable(pieceToChar, "PNBRLSG.........Kpnbrlsg.........k");
\r
3889 case VariantShowgi:
\r
3890 pieces = ShogiArray;
\r
3891 gameInfo.boardWidth = 9;
\r
3892 gameInfo.boardHeight = 9;
\r
3893 gameInfo.holdingsSize = 7;
\r
3894 nrCastlingRights = 0;
\r
3895 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3896 SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k");
\r
3898 case VariantCourier:
\r
3899 pieces = CourierArray;
\r
3900 gameInfo.boardWidth = 12;
\r
3901 nrCastlingRights = 0;
\r
3902 SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k");
\r
3903 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3905 case VariantKnightmate:
\r
3906 pieces = KnightmateArray;
\r
3907 strcpy(pieceToChar, "P.BRQ...M.K......p.brq...m.k......");
\r
3909 case VariantFairy:
\r
3910 pieces = fairyArray;
\r
3911 SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk");
\r
3912 startedFromSetupPosition = TRUE;
\r
3914 case VariantCrazyhouse:
\r
3915 case VariantBughouse:
\r
3916 pieces = FIDEArray;
\r
3917 gameInfo.holdingsSize = 5;
\r
3919 case VariantWildCastle:
\r
3920 pieces = FIDEArray;
\r
3921 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
3923 case VariantNoCastle:
\r
3924 pieces = FIDEArray;
\r
3925 nrCastlingRights = 0;
\r
3926 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3927 /* !!?unconstrained back-rank shuffle */
\r
3932 if(appData.NrFiles >= 0) {
\r
3933 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
3934 gameInfo.boardWidth = appData.NrFiles;
\r
3936 if(appData.NrRanks >= 0) {
\r
3937 gameInfo.boardHeight = appData.NrRanks;
\r
3939 if(appData.holdingsSize >= 0) {
\r
3940 i = appData.holdingsSize;
\r
3941 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
3942 gameInfo.holdingsSize = i;
\r
3944 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
3945 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
3946 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
3948 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
3949 if(pawnRow < 1) pawnRow = 1;
\r
3951 /* User pieceToChar list overrules defaults */
\r
3952 if(appData.pieceToCharTable != NULL)
\r
3953 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
3955 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
3957 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
3958 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
3959 for( i=0; i<BOARD_HEIGHT; i++ )
\r
3960 initialPosition[i][j] = s;
\r
3962 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
3963 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
3964 initialPosition[pawnRow][j] = WhitePawn;
\r
3965 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
3966 if(gameInfo.variant == VariantXiangqi) {
\r
3968 initialPosition[pawnRow][j] =
\r
3969 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
3970 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
3971 initialPosition[2][j] = WhiteCannon;
\r
3972 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
3976 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
3978 if( (gameInfo.variant == VariantShogi
\r
3979 ||gameInfo.variant == VariantShowgi
\r
3980 ) && !overrule ) {
\r
3982 initialPosition[1][j] = WhiteBishop;
\r
3983 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
3985 initialPosition[1][j] = WhiteRook;
\r
3986 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
3989 if( nrCastlingRights == -1) {
\r
3990 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
3991 /* This sets default castling rights from none to normal corners */
\r
3992 /* Variants with other castling rights must set them themselves above */
\r
3993 nrCastlingRights = 6;
\r
3995 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
3996 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
3997 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
3998 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
3999 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4000 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4002 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4003 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4006 if(gameInfo.variant == VariantFischeRandom) {
\r
4007 if( appData.defaultFrcPosition < 0 ) {
\r
4008 ShuffleFRC( initialPosition );
\r
4011 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4015 CopyBoard(boards[0], initialPosition);
\r
4017 if(oldx != gameInfo.boardWidth ||
\r
4018 oldy != gameInfo.boardHeight ||
\r
4019 oldh != gameInfo.holdingsWidth
\r
4021 || oldv == VariantGothic ||
\r
4022 gameInfo.variant == VariantGothic
\r
4025 InitDrawingSizes(-2 ,0);
\r
4028 DrawPosition(TRUE, boards[currentMove]);
\r
4032 SendBoard(cps, moveNum)
\r
4033 ChessProgramState *cps;
\r
4036 char message[MSG_SIZ];
\r
4038 if (cps->useSetboard) {
\r
4039 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4040 sprintf(message, "setboard %s\n", fen);
\r
4041 SendToProgram(message, cps);
\r
4047 /* Kludge to set black to move, avoiding the troublesome and now
\r
4048 * deprecated "black" command.
\r
4050 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4052 SendToProgram("edit\n", cps);
\r
4053 SendToProgram("#\n", cps);
\r
4054 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4055 bp = &boards[moveNum][i][0];
\r
4056 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4057 if ((int) *bp < (int) BlackPawn) {
\r
4058 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4059 AAA + j, ONE + i);
\r
4060 SendToProgram(message, cps);
\r
4065 SendToProgram("c\n", cps);
\r
4066 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4067 bp = &boards[moveNum][i][0];
\r
4068 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4069 if (((int) *bp != (int) EmptySquare)
\r
4070 && ((int) *bp >= (int) BlackPawn)) {
\r
4071 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4072 AAA + j, ONE + i);
\r
4073 SendToProgram(message, cps);
\r
4078 SendToProgram(".\n", cps);
\r
4083 IsPromotion(fromX, fromY, toX, toY)
\r
4084 int fromX, fromY, toX, toY;
\r
4086 /* [HGM] add Shogi promotions */
\r
4087 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4088 ChessSquare piece;
\r
4090 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4091 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4092 /* [HGM] Note to self: line above also weeds out drops */
\r
4093 piece = boards[currentMove][fromY][fromX];
\r
4094 if(gameInfo.variant == VariantShogi) {
\r
4095 promotionZoneSize = 3;
\r
4096 highestPromotingPiece = (int)WhiteKing;
\r
4097 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4098 and if in normal chess we then allow promotion to King, why not
\r
4099 allow promotion of other piece in Shogi? */
\r
4101 if((int)piece >= BlackPawn) {
\r
4102 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4104 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4106 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4107 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4109 return ( (int)piece <= highestPromotingPiece );
\r
4113 InPalace(row, column)
\r
4115 { /* [HGM] for Xiangqi */
\r
4116 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4117 column < (BOARD_WIDTH + 4)/2 &&
\r
4118 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4123 PieceForSquare (x, y)
\r
4127 if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)
\r
4130 return boards[currentMove][y][x];
\r
4134 OKToStartUserMove(x, y)
\r
4137 ChessSquare from_piece;
\r
4140 if (matchMode) return FALSE;
\r
4141 if (gameMode == EditPosition) return TRUE;
\r
4143 if (x >= 0 && y >= 0)
\r
4144 from_piece = boards[currentMove][y][x];
\r
4146 from_piece = EmptySquare;
\r
4148 if (from_piece == EmptySquare) return FALSE;
\r
4150 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4151 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4153 switch (gameMode) {
\r
4154 case PlayFromGameFile:
\r
4156 case TwoMachinesPlay:
\r
4160 case IcsObserving:
\r
4164 case MachinePlaysWhite:
\r
4165 case IcsPlayingBlack:
\r
4166 if (appData.zippyPlay) return FALSE;
\r
4167 if (white_piece) {
\r
4168 DisplayMoveError("You are playing Black");
\r
4173 case MachinePlaysBlack:
\r
4174 case IcsPlayingWhite:
\r
4175 if (appData.zippyPlay) return FALSE;
\r
4176 if (!white_piece) {
\r
4177 DisplayMoveError("You are playing White");
\r
4183 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4184 DisplayMoveError("It is White's turn");
\r
4187 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4188 DisplayMoveError("It is Black's turn");
\r
4191 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4192 /* Editing correspondence game history */
\r
4193 /* Could disallow this or prompt for confirmation */
\r
4194 cmailOldMove = -1;
\r
4196 if (currentMove < forwardMostMove) {
\r
4197 /* Discarding moves */
\r
4198 /* Could prompt for confirmation here,
\r
4199 but I don't think that's such a good idea */
\r
4200 forwardMostMove = currentMove;
\r
4204 case BeginningOfGame:
\r
4205 if (appData.icsActive) return FALSE;
\r
4206 if (!appData.noChessProgram) {
\r
4207 if (!white_piece) {
\r
4208 DisplayMoveError("You are playing White");
\r
4215 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4216 DisplayMoveError("It is White's turn");
\r
4219 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4220 DisplayMoveError("It is Black's turn");
\r
4226 case IcsExamining:
\r
4229 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4230 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4231 DisplayMoveError("Displayed position is not current");
\r
4237 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4238 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4239 int lastLoadGameUseList = FALSE;
\r
4240 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4241 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4245 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4246 int fromX, fromY, toX, toY;
\r
4249 ChessMove moveType;
\r
4250 ChessSquare pdown, pup;
\r
4252 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4253 if ((fromX == toX) && (fromY == toY)) {
\r
4254 return ImpossibleMove;
\r
4257 /* Check if the user is playing in turn. This is complicated because we
\r
4258 let the user "pick up" a piece before it is his turn. So the piece he
\r
4259 tried to pick up may have been captured by the time he puts it down!
\r
4260 Therefore we use the color the user is supposed to be playing in this
\r
4261 test, not the color of the piece that is currently on the starting
\r
4262 square---except in EditGame mode, where the user is playing both
\r
4263 sides; fortunately there the capture race can't happen. (It can
\r
4264 now happen in IcsExamining mode, but that's just too bad. The user
\r
4265 will get a somewhat confusing message in that case.)
\r
4268 switch (gameMode) {
\r
4269 case PlayFromGameFile:
\r
4271 case TwoMachinesPlay:
\r
4273 case IcsObserving:
\r
4275 /* We switched into a game mode where moves are not accepted,
\r
4276 perhaps while the mouse button was down. */
\r
4277 return ImpossibleMove;
\r
4279 case MachinePlaysWhite:
\r
4280 /* User is moving for Black */
\r
4281 if (WhiteOnMove(currentMove)) {
\r
4282 DisplayMoveError("It is White's turn");
\r
4283 return ImpossibleMove;
\r
4287 case MachinePlaysBlack:
\r
4288 /* User is moving for White */
\r
4289 if (!WhiteOnMove(currentMove)) {
\r
4290 DisplayMoveError("It is Black's turn");
\r
4291 return ImpossibleMove;
\r
4296 case IcsExamining:
\r
4297 case BeginningOfGame:
\r
4300 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4301 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4302 /* User is moving for Black */
\r
4303 if (WhiteOnMove(currentMove)) {
\r
4304 DisplayMoveError("It is White's turn");
\r
4305 return ImpossibleMove;
\r
4308 /* User is moving for White */
\r
4309 if (!WhiteOnMove(currentMove)) {
\r
4310 DisplayMoveError("It is Black's turn");
\r
4311 return ImpossibleMove;
\r
4316 case IcsPlayingBlack:
\r
4317 /* User is moving for Black */
\r
4318 if (WhiteOnMove(currentMove)) {
\r
4319 if (!appData.premove) {
\r
4320 DisplayMoveError("It is White's turn");
\r
4321 } else if (toX >= 0 && toY >= 0) {
\r
4324 premoveFromX = fromX;
\r
4325 premoveFromY = fromY;
\r
4326 premovePromoChar = promoChar;
\r
4328 if (appData.debugMode)
\r
4329 fprintf(debugFP, "Got premove: fromX %d,"
\r
4330 "fromY %d, toX %d, toY %d\n",
\r
4331 fromX, fromY, toX, toY);
\r
4333 return ImpossibleMove;
\r
4337 case IcsPlayingWhite:
\r
4338 /* User is moving for White */
\r
4339 if (!WhiteOnMove(currentMove)) {
\r
4340 if (!appData.premove) {
\r
4341 DisplayMoveError("It is Black's turn");
\r
4342 } else if (toX >= 0 && toY >= 0) {
\r
4345 premoveFromX = fromX;
\r
4346 premoveFromY = fromY;
\r
4347 premovePromoChar = promoChar;
\r
4349 if (appData.debugMode)
\r
4350 fprintf(debugFP, "Got premove: fromX %d,"
\r
4351 "fromY %d, toX %d, toY %d\n",
\r
4352 fromX, fromY, toX, toY);
\r
4354 return ImpossibleMove;
\r
4361 case EditPosition:
\r
4362 /* EditPosition, empty square, or different color piece;
\r
4363 click-click move is possible */
\r
4364 if (toX == -2 || toY == -2) {
\r
4365 boards[0][fromY][fromX] = EmptySquare;
\r
4366 DrawPosition(FALSE, boards[currentMove]);
\r
4367 } else if (toX >= 0 && toY >= 0) {
\r
4368 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4369 boards[0][fromY][fromX] = EmptySquare;
\r
4370 DrawPosition(FALSE, boards[currentMove]);
\r
4372 return ImpossibleMove;
\r
4375 /* [HGM] suppress all moves into holdings area and guard band */
\r
4376 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4377 return ImpossibleMove;
\r
4379 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4380 /* note: EditPosition already filtered out and performed! */
\r
4381 pdown = boards[currentMove][fromY][fromX];
\r
4382 pup = boards[currentMove][toY][toX];
\r
4384 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4385 WhitePawn <= pup && pup < BlackPawn) ||
\r
4386 (BlackPawn <= pdown && pdown < EmptySquare &&
\r
4387 BlackPawn <= pup && pup < EmptySquare) )
\r
4388 return ImpossibleMove;
\r
4390 /* [HGM] If move started in holdings, it means a drop */
\r
4391 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4392 if( pup != EmptySquare ) return ImpossibleMove;
\r
4393 if(appData.testLegality) {
\r
4394 /* it would be more logical if LegalityTest() also figured out
\r
4395 * which drops are legal. For now we forbid pawns on back rank.
\r
4396 * Shogi is on its own here...
\r
4398 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
4399 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
4400 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
4402 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4405 userOfferedDraw = FALSE;
\r
4407 /* [HGM] always test for legality, to get promotion info */
\r
4408 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4409 epStatus[currentMove], castlingRights[currentMove],
\r
4410 fromY, fromX, toY, toX, promoChar);
\r
4412 /* [HGM] but possibly ignore an IllegalMove result */
\r
4413 if (appData.testLegality) {
\r
4414 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4415 DisplayMoveError("Illegal move");
\r
4416 return ImpossibleMove;
\r
4421 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4422 function is made into one that returns an OK move type if FinishMove
\r
4423 should be called. This to give the calling driver routine the
\r
4424 opportunity to finish the userMove input with a promotion popup,
\r
4425 without bothering the user with this for invalid or illegal moves */
\r
4427 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4430 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4432 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4433 ChessMove moveType;
\r
4434 int fromX, fromY, toX, toY;
\r
4435 /*char*/int promoChar;
\r
4437 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4438 move type in caller when we know the move is a legal promotion */
\r
4439 if(moveType == NormalMove)
\r
4440 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4442 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4443 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4444 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4445 fromX = boards[currentMove][fromY][fromX];
\r
4446 fromY = DROP_RANK;
\r
4449 /* [HGM] <popupFix> The following if has been moved here from
\r
4450 UserMoveEnevt(). Because it seemed to belon here (why not allow
\r
4451 piece drops in training games?), and because it can only be
\r
4452 performed after it is known to what we promote. */
\r
4453 if (gameMode == Training) {
\r
4454 /* compare the move played on the board to the next move in the
\r
4455 * game. If they match, display the move and the opponent's response.
\r
4456 * If they don't match, display an error message.
\r
4460 CopyBoard(testBoard, boards[currentMove]);
\r
4461 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4463 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4464 ForwardInner(currentMove+1);
\r
4466 /* Autoplay the opponent's response.
\r
4467 * if appData.animate was TRUE when Training mode was entered,
\r
4468 * the response will be animated.
\r
4470 saveAnimate = appData.animate;
\r
4471 appData.animate = animateTraining;
\r
4472 ForwardInner(currentMove+1);
\r
4473 appData.animate = saveAnimate;
\r
4475 /* check for the end of the game */
\r
4476 if (currentMove >= forwardMostMove) {
\r
4477 gameMode = PlayFromGameFile;
\r
4479 SetTrainingModeOff();
\r
4480 DisplayInformation("End of game");
\r
4483 DisplayError("Incorrect move", 0);
\r
4488 /* Ok, now we know that the move is good, so we can kill
\r
4489 the previous line in Analysis Mode */
\r
4490 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4491 forwardMostMove = currentMove;
\r
4494 /* If we need the chess program but it's dead, restart it */
\r
4495 ResurrectChessProgram();
\r
4497 /* A user move restarts a paused game*/
\r
4501 thinkOutput[0] = NULLCHAR;
\r
4503 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4505 if (gameMode == BeginningOfGame) {
\r
4506 if (appData.noChessProgram) {
\r
4507 gameMode = EditGame;
\r
4510 char buf[MSG_SIZ];
\r
4511 gameMode = MachinePlaysBlack;
\r
4513 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4514 DisplayTitle(buf);
\r
4515 if (first.sendName) {
\r
4516 sprintf(buf, "name %s\n", gameInfo.white);
\r
4517 SendToProgram(buf, &first);
\r
4523 /* Relay move to ICS or chess engine */
\r
4524 if (appData.icsActive) {
\r
4525 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4526 gameMode == IcsExamining) {
\r
4527 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4528 ics_user_moved = 1;
\r
4531 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4532 gameMode == MachinePlaysWhite ||
\r
4533 gameMode == MachinePlaysBlack)) {
\r
4534 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4536 SendMoveToProgram(forwardMostMove-1, &first);
\r
4537 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4538 first.maybeThinking = TRUE;
\r
4540 if (currentMove == cmailOldMove + 1) {
\r
4541 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4545 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4547 switch (gameMode) {
\r
4549 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4550 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4554 case MT_CHECKMATE:
\r
4555 if (WhiteOnMove(currentMove)) {
\r
4556 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4558 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4561 case MT_STALEMATE:
\r
4562 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4567 case MachinePlaysBlack:
\r
4568 case MachinePlaysWhite:
\r
4569 /* disable certain menu options while machine is thinking */
\r
4570 SetMachineThinkingEnables();
\r
4579 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
4580 int fromX, fromY, toX, toY;
\r
4583 /* [HGM] This routine was added to allow calling of its two logical
\r
4584 parts from other modules in the old way. Before, UserMoveEvent()
\r
4585 automatically called FinishMove() if the move was OK, and returned
\r
4586 otherwise. I separated the two, in order to make it possible to
\r
4587 slip a promotion popup in between. But that it always needs two
\r
4588 calls, to the first part, (now called UserMoveTest() ), and to
\r
4589 FinishMove if the first part succeeded. Calls that do not need
\r
4590 to do anything in between, can call this routine the old way.
\r
4592 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
4594 if(moveType != ImpossibleMove)
\r
4595 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4598 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4600 char * hint = lastHint;
\r
4601 FrontEndProgramStats stats;
\r
4603 stats.which = cps == &first ? 0 : 1;
\r
4604 stats.depth = cpstats->depth;
\r
4605 stats.nodes = cpstats->nodes;
\r
4606 stats.score = cpstats->score;
\r
4607 stats.time = cpstats->time;
\r
4608 stats.pv = cpstats->movelist;
\r
4609 stats.hint = lastHint;
\r
4610 stats.an_move_index = 0;
\r
4611 stats.an_move_count = 0;
\r
4613 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4614 stats.hint = cpstats->move_name;
\r
4615 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4616 stats.an_move_count = cpstats->nr_moves;
\r
4619 SetProgramStats( &stats );
\r
4623 HandleMachineMove(message, cps)
\r
4625 ChessProgramState *cps;
\r
4627 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4628 char realname[MSG_SIZ];
\r
4629 int fromX, fromY, toX, toY;
\r
4630 ChessMove moveType;
\r
4636 * Kludge to ignore BEL characters
\r
4638 while (*message == '\007') message++;
\r
4641 * Look for book output
\r
4643 if (cps == &first && bookRequested) {
\r
4644 if (message[0] == '\t' || message[0] == ' ') {
\r
4645 /* Part of the book output is here; append it */
\r
4646 strcat(bookOutput, message);
\r
4647 strcat(bookOutput, " \n");
\r
4649 } else if (bookOutput[0] != NULLCHAR) {
\r
4650 /* All of book output has arrived; display it */
\r
4651 char *p = bookOutput;
\r
4652 while (*p != NULLCHAR) {
\r
4653 if (*p == '\t') *p = ' ';
\r
4656 DisplayInformation(bookOutput);
\r
4657 bookRequested = FALSE;
\r
4658 /* Fall through to parse the current output */
\r
4663 * Look for machine move.
\r
4665 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4666 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4668 /* This method is only useful on engines that support ping */
\r
4669 if (cps->lastPing != cps->lastPong) {
\r
4670 if (gameMode == BeginningOfGame) {
\r
4671 /* Extra move from before last new; ignore */
\r
4672 if (appData.debugMode) {
\r
4673 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4676 if (appData.debugMode) {
\r
4677 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4678 cps->which, gameMode);
\r
4681 SendToProgram("undo\n", cps);
\r
4686 switch (gameMode) {
\r
4687 case BeginningOfGame:
\r
4688 /* Extra move from before last reset; ignore */
\r
4689 if (appData.debugMode) {
\r
4690 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4697 /* Extra move after we tried to stop. The mode test is
\r
4698 not a reliable way of detecting this problem, but it's
\r
4699 the best we can do on engines that don't support ping.
\r
4701 if (appData.debugMode) {
\r
4702 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4703 cps->which, gameMode);
\r
4705 SendToProgram("undo\n", cps);
\r
4708 case MachinePlaysWhite:
\r
4709 case IcsPlayingWhite:
\r
4710 machineWhite = TRUE;
\r
4713 case MachinePlaysBlack:
\r
4714 case IcsPlayingBlack:
\r
4715 machineWhite = FALSE;
\r
4718 case TwoMachinesPlay:
\r
4719 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
4722 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
4723 if (appData.debugMode) {
\r
4725 "Ignoring move out of turn by %s, gameMode %d"
\r
4726 ", forwardMost %d\n",
\r
4727 cps->which, gameMode, forwardMostMove);
\r
4732 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
4733 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4734 /* Machine move could not be parsed; ignore it. */
\r
4735 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
4736 machineMove, cps->which);
\r
4737 DisplayError(buf1, 0);
\r
4738 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
4739 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4740 if (gameMode == TwoMachinesPlay) {
\r
4741 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4747 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
4748 /* So we have to redo legality test with true e.p. status here, */
\r
4749 /* to make sure an illegal e.p. capture does not slip through, */
\r
4750 /* to cause a forfeit on a justified illegal-move complaint */
\r
4751 /* of the opponent. */
\r
4752 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
4753 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
4755 ChessMove moveType;
\r
4756 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
4757 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
4758 fromY, fromX, toY, toX, promoChar);
\r
4759 if (appData.debugMode) {
\r
4761 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
4762 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
4763 fprintf(debugFP, "castling rights\n");
\r
4765 if(moveType == IllegalMove) {
\r
4766 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
4767 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4768 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4770 } else if(gameInfo.variant != VariantFischeRandom)
\r
4771 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
4772 when they shouldn't (like TSCP-Gothic) */
\r
4773 switch(moveType) {
\r
4774 case WhiteASideCastleFR:
\r
4775 case BlackASideCastleFR:
\r
4777 currentMoveString[2]++;
\r
4779 case WhiteHSideCastleFR:
\r
4780 case BlackHSideCastleFR:
\r
4782 currentMoveString[2]--;
\r
4786 hintRequested = FALSE;
\r
4787 lastHint[0] = NULLCHAR;
\r
4788 bookRequested = FALSE;
\r
4789 /* Program may be pondering now */
\r
4790 cps->maybeThinking = TRUE;
\r
4791 if (cps->sendTime == 2) cps->sendTime = 1;
\r
4792 if (cps->offeredDraw) cps->offeredDraw--;
\r
4795 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
4797 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4798 ics_user_moved = 1;
\r
4801 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
4802 strcpy(machineMove, currentMoveString);
\r
4803 strcat(machineMove, "\n");
\r
4804 strcpy(moveList[forwardMostMove], machineMove);
\r
4806 /* [AS] Save move info and clear stats for next move */
\r
4807 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
4808 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
4809 pvInfoList[ forwardMostMove ].time = -1;
\r
4810 ClearProgramStats();
\r
4811 thinkOutput[0] = NULLCHAR;
\r
4812 hiddenThinkOutputState = 0;
\r
4814 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
4816 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
4817 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
4820 while( count < adjudicateLossPlies ) {
\r
4821 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
4824 score = -score; /* Flip score for winning side */
\r
4827 if( score > adjudicateLossThreshold ) {
\r
4834 if( count >= adjudicateLossPlies ) {
\r
4835 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4837 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
4838 "Xboard adjudication",
\r
4845 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
4847 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
4848 int count = 0, epFile = epStatus[forwardMostMove];
\r
4850 if(appData.testLegality && appData.checkMates)
\r
4851 // don't wait for engine to announce game end if we can judge ourselves
\r
4852 switch (MateTest(boards[forwardMostMove],
\r
4853 PosFlags(forwardMostMove), epFile,
\r
4854 castlingRights[forwardMostMove]) ) {
\r
4859 case MT_STALEMATE:
\r
4860 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4861 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
4864 case MT_CHECKMATE:
\r
4865 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4866 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
4867 "Xboard adjudication: Checkmate",
\r
4872 if( appData.testLegality )
\r
4873 { /* [HGM] Some more adjudications for obstinate engines */
\r
4874 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
4876 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
4877 static int moveCount;
\r
4879 /* First absolutely insufficient mating material. Count what is on board. */
\r
4880 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
4881 { ChessSquare p = boards[forwardMostMove][i][j];
\r
4885 { /* count B,N,R and other of each side */
\r
4902 case EmptySquare:
\r
4907 PawnAdvance += m; NrPawns++;
\r
4909 NrPieces += (p != EmptySquare);
\r
4912 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )
\r
4913 { /* KBK, KNK or KK */
\r
4915 /* always flag draws, for judging claims */
\r
4916 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
4918 if(appData.materialDraws) {
\r
4919 /* but only adjudicate them if adjudication enabled */
\r
4920 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4921 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
4926 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
4927 if(NrPieces == 4 &&
\r
4928 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
4929 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
4930 || NrWN==2 || NrBN==2 /* KNNK */
\r
4931 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
4933 if(--moveCount < 0 && appData.trivialDraws)
\r
4934 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
4935 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4936 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
4939 } else moveCount = 6;
\r
4941 if (appData.debugMode) { int i;
\r
4942 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
4943 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
4944 appData.drawRepeats);
\r
4945 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
4946 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
4949 /* Check for rep-draws */
\r
4951 for(k = forwardMostMove-2;
\r
4952 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
4953 epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
4956 if (appData.debugMode) {
\r
4957 fprintf(debugFP, " loop\n");
\r
4959 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
4960 if (appData.debugMode) {
\r
4961 fprintf(debugFP, "match\n");
\r
4963 /* compare castling rights */
\r
4964 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
4965 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
4966 rights++; /* King lost rights, while rook still had them */
\r
4967 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
4968 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
4969 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
4970 rights++; /* but at least one rook lost them */
\r
4972 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
4973 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
4975 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
4976 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
4977 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
4980 if (appData.debugMode) {
\r
4981 for(i=0; i<nrCastlingRights; i++)
\r
4982 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
4985 if (appData.debugMode) {
\r
4986 fprintf(debugFP, " %d %d\n", rights, k);
\r
4988 if( rights == 0 && ++count > appData.drawRepeats-2
\r
4989 && appData.drawRepeats > 1) {
\r
4990 /* adjudicate after user-specified nr of repeats */
\r
4991 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4992 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
4995 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
4996 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5000 /* Now we test for 50-move draws. Determine ply count */
\r
5001 count = forwardMostMove;
\r
5002 /* look for last irreversble move */
\r
5003 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5005 /* if we hit starting position, add initial plies */
\r
5006 if( count == backwardMostMove )
\r
5007 count -= initialRulePlies;
\r
5008 count = forwardMostMove - count;
\r
5010 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5011 /* this is used to judge if draw claims are legal */
\r
5012 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5013 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5014 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5022 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5023 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5025 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5030 if (gameMode == TwoMachinesPlay) {
\r
5031 if (cps->other->sendTime) {
\r
5032 SendTimeRemaining(cps->other,
\r
5033 cps->other->twoMachinesColor[0] == 'w');
\r
5035 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
5037 firstMove = FALSE;
\r
5038 if (cps->other->useColors) {
\r
5039 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5041 SendToProgram("go\n", cps->other);
\r
5043 cps->other->maybeThinking = TRUE;
\r
5046 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5048 if (!pausing && appData.ringBellAfterMoves) {
\r
5053 * Reenable menu items that were disabled while
\r
5054 * machine was thinking
\r
5056 if (gameMode != TwoMachinesPlay)
\r
5057 SetUserThinkingEnables();
\r
5062 /* Set special modes for chess engines. Later something general
\r
5063 * could be added here; for now there is just one kludge feature,
\r
5064 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5065 * when "xboard" is given as an interactive command.
\r
5067 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5068 cps->useSigint = FALSE;
\r
5069 cps->useSigterm = FALSE;
\r
5072 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5073 * want this, I was asked to put it in, and obliged.
\r
5075 if (!strncmp(message, "setboard ", 9)) {
\r
5076 Board initial_position; int i;
\r
5078 GameEnds(GameIsDrawn, "Engine aborts game", GE_XBOARD);
\r
5080 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5081 DisplayError("Bad FEN received from engine", 0);
\r
5084 Reset(FALSE, FALSE);
\r
5085 CopyBoard(boards[0], initial_position);
\r
5086 initialRulePlies = FENrulePlies;
\r
5087 epStatus[0] = FENepStatus;
\r
5088 for( i=0; i<nrCastlingRights; i++ )
\r
5089 castlingRights[0][i] = FENcastlingRights[i];
\r
5090 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5091 else gameMode = MachinePlaysBlack;
\r
5092 DrawPosition(FALSE, boards[currentMove]);
\r
5098 * Look for communication commands
\r
5100 if (!strncmp(message, "telluser ", 9)) {
\r
5101 DisplayNote(message + 9);
\r
5104 if (!strncmp(message, "tellusererror ", 14)) {
\r
5105 DisplayError(message + 14, 0);
\r
5108 if (!strncmp(message, "tellopponent ", 13)) {
\r
5109 if (appData.icsActive) {
\r
5111 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5115 DisplayNote(message + 13);
\r
5119 if (!strncmp(message, "tellothers ", 11)) {
\r
5120 if (appData.icsActive) {
\r
5122 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5128 if (!strncmp(message, "tellall ", 8)) {
\r
5129 if (appData.icsActive) {
\r
5131 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5135 DisplayNote(message + 8);
\r
5139 if (strncmp(message, "warning", 7) == 0) {
\r
5140 /* Undocumented feature, use tellusererror in new code */
\r
5141 DisplayError(message, 0);
\r
5144 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5145 strcpy(realname, cps->tidy);
\r
5146 strcat(realname, " query");
\r
5147 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5150 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5151 * sent until we are logged on. Crafty kibitzes have been known to
\r
5152 * interfere with the login process.
\r
5155 if (!strncmp(message, "tellics ", 8)) {
\r
5156 SendToICS(message + 8);
\r
5160 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5161 SendToICS(ics_prefix);
\r
5162 SendToICS(message + 15);
\r
5166 /* The following are for backward compatibility only */
\r
5167 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5168 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5169 SendToICS(ics_prefix);
\r
5170 SendToICS(message);
\r
5175 if (strncmp(message, "feature ", 8) == 0) {
\r
5176 ParseFeatures(message+8, cps);
\r
5178 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5182 * If the move is illegal, cancel it and redraw the board.
\r
5183 * Also deal with other error cases. Matching is rather loose
\r
5184 * here to accommodate engines written before the spec.
\r
5186 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5187 strncmp(message, "Error", 5) == 0) {
\r
5188 if (StrStr(message, "name") ||
\r
5189 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5190 StrStr(message, "result") || StrStr(message, "board") ||
\r
5191 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5192 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5193 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5194 StrStr(message, "accepted")) {
\r
5197 if (StrStr(message, "protover")) {
\r
5198 /* Program is responding to input, so it's apparently done
\r
5199 initializing, and this error message indicates it is
\r
5200 protocol version 1. So we don't need to wait any longer
\r
5201 for it to initialize and send feature commands. */
\r
5202 FeatureDone(cps, 1);
\r
5203 cps->protocolVersion = 1;
\r
5206 cps->maybeThinking = FALSE;
\r
5208 if (StrStr(message, "draw")) {
\r
5209 /* Program doesn't have "draw" command */
\r
5210 cps->sendDrawOffers = 0;
\r
5213 if (cps->sendTime != 1 &&
\r
5214 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5215 /* Program apparently doesn't have "time" or "otim" command */
\r
5216 cps->sendTime = 0;
\r
5219 if (StrStr(message, "analyze")) {
\r
5220 cps->analysisSupport = FALSE;
\r
5221 cps->analyzing = FALSE;
\r
5222 Reset(FALSE, TRUE);
\r
5223 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5224 DisplayError(buf2, 0);
\r
5227 if (StrStr(message, "(no matching move)st")) {
\r
5228 /* Special kludge for GNU Chess 4 only */
\r
5229 cps->stKludge = TRUE;
\r
5230 SendTimeControl(cps, movesPerSession, timeControl,
\r
5231 timeIncrement, appData.searchDepth,
\r
5235 if (StrStr(message, "(no matching move)sd")) {
\r
5236 /* Special kludge for GNU Chess 4 only */
\r
5237 cps->sdKludge = TRUE;
\r
5238 SendTimeControl(cps, movesPerSession, timeControl,
\r
5239 timeIncrement, appData.searchDepth,
\r
5243 if (!StrStr(message, "llegal")) {
\r
5246 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5247 gameMode == IcsIdle) return;
\r
5248 if (forwardMostMove <= backwardMostMove) return;
\r
5250 /* Following removed: it caused a bug where a real illegal move
\r
5251 message in analyze mored would be ignored. */
\r
5252 if (cps == &first && programStats.ok_to_send == 0) {
\r
5253 /* Bogus message from Crafty responding to "." This filtering
\r
5254 can miss some of the bad messages, but fortunately the bug
\r
5255 is fixed in current Crafty versions, so it doesn't matter. */
\r
5259 if (pausing) PauseEvent();
\r
5260 if (gameMode == PlayFromGameFile) {
\r
5261 /* Stop reading this game file */
\r
5262 gameMode = EditGame;
\r
5265 currentMove = --forwardMostMove;
\r
5266 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5268 DisplayBothClocks();
\r
5269 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5270 parseList[currentMove], cps->which);
\r
5271 DisplayMoveError(buf1);
\r
5272 DrawPosition(FALSE, boards[currentMove]);
\r
5274 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5275 /* only passes fully legal moves */
\r
5276 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5277 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5278 "False illegal-move claim", GE_XBOARD );
\r
5282 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5283 /* Program has a broken "time" command that
\r
5284 outputs a string not ending in newline.
\r
5286 cps->sendTime = 0;
\r
5290 * If chess program startup fails, exit with an error message.
\r
5291 * Attempts to recover here are futile.
\r
5293 if ((StrStr(message, "unknown host") != NULL)
\r
5294 || (StrStr(message, "No remote directory") != NULL)
\r
5295 || (StrStr(message, "not found") != NULL)
\r
5296 || (StrStr(message, "No such file") != NULL)
\r
5297 || (StrStr(message, "can't alloc") != NULL)
\r
5298 || (StrStr(message, "Permission denied") != NULL)) {
\r
5300 cps->maybeThinking = FALSE;
\r
5301 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5302 cps->which, cps->program, cps->host, message);
\r
5303 RemoveInputSource(cps->isr);
\r
5304 DisplayFatalError(buf1, 0, 1);
\r
5309 * Look for hint output
\r
5311 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5312 if (cps == &first && hintRequested) {
\r
5313 hintRequested = FALSE;
\r
5314 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5315 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5316 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5317 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5318 fromY, fromX, toY, toX, promoChar, buf1);
\r
5319 sprintf(buf2, "Hint: %s", buf1);
\r
5320 DisplayInformation(buf2);
\r
5322 /* Hint move could not be parsed!? */
\r
5324 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5325 buf1, cps->which);
\r
5326 DisplayError(buf2, 0);
\r
5329 strcpy(lastHint, buf1);
\r
5335 * Ignore other messages if game is not in progress
\r
5337 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5338 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5341 * look for win, lose, draw, or draw offer
\r
5343 if (strncmp(message, "1-0", 3) == 0) {
\r
5344 char *p, *q, *r = "";
\r
5345 p = strchr(message, '{');
\r
5347 q = strchr(p, '}');
\r
5353 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5355 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5356 char *p, *q, *r = "";
\r
5357 p = strchr(message, '{');
\r
5359 q = strchr(p, '}');
\r
5365 /* Kludge for Arasan 4.1 bug */
\r
5366 if (strcmp(r, "Black resigns") == 0) {
\r
5367 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5370 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5372 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5373 char *p, *q, *r = "";
\r
5374 p = strchr(message, '{');
\r
5376 q = strchr(p, '}');
\r
5383 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
5386 } else if (strncmp(message, "White resign", 12) == 0) {
\r
5387 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5389 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
5390 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5392 } else if (strncmp(message, "White", 5) == 0 &&
\r
5393 message[5] != '(' &&
\r
5394 StrStr(message, "Black") == NULL) {
\r
5395 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5397 } else if (strncmp(message, "Black", 5) == 0 &&
\r
5398 message[5] != '(') {
\r
5399 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5401 } else if (strcmp(message, "resign") == 0 ||
\r
5402 strcmp(message, "computer resigns") == 0) {
\r
5403 switch (gameMode) {
\r
5404 case MachinePlaysBlack:
\r
5405 case IcsPlayingBlack:
\r
5406 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5408 case MachinePlaysWhite:
\r
5409 case IcsPlayingWhite:
\r
5410 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5412 case TwoMachinesPlay:
\r
5413 if (cps->twoMachinesColor[0] == 'w')
\r
5414 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5416 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5419 /* can't happen */
\r
5423 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5424 switch (gameMode) {
\r
5425 case MachinePlaysBlack:
\r
5426 case IcsPlayingBlack:
\r
5427 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5429 case MachinePlaysWhite:
\r
5430 case IcsPlayingWhite:
\r
5431 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5433 case TwoMachinesPlay:
\r
5434 if (cps->twoMachinesColor[0] == 'w')
\r
5435 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5437 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5440 /* can't happen */
\r
5444 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5445 switch (gameMode) {
\r
5446 case MachinePlaysBlack:
\r
5447 case IcsPlayingBlack:
\r
5448 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5450 case MachinePlaysWhite:
\r
5451 case IcsPlayingWhite:
\r
5452 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5454 case TwoMachinesPlay:
\r
5455 if (cps->twoMachinesColor[0] == 'w')
\r
5456 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5458 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5461 /* can't happen */
\r
5465 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5466 if (WhiteOnMove(forwardMostMove)) {
\r
5467 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5469 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5472 } else if (strstr(message, "Draw") != NULL ||
\r
5473 strstr(message, "game is a draw") != NULL) {
\r
5474 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5476 } else if (strstr(message, "offer") != NULL &&
\r
5477 strstr(message, "draw") != NULL) {
\r
5479 if (appData.zippyPlay && first.initDone) {
\r
5480 /* Relay offer to ICS */
\r
5481 SendToICS(ics_prefix);
\r
5482 SendToICS("draw\n");
\r
5485 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5486 if (gameMode == TwoMachinesPlay) {
\r
5487 if (cps->other->offeredDraw) {
\r
5488 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5490 if (cps->other->sendDrawOffers) {
\r
5491 SendToProgram("draw\n", cps->other);
\r
5494 } else if (gameMode == MachinePlaysWhite ||
\r
5495 gameMode == MachinePlaysBlack) {
\r
5496 if (userOfferedDraw) {
\r
5497 DisplayInformation("Machine accepts your draw offer");
\r
5498 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5500 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5507 * Look for thinking output
\r
5509 if ( appData.showThinking) {
\r
5510 int plylev, mvleft, mvtot, curscore, time;
\r
5511 char mvname[MOVE_LEN];
\r
5512 unsigned long nodes;
\r
5514 int ignore = FALSE;
\r
5515 int prefixHint = FALSE;
\r
5516 mvname[0] = NULLCHAR;
\r
5518 switch (gameMode) {
\r
5519 case MachinePlaysBlack:
\r
5520 case IcsPlayingBlack:
\r
5521 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5523 case MachinePlaysWhite:
\r
5524 case IcsPlayingWhite:
\r
5525 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5530 case TwoMachinesPlay:
\r
5531 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5541 buf1[0] = NULLCHAR;
\r
5542 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5543 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5545 if (plyext != ' ' && plyext != '\t') {
\r
5549 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5550 if( cps->scoreIsAbsolute &&
\r
5551 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5553 curscore = -curscore;
\r
5557 programStats.depth = plylev;
\r
5558 programStats.nodes = nodes;
\r
5559 programStats.time = time;
\r
5560 programStats.score = curscore;
\r
5561 programStats.got_only_move = 0;
\r
5563 /* Buffer overflow protection */
\r
5564 if (buf1[0] != NULLCHAR) {
\r
5565 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5566 && appData.debugMode) {
\r
5568 "PV is too long; using the first %d bytes.\n",
\r
5569 sizeof(programStats.movelist) - 1);
\r
5572 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5574 sprintf(programStats.movelist, " no PV\n");
\r
5577 if (programStats.seen_stat) {
\r
5578 programStats.ok_to_send = 1;
\r
5581 if (strchr(programStats.movelist, '(') != NULL) {
\r
5582 programStats.line_is_book = 1;
\r
5583 programStats.nr_moves = 0;
\r
5584 programStats.moves_left = 0;
\r
5586 programStats.line_is_book = 0;
\r
5589 SendProgramStatsToFrontend( cps, &programStats );
\r
5592 [AS] Protect the thinkOutput buffer from overflow... this
\r
5593 is only useful if buf1 hasn't overflowed first!
\r
5595 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5597 (gameMode == TwoMachinesPlay ?
\r
5598 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5599 ((double) curscore) / 100.0,
\r
5600 prefixHint ? lastHint : "",
\r
5601 prefixHint ? " " : "" );
\r
5603 if( buf1[0] != NULLCHAR ) {
\r
5604 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5606 if( strlen(buf1) > max_len ) {
\r
5607 if( appData.debugMode) {
\r
5608 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5610 buf1[max_len+1] = '\0';
\r
5613 strcat( thinkOutput, buf1 );
\r
5616 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5617 DisplayMove(currentMove - 1);
\r
5618 DisplayAnalysis();
\r
5622 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
5623 /* crafty (9.25+) says "(only move) <move>"
\r
5624 * if there is only 1 legal move
\r
5626 sscanf(p, "(only move) %s", buf1);
\r
5627 sprintf(thinkOutput, "%s (only move)", buf1);
\r
5628 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
5629 programStats.depth = 1;
\r
5630 programStats.nr_moves = 1;
\r
5631 programStats.moves_left = 1;
\r
5632 programStats.nodes = 1;
\r
5633 programStats.time = 1;
\r
5634 programStats.got_only_move = 1;
\r
5636 /* Not really, but we also use this member to
\r
5637 mean "line isn't going to change" (Crafty
\r
5638 isn't searching, so stats won't change) */
\r
5639 programStats.line_is_book = 1;
\r
5641 SendProgramStatsToFrontend( cps, &programStats );
\r
5643 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5644 DisplayMove(currentMove - 1);
\r
5645 DisplayAnalysis();
\r
5648 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
5649 &time, &nodes, &plylev, &mvleft,
\r
5650 &mvtot, mvname) >= 5) {
\r
5651 /* The stat01: line is from Crafty (9.29+) in response
\r
5652 to the "." command */
\r
5653 programStats.seen_stat = 1;
\r
5654 cps->maybeThinking = TRUE;
\r
5656 if (programStats.got_only_move || !appData.periodicUpdates)
\r
5659 programStats.depth = plylev;
\r
5660 programStats.time = time;
\r
5661 programStats.nodes = nodes;
\r
5662 programStats.moves_left = mvleft;
\r
5663 programStats.nr_moves = mvtot;
\r
5664 strcpy(programStats.move_name, mvname);
\r
5665 programStats.ok_to_send = 1;
\r
5666 programStats.movelist[0] = '\0';
\r
5668 SendProgramStatsToFrontend( cps, &programStats );
\r
5670 DisplayAnalysis();
\r
5673 } else if (strncmp(message,"++",2) == 0) {
\r
5674 /* Crafty 9.29+ outputs this */
\r
5675 programStats.got_fail = 2;
\r
5678 } else if (strncmp(message,"--",2) == 0) {
\r
5679 /* Crafty 9.29+ outputs this */
\r
5680 programStats.got_fail = 1;
\r
5683 } else if (thinkOutput[0] != NULLCHAR &&
\r
5684 strncmp(message, " ", 4) == 0) {
\r
5685 unsigned message_len;
\r
5688 while (*p && *p == ' ') p++;
\r
5690 message_len = strlen( p );
\r
5692 /* [AS] Avoid buffer overflow */
\r
5693 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
5694 strcat(thinkOutput, " ");
\r
5695 strcat(thinkOutput, p);
\r
5698 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
5699 strcat(programStats.movelist, " ");
\r
5700 strcat(programStats.movelist, p);
\r
5703 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5704 DisplayMove(currentMove - 1);
\r
5705 DisplayAnalysis();
\r
5711 buf1[0] = NULLCHAR;
\r
5713 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5714 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
5716 ChessProgramStats cpstats;
\r
5718 if (plyext != ' ' && plyext != '\t') {
\r
5722 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5723 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
5724 curscore = -curscore;
\r
5727 cpstats.depth = plylev;
\r
5728 cpstats.nodes = nodes;
\r
5729 cpstats.time = time;
\r
5730 cpstats.score = curscore;
\r
5731 cpstats.got_only_move = 0;
\r
5732 cpstats.movelist[0] = '\0';
\r
5734 if (buf1[0] != NULLCHAR) {
\r
5735 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
5738 cpstats.ok_to_send = 0;
\r
5739 cpstats.line_is_book = 0;
\r
5740 cpstats.nr_moves = 0;
\r
5741 cpstats.moves_left = 0;
\r
5743 SendProgramStatsToFrontend( cps, &cpstats );
\r
5750 /* Parse a game score from the character string "game", and
\r
5751 record it as the history of the current game. The game
\r
5752 score is NOT assumed to start from the standard position.
\r
5753 The display is not updated in any way.
\r
5756 ParseGameHistory(game)
\r
5759 ChessMove moveType;
\r
5760 int fromX, fromY, toX, toY, boardIndex;
\r
5763 char buf[MSG_SIZ];
\r
5765 if (appData.debugMode)
\r
5766 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
5768 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
5769 gameInfo.site = StrSave(appData.icsHost);
\r
5770 gameInfo.date = PGNDate();
\r
5771 gameInfo.round = StrSave("-");
\r
5773 /* Parse out names of players */
\r
5774 while (*game == ' ') game++;
\r
5776 while (*game != ' ') *p++ = *game++;
\r
5778 gameInfo.white = StrSave(buf);
\r
5779 while (*game == ' ') game++;
\r
5781 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
5783 gameInfo.black = StrSave(buf);
\r
5786 boardIndex = blackPlaysFirst ? 1 : 0;
\r
5789 yyboardindex = boardIndex;
\r
5790 moveType = (ChessMove) yylex();
\r
5791 switch (moveType) {
\r
5793 case WhitePromotionChancellor:
\r
5794 case BlackPromotionChancellor:
\r
5795 case WhitePromotionArchbishop:
\r
5796 case BlackPromotionArchbishop:
\r
5798 case WhitePromotionQueen:
\r
5799 case BlackPromotionQueen:
\r
5800 case WhitePromotionRook:
\r
5801 case BlackPromotionRook:
\r
5802 case WhitePromotionBishop:
\r
5803 case BlackPromotionBishop:
\r
5804 case WhitePromotionKnight:
\r
5805 case BlackPromotionKnight:
\r
5806 case WhitePromotionKing:
\r
5807 case BlackPromotionKing:
\r
5809 case WhiteCapturesEnPassant:
\r
5810 case BlackCapturesEnPassant:
\r
5811 case WhiteKingSideCastle:
\r
5812 case WhiteQueenSideCastle:
\r
5813 case BlackKingSideCastle:
\r
5814 case BlackQueenSideCastle:
\r
5815 case WhiteKingSideCastleWild:
\r
5816 case WhiteQueenSideCastleWild:
\r
5817 case BlackKingSideCastleWild:
\r
5818 case BlackQueenSideCastleWild:
\r
5820 case WhiteHSideCastleFR:
\r
5821 case WhiteASideCastleFR:
\r
5822 case BlackHSideCastleFR:
\r
5823 case BlackASideCastleFR:
\r
5825 case IllegalMove: /* maybe suicide chess, etc. */
\r
5826 fromX = currentMoveString[0] - AAA;
\r
5827 fromY = currentMoveString[1] - ONE;
\r
5828 toX = currentMoveString[2] - AAA;
\r
5829 toY = currentMoveString[3] - ONE;
\r
5830 promoChar = currentMoveString[4];
\r
5834 fromX = moveType == WhiteDrop ?
\r
5835 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
5836 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
5837 fromY = DROP_RANK;
\r
5838 toX = currentMoveString[2] - AAA;
\r
5839 toY = currentMoveString[3] - ONE;
\r
5840 promoChar = NULLCHAR;
\r
5842 case AmbiguousMove:
\r
5844 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
5845 DisplayError(buf, 0);
\r
5847 case ImpossibleMove:
\r
5849 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
5850 DisplayError(buf, 0);
\r
5852 case (ChessMove) 0: /* end of file */
\r
5853 if (boardIndex < backwardMostMove) {
\r
5854 /* Oops, gap. How did that happen? */
\r
5855 DisplayError("Gap in move list", 0);
\r
5858 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5859 if (boardIndex > forwardMostMove) {
\r
5860 forwardMostMove = boardIndex;
\r
5864 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
5865 strcat(parseList[boardIndex-1], " ");
\r
5866 strcat(parseList[boardIndex-1], yy_text);
\r
5878 case GameUnfinished:
\r
5879 if (gameMode == IcsExamining) {
\r
5880 if (boardIndex < backwardMostMove) {
\r
5881 /* Oops, gap. How did that happen? */
\r
5884 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5887 gameInfo.result = moveType;
\r
5888 p = strchr(yy_text, '{');
\r
5889 if (p == NULL) p = strchr(yy_text, '(');
\r
5892 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
5894 q = strchr(p, *p == '{' ? '}' : ')');
\r
5895 if (q != NULL) *q = NULLCHAR;
\r
5898 gameInfo.resultDetails = StrSave(p);
\r
5901 if (boardIndex >= forwardMostMove &&
\r
5902 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
5903 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5906 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
5907 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
5908 parseList[boardIndex]);
\r
5909 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
5910 /* currentMoveString is set as a side-effect of yylex */
\r
5911 strcpy(moveList[boardIndex], currentMoveString);
\r
5912 strcat(moveList[boardIndex], "\n");
\r
5914 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
5915 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
5916 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
5918 case MT_STALEMATE:
\r
5922 if(gameInfo.variant != VariantShogi)
\r
5923 strcat(parseList[boardIndex - 1], "+");
\r
5925 case MT_CHECKMATE:
\r
5926 strcat(parseList[boardIndex - 1], "#");
\r
5933 /* Apply a move to the given board */
\r
5935 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
5936 int fromX, fromY, toX, toY;
\r
5940 ChessSquare captured = board[toY][toX], piece; int p;
\r
5942 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
5943 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
5944 && promoChar != 0) promoChar = 'F';
\r
5946 if (fromX == toX && fromY == toY) return;
\r
5948 if (fromY == DROP_RANK) {
\r
5949 /* must be first */
\r
5950 board[toY][toX] = (ChessSquare) fromX;
\r
5952 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
5954 /* Code added by Tord: */
\r
5955 /* FRC castling assumed when king captures friendly rook. */
\r
5956 if (board[fromY][fromX] == WhiteKing &&
\r
5957 board[toY][toX] == WhiteRook) {
\r
5958 board[fromY][fromX] = EmptySquare;
\r
5959 board[toY][toX] = EmptySquare;
\r
5961 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
5963 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
5965 } else if (board[fromY][fromX] == BlackKing &&
\r
5966 board[toY][toX] == BlackRook) {
\r
5967 board[fromY][fromX] = EmptySquare;
\r
5968 board[toY][toX] = EmptySquare;
\r
5970 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
5972 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
5974 /* End of code added by Tord */
\r
5976 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5977 && board[fromY][fromX] == WhiteKing
\r
5978 && toY == fromY && toX > fromX+1) {
\r
5979 board[fromY][fromX] = EmptySquare;
\r
5980 board[toY][toX] = WhiteKing;
\r
5981 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
5982 board[toY][toX-1] = WhiteRook;
\r
5983 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5984 && board[fromY][fromX] == WhiteKing
\r
5985 && toY == fromY && toX < fromX-1) {
\r
5986 board[fromY][fromX] = EmptySquare;
\r
5987 board[toY][toX] = WhiteKing;
\r
5988 board[fromY][BOARD_LEFT] = EmptySquare;
\r
5989 board[toY][toX+1] = WhiteRook;
\r
5990 } else if (fromY == 0 && fromX == 3
\r
5991 && board[fromY][fromX] == WhiteKing
\r
5992 && toY == 0 && toX == 5) {
\r
5993 board[fromY][fromX] = EmptySquare;
\r
5994 board[toY][toX] = WhiteKing;
\r
5995 board[fromY][7] = EmptySquare;
\r
5996 board[toY][4] = WhiteRook;
\r
5997 } else if (fromY == 0 && fromX == 3
\r
5998 && board[fromY][fromX] == WhiteKing
\r
5999 && toY == 0 && toX == 1) {
\r
6000 board[fromY][fromX] = EmptySquare;
\r
6001 board[toY][toX] = WhiteKing;
\r
6002 board[fromY][0] = EmptySquare;
\r
6003 board[toY][2] = WhiteRook;
\r
6004 } else if (board[fromY][fromX] == WhitePawn
\r
6005 && toY == BOARD_HEIGHT-1
\r
6006 && gameInfo.variant != VariantXiangqi
\r
6008 /* white pawn promotion */
\r
6009 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6010 if (board[toY][toX] == EmptySquare) {
\r
6011 board[toY][toX] = WhiteQueen;
\r
6013 if(gameInfo.variant==VariantBughouse ||
\r
6014 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6015 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6016 board[fromY][fromX] = EmptySquare;
\r
6017 } else if ((fromY == BOARD_HEIGHT-4)
\r
6019 && (board[fromY][fromX] == WhitePawn)
\r
6020 && (board[toY][toX] == EmptySquare)) {
\r
6021 board[fromY][fromX] = EmptySquare;
\r
6022 board[toY][toX] = WhitePawn;
\r
6023 captured = board[toY - 1][toX];
\r
6024 board[toY - 1][toX] = EmptySquare;
\r
6025 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6026 && board[fromY][fromX] == BlackKing
\r
6027 && toY == fromY && toX > fromX+1) {
\r
6028 board[fromY][fromX] = EmptySquare;
\r
6029 board[toY][toX] = BlackKing;
\r
6030 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6031 board[toY][toX-1] = BlackRook;
\r
6032 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6033 && board[fromY][fromX] == BlackKing
\r
6034 && toY == fromY && toX < fromX-1) {
\r
6035 board[fromY][fromX] = EmptySquare;
\r
6036 board[toY][toX] = BlackKing;
\r
6037 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6038 board[toY][toX+1] = BlackRook;
\r
6039 } else if (fromY == 7 && fromX == 3
\r
6040 && board[fromY][fromX] == BlackKing
\r
6041 && toY == 7 && toX == 5) {
\r
6042 board[fromY][fromX] = EmptySquare;
\r
6043 board[toY][toX] = BlackKing;
\r
6044 board[fromY][7] = EmptySquare;
\r
6045 board[toY][4] = BlackRook;
\r
6046 } else if (fromY == 7 && fromX == 3
\r
6047 && board[fromY][fromX] == BlackKing
\r
6048 && toY == 7 && toX == 1) {
\r
6049 board[fromY][fromX] = EmptySquare;
\r
6050 board[toY][toX] = BlackKing;
\r
6051 board[fromY][0] = EmptySquare;
\r
6052 board[toY][2] = BlackRook;
\r
6053 } else if (board[fromY][fromX] == BlackPawn
\r
6055 && gameInfo.variant != VariantXiangqi
\r
6057 /* black pawn promotion */
\r
6058 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6059 if (board[0][toX] == EmptySquare) {
\r
6060 board[0][toX] = BlackQueen;
\r
6062 if(gameInfo.variant==VariantBughouse ||
\r
6063 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6064 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6065 board[fromY][fromX] = EmptySquare;
\r
6066 } else if ((fromY == 3)
\r
6068 && (board[fromY][fromX] == BlackPawn)
\r
6069 && (board[toY][toX] == EmptySquare)) {
\r
6070 board[fromY][fromX] = EmptySquare;
\r
6071 board[toY][toX] = BlackPawn;
\r
6072 captured = board[toY + 1][toX];
\r
6073 board[toY + 1][toX] = EmptySquare;
\r
6075 board[toY][toX] = board[fromY][fromX];
\r
6076 board[fromY][fromX] = EmptySquare;
\r
6079 /* [HGM] now we promote for Shogi, if needed */
\r
6080 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
6081 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6084 if (gameInfo.holdingsWidth != 0) {
\r
6086 /* !!A lot more code needs to be written to support holdings */
\r
6087 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
6088 /* penultimate board files, so they are automaticlly stored */
\r
6089 /* in the game history. */
\r
6090 if (fromY == DROP_RANK) {
\r
6091 /* Delete from holdings, by decreasing count */
\r
6092 /* and erasing image if necessary */
\r
6094 if(p < (int) BlackPawn) { /* white drop */
\r
6095 p -= (int)WhitePawn;
\r
6096 if(p >= gameInfo.holdingsSize) p = 0;
\r
6097 if(--board[p][BOARD_WIDTH-2] == 0)
\r
6098 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
6099 } else { /* black drop */
\r
6100 p -= (int)BlackPawn;
\r
6101 if(p >= gameInfo.holdingsSize) p = 0;
\r
6102 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
6103 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
6106 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
6107 && gameInfo.variant != VariantBughouse ) {
\r
6108 /* Add to holdings, if holdings exist */
\r
6109 p = (int) captured;
\r
6110 if (p >= (int) BlackPawn) {
\r
6111 p -= (int)BlackPawn;
\r
6112 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6113 /* in Shogi restore piece to its original first */
\r
6114 captured = (ChessSquare) (DEMOTED captured);
\r
6117 p = PieceToNumber((ChessSquare)p);
\r
6118 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
6119 board[p][BOARD_WIDTH-2]++;
\r
6120 board[p][BOARD_WIDTH-1] =
\r
6121 BLACK_TO_WHITE captured;
\r
6123 p -= (int)WhitePawn;
\r
6124 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6125 captured = (ChessSquare) (DEMOTED captured);
\r
6128 p = PieceToNumber((ChessSquare)p);
\r
6129 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
6130 board[BOARD_HEIGHT-1-p][1]++;
\r
6131 board[BOARD_HEIGHT-1-p][0] =
\r
6132 WHITE_TO_BLACK captured;
\r
6136 } else if (gameInfo.variant == VariantAtomic) {
\r
6137 if (captured != EmptySquare) {
\r
6139 for (y = toY-1; y <= toY+1; y++) {
\r
6140 for (x = toX-1; x <= toX+1; x++) {
\r
6141 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
6142 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
6143 board[y][x] = EmptySquare;
\r
6147 board[toY][toX] = EmptySquare;
\r
6150 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
6151 /* [HGM] Shogi promotions */
\r
6152 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6157 /* Updates forwardMostMove */
\r
6159 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6160 int fromX, fromY, toX, toY;
\r
6163 forwardMostMove++;
\r
6165 if (forwardMostMove >= MAX_MOVES) {
\r
6166 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6171 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6172 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6173 if (commentList[forwardMostMove] != NULL) {
\r
6174 free(commentList[forwardMostMove]);
\r
6175 commentList[forwardMostMove] = NULL;
\r
6177 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6178 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6181 epStatus[forwardMostMove] = EP_NONE;
\r
6183 if( boards[forwardMostMove][toY][toX] != EmptySquare )
\r
6184 epStatus[forwardMostMove] = EP_CAPTURE;
\r
6186 if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {
\r
6187 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6188 if( toY-fromY==2 &&
\r
6189 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||
\r
6190 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )
\r
6191 epStatus[forwardMostMove] = toX;
\r
6193 if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {
\r
6194 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6195 if( toY-fromY== -2 &&
\r
6196 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||
\r
6197 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )
\r
6198 epStatus[forwardMostMove] = toX;
\r
6201 for(i=0; i<nrCastlingRights; i++) {
\r
6202 castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];
\r
6203 if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||
\r
6204 castlingRights[forwardMostMove][i] == toX && castlingRank[i] == toY
\r
6205 ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece
\r
6210 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6211 gameInfo.result = GameUnfinished;
\r
6212 if (gameInfo.resultDetails != NULL) {
\r
6213 free(gameInfo.resultDetails);
\r
6214 gameInfo.resultDetails = NULL;
\r
6216 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6217 moveList[forwardMostMove - 1]);
\r
6218 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6219 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6220 fromY, fromX, toY, toX, promoChar,
\r
6221 parseList[forwardMostMove - 1]);
\r
6222 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6223 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6224 castlingRights[forwardMostMove]) ) {
\r
6226 case MT_STALEMATE:
\r
6230 if(gameInfo.variant != VariantShogi)
\r
6231 strcat(parseList[forwardMostMove - 1], "+");
\r
6233 case MT_CHECKMATE:
\r
6234 strcat(parseList[forwardMostMove - 1], "#");
\r
6237 if (appData.debugMode) {
\r
6238 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
6243 /* Updates currentMove if not pausing */
\r
6245 ShowMove(fromX, fromY, toX, toY)
\r
6247 int instant = (gameMode == PlayFromGameFile) ?
\r
6248 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6249 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6251 if (forwardMostMove == currentMove + 1) {
\r
6252 AnimateMove(boards[forwardMostMove - 1],
\r
6253 fromX, fromY, toX, toY);
\r
6255 if (appData.highlightLastMove) {
\r
6256 SetHighlights(fromX, fromY, toX, toY);
\r
6259 currentMove = forwardMostMove;
\r
6262 if (instant) return;
\r
6264 DisplayMove(currentMove - 1);
\r
6265 DrawPosition(FALSE, boards[currentMove]);
\r
6266 DisplayBothClocks();
\r
6267 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6272 InitChessProgram(cps)
\r
6273 ChessProgramState *cps;
\r
6275 char buf[MSG_SIZ], *b; int overruled;
\r
6276 if (appData.noChessProgram) return;
\r
6277 hintRequested = FALSE;
\r
6278 bookRequested = FALSE;
\r
6279 SendToProgram(cps->initString, cps);
\r
6280 if (gameInfo.variant != VariantNormal &&
\r
6281 gameInfo.variant != VariantLoadable
\r
6282 /* [HGM] also send variant if board size non-standard */
\r
6283 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8
\r
6285 char *v = VariantName(gameInfo.variant);
\r
6286 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
6287 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
6288 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
6289 DisplayFatalError(buf, 0, 1);
\r
6293 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
6294 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6295 if( gameInfo.variant == VariantXiangqi )
\r
6296 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
6297 if( gameInfo.variant == VariantShogi )
\r
6298 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
6299 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
6300 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
6301 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )
\r
6302 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6303 if( gameInfo.variant == VariantCourier )
\r
6304 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6307 if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {
\r
6308 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
6309 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
6310 DisplayFatalError(buf, 0, 1);
\r
6313 /* [HGM] here we really should compare with the maximum supported board size */
\r
6314 sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,
\r
6315 gameInfo.boardHeight, gameInfo.holdingsSize );
\r
6316 while(*b++ != '_');
\r
6318 sprintf(b, "variant %s\n", VariantName(gameInfo.variant));
\r
6319 SendToProgram(buf, cps);
\r
6321 if (cps->sendICS) {
\r
6322 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
6323 SendToProgram(buf, cps);
\r
6325 cps->maybeThinking = FALSE;
\r
6326 cps->offeredDraw = 0;
\r
6327 if (!appData.icsActive) {
\r
6328 SendTimeControl(cps, movesPerSession, timeControl,
\r
6329 timeIncrement, appData.searchDepth,
\r
6332 if (appData.showThinking) {
\r
6333 SendToProgram("post\n", cps);
\r
6335 SendToProgram("hard\n", cps);
\r
6336 if (!appData.ponderNextMove) {
\r
6337 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
6338 it without being sure what state we are in first. "hard"
\r
6339 is not a toggle, so that one is OK.
\r
6341 SendToProgram("easy\n", cps);
\r
6343 if (cps->usePing) {
\r
6344 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
6345 SendToProgram(buf, cps);
\r
6347 cps->initDone = TRUE;
\r
6352 StartChessProgram(cps)
\r
6353 ChessProgramState *cps;
\r
6355 char buf[MSG_SIZ];
\r
6358 if (appData.noChessProgram) return;
\r
6359 cps->initDone = FALSE;
\r
6361 if (strcmp(cps->host, "localhost") == 0) {
\r
6362 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
6363 } else if (*appData.remoteShell == NULLCHAR) {
\r
6364 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
6366 if (*appData.remoteUser == NULLCHAR) {
\r
6367 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
6370 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
6371 cps->host, appData.remoteUser, cps->program);
\r
6373 err = StartChildProcess(buf, "", &cps->pr);
\r
6377 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
6378 DisplayFatalError(buf, err, 1);
\r
6384 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
6385 if (cps->protocolVersion > 1) {
\r
6386 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
6387 SendToProgram(buf, cps);
\r
6389 SendToProgram("xboard\n", cps);
\r
6395 TwoMachinesEventIfReady P((void))
\r
6397 if (first.lastPing != first.lastPong) {
\r
6398 DisplayMessage("", "Waiting for first chess program");
\r
6399 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6402 if (second.lastPing != second.lastPong) {
\r
6403 DisplayMessage("", "Waiting for second chess program");
\r
6404 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6408 TwoMachinesEvent();
\r
6412 NextMatchGame P((void))
\r
6414 Reset(FALSE, TRUE);
\r
6415 if (*appData.loadGameFile != NULLCHAR) {
\r
6416 LoadGameFromFile(appData.loadGameFile,
\r
6417 appData.loadGameIndex,
\r
6418 appData.loadGameFile, FALSE);
\r
6419 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
6420 LoadPositionFromFile(appData.loadPositionFile,
\r
6421 appData.loadPositionIndex,
\r
6422 appData.loadPositionFile);
\r
6424 TwoMachinesEventIfReady();
\r
6427 void UserAdjudicationEvent( int result )
\r
6429 ChessMove gameResult = GameIsDrawn;
\r
6431 if( result > 0 ) {
\r
6432 gameResult = WhiteWins;
\r
6434 else if( result < 0 ) {
\r
6435 gameResult = BlackWins;
\r
6438 if( gameMode == TwoMachinesPlay ) {
\r
6439 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
6445 GameEnds(result, resultDetails, whosays)
\r
6447 char *resultDetails;
\r
6450 GameMode nextGameMode;
\r
6452 char buf[MSG_SIZ];
\r
6454 if (appData.debugMode) {
\r
6455 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
6456 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6459 if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
6460 /* If we are playing on ICS, the server decides when the
\r
6461 game is over, but the engine can offer to draw, claim
\r
6462 a draw, or resign.
\r
6465 if (appData.zippyPlay && first.initDone) {
\r
6466 if (result == GameIsDrawn) {
\r
6467 /* In case draw still needs to be claimed */
\r
6468 SendToICS(ics_prefix);
\r
6469 SendToICS("draw\n");
\r
6470 } else if (StrCaseStr(resultDetails, "resign")) {
\r
6471 SendToICS(ics_prefix);
\r
6472 SendToICS("resign\n");
\r
6479 /* If we're loading the game from a file, stop */
\r
6480 if (whosays == GE_FILE) {
\r
6481 (void) StopLoadGameTimer();
\r
6482 gameFileFP = NULL;
\r
6485 /* Cancel draw offers */
\r
6486 first.offeredDraw = second.offeredDraw = 0;
\r
6488 /* If this is an ICS game, only ICS can really say it's done;
\r
6489 if not, anyone can. */
\r
6490 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6491 gameMode == IcsPlayingBlack ||
\r
6492 gameMode == IcsObserving ||
\r
6493 gameMode == IcsExamining);
\r
6495 if (!isIcsGame || whosays == GE_ICS) {
\r
6496 /* OK -- not an ICS game, or ICS said it was done */
\r
6498 if (appData.debugMode) {
\r
6499 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6500 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6502 if (!isIcsGame && !appData.noChessProgram)
\r
6503 SetUserThinkingEnables();
\r
6505 /* [HGM] if a machine claims the game end we verify this claim */
\r
6506 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6507 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6510 if (appData.debugMode) {
\r
6511 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6512 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6514 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6515 first.twoMachinesColor[0] :
\r
6516 second.twoMachinesColor[0] ;
\r
6517 if( result == WhiteWins && claimer == 'w' ||
\r
6518 result == BlackWins && claimer == 'b' ) {
\r
6519 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6520 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6521 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6522 resultDetails = buf;
\r
6524 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {
\r
6525 /* Draw that was not flagged by Xboard is false */
\r
6526 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6527 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6528 resultDetails = buf;
\r
6530 /* (Claiming a loss is accepted no questions asked!) */
\r
6533 if (appData.debugMode) {
\r
6534 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6535 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6537 if (resultDetails != NULL) {
\r
6538 gameInfo.result = result;
\r
6539 gameInfo.resultDetails = StrSave(resultDetails);
\r
6541 /* Tell program how game ended in case it is learning */
\r
6542 if (gameMode == MachinePlaysWhite ||
\r
6543 gameMode == MachinePlaysBlack ||
\r
6544 gameMode == TwoMachinesPlay ||
\r
6545 gameMode == IcsPlayingWhite ||
\r
6546 gameMode == IcsPlayingBlack ||
\r
6547 gameMode == BeginningOfGame) {
\r
6548 char buf[MSG_SIZ];
\r
6549 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
6551 if (first.pr != NoProc) {
\r
6552 SendToProgram(buf, &first);
\r
6554 if (second.pr != NoProc &&
\r
6555 gameMode == TwoMachinesPlay) {
\r
6556 SendToProgram(buf, &second);
\r
6560 /* display last move only if game was not loaded from file */
\r
6561 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
6562 DisplayMove(currentMove - 1);
\r
6564 if (forwardMostMove != 0) {
\r
6565 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
6566 if (*appData.saveGameFile != NULLCHAR) {
\r
6567 SaveGameToFile(appData.saveGameFile, TRUE);
\r
6568 } else if (appData.autoSaveGames) {
\r
6571 if (*appData.savePositionFile != NULLCHAR) {
\r
6572 SavePositionToFile(appData.savePositionFile);
\r
6578 if (appData.icsActive) {
\r
6579 if (appData.quietPlay &&
\r
6580 (gameMode == IcsPlayingWhite ||
\r
6581 gameMode == IcsPlayingBlack)) {
\r
6582 SendToICS(ics_prefix);
\r
6583 SendToICS("set shout 1\n");
\r
6585 nextGameMode = IcsIdle;
\r
6586 ics_user_moved = FALSE;
\r
6587 /* clean up premove. It's ugly when the game has ended and the
\r
6588 * premove highlights are still on the board.
\r
6591 gotPremove = FALSE;
\r
6592 ClearPremoveHighlights();
\r
6593 DrawPosition(FALSE, boards[currentMove]);
\r
6595 if (whosays == GE_ICS) {
\r
6598 if (gameMode == IcsPlayingWhite)
\r
6599 PlayIcsWinSound();
\r
6600 else if(gameMode == IcsPlayingBlack)
\r
6601 PlayIcsLossSound();
\r
6604 if (gameMode == IcsPlayingBlack)
\r
6605 PlayIcsWinSound();
\r
6606 else if(gameMode == IcsPlayingWhite)
\r
6607 PlayIcsLossSound();
\r
6610 PlayIcsDrawSound();
\r
6613 PlayIcsUnfinishedSound();
\r
6616 } else if (gameMode == EditGame ||
\r
6617 gameMode == PlayFromGameFile ||
\r
6618 gameMode == AnalyzeMode ||
\r
6619 gameMode == AnalyzeFile) {
\r
6620 nextGameMode = gameMode;
\r
6622 nextGameMode = EndOfGame;
\r
6627 nextGameMode = gameMode;
\r
6630 if (appData.noChessProgram) {
\r
6631 gameMode = nextGameMode;
\r
6636 if (first.reuse) {
\r
6637 /* Put first chess program into idle state */
\r
6638 if (first.pr != NoProc &&
\r
6639 (gameMode == MachinePlaysWhite ||
\r
6640 gameMode == MachinePlaysBlack ||
\r
6641 gameMode == TwoMachinesPlay ||
\r
6642 gameMode == IcsPlayingWhite ||
\r
6643 gameMode == IcsPlayingBlack ||
\r
6644 gameMode == BeginningOfGame)) {
\r
6645 SendToProgram("force\n", &first);
\r
6646 if (first.usePing) {
\r
6647 char buf[MSG_SIZ];
\r
6648 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
6649 SendToProgram(buf, &first);
\r
6652 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6653 /* Kill off first chess program */
\r
6654 if (first.isr != NULL)
\r
6655 RemoveInputSource(first.isr);
\r
6658 if (first.pr != NoProc) {
\r
6659 ExitAnalyzeMode();
\r
6660 DoSleep( appData.delayBeforeQuit );
\r
6661 SendToProgram("quit\n", &first);
\r
6662 DoSleep( appData.delayAfterQuit );
\r
6663 DestroyChildProcess(first.pr, first.useSigterm);
\r
6665 first.pr = NoProc;
\r
6667 if (second.reuse) {
\r
6668 /* Put second chess program into idle state */
\r
6669 if (second.pr != NoProc &&
\r
6670 gameMode == TwoMachinesPlay) {
\r
6671 SendToProgram("force\n", &second);
\r
6672 if (second.usePing) {
\r
6673 char buf[MSG_SIZ];
\r
6674 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
6675 SendToProgram(buf, &second);
\r
6678 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6679 /* Kill off second chess program */
\r
6680 if (second.isr != NULL)
\r
6681 RemoveInputSource(second.isr);
\r
6682 second.isr = NULL;
\r
6684 if (second.pr != NoProc) {
\r
6685 DoSleep( appData.delayBeforeQuit );
\r
6686 SendToProgram("quit\n", &second);
\r
6687 DoSleep( appData.delayAfterQuit );
\r
6688 DestroyChildProcess(second.pr, second.useSigterm);
\r
6690 second.pr = NoProc;
\r
6693 if (matchMode && gameMode == TwoMachinesPlay) {
\r
6696 if (first.twoMachinesColor[0] == 'w') {
\r
6697 first.matchWins++;
\r
6699 second.matchWins++;
\r
6703 if (first.twoMachinesColor[0] == 'b') {
\r
6704 first.matchWins++;
\r
6706 second.matchWins++;
\r
6712 if (matchGame < appData.matchGames) {
\r
6714 tmp = first.twoMachinesColor;
\r
6715 first.twoMachinesColor = second.twoMachinesColor;
\r
6716 second.twoMachinesColor = tmp;
\r
6717 gameMode = nextGameMode;
\r
6719 if(appData.matchPause>10000 || appData.matchPause<10)
\r
6720 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
6721 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
6724 char buf[MSG_SIZ];
\r
6725 gameMode = nextGameMode;
\r
6726 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
6727 first.tidy, second.tidy,
\r
6728 first.matchWins, second.matchWins,
\r
6729 appData.matchGames - (first.matchWins + second.matchWins));
\r
6730 DisplayFatalError(buf, 0, 0);
\r
6733 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6734 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
6735 ExitAnalyzeMode();
\r
6736 gameMode = nextGameMode;
\r
6740 /* Assumes program was just initialized (initString sent).
\r
6741 Leaves program in force mode. */
\r
6743 FeedMovesToProgram(cps, upto)
\r
6744 ChessProgramState *cps;
\r
6749 if (appData.debugMode)
\r
6750 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
6751 startedFromSetupPosition ? "position and " : "",
\r
6752 backwardMostMove, upto, cps->which);
\r
6753 SendToProgram("force\n", cps);
\r
6754 if (startedFromSetupPosition) {
\r
6755 SendBoard(cps, backwardMostMove);
\r
6757 for (i = backwardMostMove; i < upto; i++) {
\r
6758 SendMoveToProgram(i, cps);
\r
6764 ResurrectChessProgram()
\r
6766 /* The chess program may have exited.
\r
6767 If so, restart it and feed it all the moves made so far. */
\r
6769 if (appData.noChessProgram || first.pr != NoProc) return;
\r
6771 StartChessProgram(&first);
\r
6772 InitChessProgram(&first);
\r
6773 FeedMovesToProgram(&first, currentMove);
\r
6775 if (!first.sendTime) {
\r
6776 /* can't tell gnuchess what its clock should read,
\r
6777 so we bow to its notion. */
\r
6779 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
6780 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
6783 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6784 first.analysisSupport) {
\r
6785 SendToProgram("analyze\n", &first);
\r
6786 first.analyzing = TRUE;
\r
6791 * Button procedures
\r
6794 Reset(redraw, init)
\r
6799 if (appData.debugMode) {
\r
6800 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
6801 redraw, init, gameMode);
\r
6804 pausing = pauseExamInvalid = FALSE;
\r
6805 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
6807 whiteFlag = blackFlag = FALSE;
\r
6808 userOfferedDraw = FALSE;
\r
6809 hintRequested = bookRequested = FALSE;
\r
6810 first.maybeThinking = FALSE;
\r
6811 second.maybeThinking = FALSE;
\r
6812 thinkOutput[0] = NULLCHAR;
\r
6813 lastHint[0] = NULLCHAR;
\r
6814 ClearGameInfo(&gameInfo);
\r
6815 gameInfo.variant = StringToVariant(appData.variant);
\r
6816 ics_user_moved = ics_clock_paused = FALSE;
\r
6817 ics_getting_history = H_FALSE;
\r
6819 white_holding[0] = black_holding[0] = NULLCHAR;
\r
6820 ClearProgramStats();
\r
6823 ClearHighlights();
\r
6824 flipView = appData.flipView;
\r
6825 ClearPremoveHighlights();
\r
6826 gotPremove = FALSE;
\r
6827 alarmSounded = FALSE;
\r
6829 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
6830 ExitAnalyzeMode();
\r
6831 gameMode = BeginningOfGame;
\r
6833 InitPosition(redraw);
\r
6834 for (i = 0; i < MAX_MOVES; i++) {
\r
6835 if (commentList[i] != NULL) {
\r
6836 free(commentList[i]);
\r
6837 commentList[i] = NULL;
\r
6841 timeRemaining[0][0] = whiteTimeRemaining;
\r
6842 timeRemaining[1][0] = blackTimeRemaining;
\r
6843 if (first.pr == NULL) {
\r
6844 StartChessProgram(&first);
\r
6846 if (init) InitChessProgram(&first);
\r
6848 DisplayMessage("", "");
\r
6849 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
6853 AutoPlayGameLoop()
\r
6856 if (!AutoPlayOneMove())
\r
6858 if (matchMode || appData.timeDelay == 0)
\r
6860 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
6862 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
6871 int fromX, fromY, toX, toY;
\r
6873 if (appData.debugMode) {
\r
6874 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
6877 if (gameMode != PlayFromGameFile)
\r
6880 if (currentMove >= forwardMostMove) {
\r
6881 gameMode = EditGame;
\r
6884 /* [AS] Clear current move marker at the end of a game */
\r
6885 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
6890 toX = moveList[currentMove][2] - AAA;
\r
6891 toY = moveList[currentMove][3] - ONE;
\r
6893 if (moveList[currentMove][1] == '@') {
\r
6894 if (appData.highlightLastMove) {
\r
6895 SetHighlights(-1, -1, toX, toY);
\r
6898 fromX = moveList[currentMove][0] - AAA;
\r
6899 fromY = moveList[currentMove][1] - ONE;
\r
6901 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
6903 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
6905 if (appData.highlightLastMove) {
\r
6906 SetHighlights(fromX, fromY, toX, toY);
\r
6909 DisplayMove(currentMove);
\r
6910 SendMoveToProgram(currentMove++, &first);
\r
6911 DisplayBothClocks();
\r
6912 DrawPosition(FALSE, boards[currentMove]);
\r
6913 if (commentList[currentMove] != NULL) {
\r
6914 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6921 LoadGameOneMove(readAhead)
\r
6922 ChessMove readAhead;
\r
6924 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
6925 char promoChar = NULLCHAR;
\r
6926 ChessMove moveType;
\r
6927 char move[MSG_SIZ];
\r
6930 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
6931 gameMode != AnalyzeMode && gameMode != Training) {
\r
6932 gameFileFP = NULL;
\r
6936 yyboardindex = forwardMostMove;
\r
6937 if (readAhead != (ChessMove)0) {
\r
6938 moveType = readAhead;
\r
6940 if (gameFileFP == NULL)
\r
6942 moveType = (ChessMove) yylex();
\r
6946 switch (moveType) {
\r
6948 if (appData.debugMode)
\r
6949 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
6951 if (*p == '{' || *p == '[' || *p == '(') {
\r
6952 p[strlen(p) - 1] = NULLCHAR;
\r
6956 /* append the comment but don't display it */
\r
6957 while (*p == '\n') p++;
\r
6958 AppendComment(currentMove, p);
\r
6961 case WhiteCapturesEnPassant:
\r
6962 case BlackCapturesEnPassant:
\r
6963 case WhitePromotionChancellor:
\r
6964 case BlackPromotionChancellor:
\r
6965 case WhitePromotionArchbishop:
\r
6966 case BlackPromotionArchbishop:
\r
6967 case WhitePromotionQueen:
\r
6968 case BlackPromotionQueen:
\r
6969 case WhitePromotionRook:
\r
6970 case BlackPromotionRook:
\r
6971 case WhitePromotionBishop:
\r
6972 case BlackPromotionBishop:
\r
6973 case WhitePromotionKnight:
\r
6974 case BlackPromotionKnight:
\r
6975 case WhitePromotionKing:
\r
6976 case BlackPromotionKing:
\r
6978 case WhiteKingSideCastle:
\r
6979 case WhiteQueenSideCastle:
\r
6980 case BlackKingSideCastle:
\r
6981 case BlackQueenSideCastle:
\r
6982 case WhiteKingSideCastleWild:
\r
6983 case WhiteQueenSideCastleWild:
\r
6984 case BlackKingSideCastleWild:
\r
6985 case BlackQueenSideCastleWild:
\r
6987 case WhiteHSideCastleFR:
\r
6988 case WhiteASideCastleFR:
\r
6989 case BlackHSideCastleFR:
\r
6990 case BlackASideCastleFR:
\r
6992 if (appData.debugMode)
\r
6993 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
6994 fromX = currentMoveString[0] - AAA;
\r
6995 fromY = currentMoveString[1] - ONE;
\r
6996 toX = currentMoveString[2] - AAA;
\r
6997 toY = currentMoveString[3] - ONE;
\r
6998 promoChar = currentMoveString[4];
\r
7003 if (appData.debugMode)
\r
7004 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7005 fromX = moveType == WhiteDrop ?
\r
7006 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
7007 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
7008 fromY = DROP_RANK;
\r
7009 toX = currentMoveString[2] - AAA;
\r
7010 toY = currentMoveString[3] - ONE;
\r
7016 case GameUnfinished:
\r
7017 if (appData.debugMode)
\r
7018 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
7019 p = strchr(yy_text, '{');
\r
7020 if (p == NULL) p = strchr(yy_text, '(');
\r
7023 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7025 q = strchr(p, *p == '{' ? '}' : ')');
\r
7026 if (q != NULL) *q = NULLCHAR;
\r
7029 GameEnds(moveType, p, GE_FILE);
\r
7031 if (cmailMsgLoaded) {
\r
7032 ClearHighlights();
\r
7033 flipView = WhiteOnMove(currentMove);
\r
7034 if (moveType == GameUnfinished) flipView = !flipView;
\r
7035 if (appData.debugMode)
\r
7036 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
7040 case (ChessMove) 0: /* end of file */
\r
7041 if (appData.debugMode)
\r
7042 fprintf(debugFP, "Parser hit end of file\n");
\r
7043 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7044 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7048 case MT_CHECKMATE:
\r
7049 if (WhiteOnMove(currentMove)) {
\r
7050 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7052 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7055 case MT_STALEMATE:
\r
7056 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7062 case MoveNumberOne:
\r
7063 if (lastLoadGameStart == GNUChessGame) {
\r
7064 /* GNUChessGames have numbers, but they aren't move numbers */
\r
7065 if (appData.debugMode)
\r
7066 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7067 yy_text, (int) moveType);
\r
7068 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7070 /* else fall thru */
\r
7073 case GNUChessGame:
\r
7075 /* Reached start of next game in file */
\r
7076 if (appData.debugMode)
\r
7077 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
7078 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7079 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7083 case MT_CHECKMATE:
\r
7084 if (WhiteOnMove(currentMove)) {
\r
7085 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7087 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7090 case MT_STALEMATE:
\r
7091 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7097 case PositionDiagram: /* should not happen; ignore */
\r
7098 case ElapsedTime: /* ignore */
\r
7099 case NAG: /* ignore */
\r
7100 if (appData.debugMode)
\r
7101 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7102 yy_text, (int) moveType);
\r
7103 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7106 if (appData.testLegality) {
\r
7107 if (appData.debugMode)
\r
7108 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
7109 sprintf(move, "Illegal move: %d.%s%s",
\r
7110 (forwardMostMove / 2) + 1,
\r
7111 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7112 DisplayError(move, 0);
\r
7115 if (appData.debugMode)
\r
7116 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
7117 yy_text, currentMoveString);
\r
7118 fromX = currentMoveString[0] - AAA;
\r
7119 fromY = currentMoveString[1] - ONE;
\r
7120 toX = currentMoveString[2] - AAA;
\r
7121 toY = currentMoveString[3] - ONE;
\r
7122 promoChar = currentMoveString[4];
\r
7126 case AmbiguousMove:
\r
7127 if (appData.debugMode)
\r
7128 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
7129 sprintf(move, "Ambiguous move: %d.%s%s",
\r
7130 (forwardMostMove / 2) + 1,
\r
7131 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7132 DisplayError(move, 0);
\r
7137 case ImpossibleMove:
\r
7138 if (appData.debugMode)
\r
7139 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
7140 sprintf(move, "Illegal move: %d.%s%s",
\r
7141 (forwardMostMove / 2) + 1,
\r
7142 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7143 DisplayError(move, 0);
\r
7149 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
7150 DrawPosition(FALSE, boards[currentMove]);
\r
7151 DisplayBothClocks();
\r
7152 if (!appData.matchMode && commentList[currentMove] != NULL)
\r
7153 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7155 (void) StopLoadGameTimer();
\r
7156 gameFileFP = NULL;
\r
7157 cmailOldMove = forwardMostMove;
\r
7160 /* currentMoveString is set as a side-effect of yylex */
\r
7161 strcat(currentMoveString, "\n");
\r
7162 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7164 thinkOutput[0] = NULLCHAR;
\r
7165 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7166 currentMove = forwardMostMove;
\r
7171 /* Load the nth game from the given file */
\r
7173 LoadGameFromFile(filename, n, title, useList)
\r
7177 /*Boolean*/ int useList;
\r
7180 char buf[MSG_SIZ];
\r
7182 if (strcmp(filename, "-") == 0) {
\r
7186 f = fopen(filename, "rb");
\r
7188 sprintf(buf, "Can't open \"%s\"", filename);
\r
7189 DisplayError(buf, errno);
\r
7193 if (fseek(f, 0, 0) == -1) {
\r
7194 /* f is not seekable; probably a pipe */
\r
7197 if (useList && n == 0) {
\r
7198 int error = GameListBuild(f);
\r
7200 DisplayError("Cannot build game list", error);
\r
7201 } else if (!ListEmpty(&gameList) &&
\r
7202 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7203 GameListPopUp(f, title);
\r
7206 GameListDestroy();
\r
7209 if (n == 0) n = 1;
\r
7210 return LoadGame(f, n, title, FALSE);
\r
7215 MakeRegisteredMove()
\r
7217 int fromX, fromY, toX, toY;
\r
7219 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7220 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
7223 if (appData.debugMode)
\r
7224 fprintf(debugFP, "Restoring %s for game %d\n",
\r
7225 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
7227 thinkOutput[0] = NULLCHAR;
\r
7228 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
7229 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
7230 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
7231 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
7232 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
7233 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
7234 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7235 ShowMove(fromX, fromY, toX, toY);
\r
7237 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7238 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7243 case MT_CHECKMATE:
\r
7244 if (WhiteOnMove(currentMove)) {
\r
7245 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
7247 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
7251 case MT_STALEMATE:
\r
7252 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
7258 case CMAIL_RESIGN:
\r
7259 if (WhiteOnMove(currentMove)) {
\r
7260 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
7262 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
7266 case CMAIL_ACCEPT:
\r
7267 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
7278 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
7280 CmailLoadGame(f, gameNumber, title, useList)
\r
7288 if (gameNumber > nCmailGames) {
\r
7289 DisplayError("No more games in this message", 0);
\r
7292 if (f == lastLoadGameFP) {
\r
7293 int offset = gameNumber - lastLoadGameNumber;
\r
7294 if (offset == 0) {
\r
7295 cmailMsg[0] = NULLCHAR;
\r
7296 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7297 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
7298 nCmailMovesRegistered--;
\r
7300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
7301 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
7302 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
7305 if (! RegisterMove()) return FALSE;
\r
7309 retVal = LoadGame(f, gameNumber, title, useList);
\r
7311 /* Make move registered during previous look at this game, if any */
\r
7312 MakeRegisteredMove();
\r
7314 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
7315 commentList[currentMove]
\r
7316 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
7317 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7323 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
7325 ReloadGame(offset)
\r
7328 int gameNumber = lastLoadGameNumber + offset;
\r
7329 if (lastLoadGameFP == NULL) {
\r
7330 DisplayError("No game has been loaded yet", 0);
\r
7333 if (gameNumber <= 0) {
\r
7334 DisplayError("Can't back up any further", 0);
\r
7337 if (cmailMsgLoaded) {
\r
7338 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
7339 lastLoadGameTitle, lastLoadGameUseList);
\r
7341 return LoadGame(lastLoadGameFP, gameNumber,
\r
7342 lastLoadGameTitle, lastLoadGameUseList);
\r
7348 /* Load the nth game from open file f */
\r
7350 LoadGame(f, gameNumber, title, useList)
\r
7357 char buf[MSG_SIZ];
\r
7358 int gn = gameNumber;
\r
7359 ListGame *lg = NULL;
\r
7360 int numPGNTags = 0;
\r
7362 GameMode oldGameMode;
\r
7364 if (appData.debugMode)
\r
7365 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
7367 if (gameMode == Training )
\r
7368 SetTrainingModeOff();
\r
7370 oldGameMode = gameMode;
\r
7371 if (gameMode != BeginningOfGame) {
\r
7372 Reset(FALSE, TRUE);
\r
7376 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
7377 fclose(lastLoadGameFP);
\r
7381 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
7384 fseek(f, lg->offset, 0);
\r
7385 GameListHighlight(gameNumber);
\r
7389 DisplayError("Game number out of range", 0);
\r
7393 GameListDestroy();
\r
7394 if (fseek(f, 0, 0) == -1) {
\r
7395 if (f == lastLoadGameFP ?
\r
7396 gameNumber == lastLoadGameNumber + 1 :
\r
7397 gameNumber == 1) {
\r
7400 DisplayError("Can't seek on game file", 0);
\r
7405 lastLoadGameFP = f;
\r
7406 lastLoadGameNumber = gameNumber;
\r
7407 strcpy(lastLoadGameTitle, title);
\r
7408 lastLoadGameUseList = useList;
\r
7413 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
7414 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
7415 lg->gameInfo.black);
\r
7416 DisplayTitle(buf);
\r
7417 } else if (*title != NULLCHAR) {
\r
7418 if (gameNumber > 1) {
\r
7419 sprintf(buf, "%s %d", title, gameNumber);
\r
7420 DisplayTitle(buf);
\r
7422 DisplayTitle(title);
\r
7426 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
7427 gameMode = PlayFromGameFile;
\r
7431 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7432 CopyBoard(boards[0], initialPosition);
\r
7436 * Skip the first gn-1 games in the file.
\r
7437 * Also skip over anything that precedes an identifiable
\r
7438 * start of game marker, to avoid being confused by
\r
7439 * garbage at the start of the file. Currently
\r
7440 * recognized start of game markers are the move number "1",
\r
7441 * the pattern "gnuchess .* game", the pattern
\r
7442 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
7443 * A game that starts with one of the latter two patterns
\r
7444 * will also have a move number 1, possibly
\r
7445 * following a position diagram.
\r
7446 * 5-4-02: Let's try being more lenient and allowing a game to
\r
7447 * start with an unnumbered move. Does that break anything?
\r
7449 cm = lastLoadGameStart = (ChessMove) 0;
\r
7451 yyboardindex = forwardMostMove;
\r
7452 cm = (ChessMove) yylex();
\r
7454 case (ChessMove) 0:
\r
7455 if (cmailMsgLoaded) {
\r
7456 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
7458 Reset(TRUE, TRUE);
\r
7459 DisplayError("Game not found in file", 0);
\r
7463 case GNUChessGame:
\r
7466 lastLoadGameStart = cm;
\r
7469 case MoveNumberOne:
\r
7470 switch (lastLoadGameStart) {
\r
7471 case GNUChessGame:
\r
7475 case MoveNumberOne:
\r
7476 case (ChessMove) 0:
\r
7477 gn--; /* count this game */
\r
7478 lastLoadGameStart = cm;
\r
7487 switch (lastLoadGameStart) {
\r
7488 case GNUChessGame:
\r
7490 case MoveNumberOne:
\r
7491 case (ChessMove) 0:
\r
7492 gn--; /* count this game */
\r
7493 lastLoadGameStart = cm;
\r
7496 lastLoadGameStart = cm; /* game counted already */
\r
7504 yyboardindex = forwardMostMove;
\r
7505 cm = (ChessMove) yylex();
\r
7506 } while (cm == PGNTag || cm == Comment);
\r
7513 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
7514 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
7515 != CMAIL_OLD_RESULT) {
\r
7516 nCmailResults ++ ;
\r
7517 cmailResult[ CMAIL_MAX_GAMES
\r
7518 - gn - 1] = CMAIL_OLD_RESULT;
\r
7524 /* Only a NormalMove can be at the start of a game
\r
7525 * without a position diagram. */
\r
7526 if (lastLoadGameStart == (ChessMove) 0) {
\r
7528 lastLoadGameStart = MoveNumberOne;
\r
7537 if (appData.debugMode)
\r
7538 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
7540 if (cm == XBoardGame) {
\r
7541 /* Skip any header junk before position diagram and/or move 1 */
\r
7543 yyboardindex = forwardMostMove;
\r
7544 cm = (ChessMove) yylex();
\r
7546 if (cm == (ChessMove) 0 ||
\r
7547 cm == GNUChessGame || cm == XBoardGame) {
\r
7548 /* Empty game; pretend end-of-file and handle later */
\r
7549 cm = (ChessMove) 0;
\r
7553 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
7554 cm == PGNTag || cm == Comment)
\r
7557 } else if (cm == GNUChessGame) {
\r
7558 if (gameInfo.event != NULL) {
\r
7559 free(gameInfo.event);
\r
7561 gameInfo.event = StrSave(yy_text);
\r
7564 startedFromSetupPosition = FALSE;
\r
7565 while (cm == PGNTag) {
\r
7566 if (appData.debugMode)
\r
7567 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
7568 err = ParsePGNTag(yy_text, &gameInfo);
\r
7569 if (!err) numPGNTags++;
\r
7571 if (gameInfo.fen != NULL) {
\r
7572 Board initial_position;
\r
7573 startedFromSetupPosition = TRUE;
\r
7574 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
7575 Reset(TRUE, TRUE);
\r
7576 DisplayError("Bad FEN position in file", 0);
\r
7579 CopyBoard(boards[0], initial_position);
\r
7580 /* [HGM] copy FEN attributes as well */
\r
7582 initialRulePlies = FENrulePlies;
\r
7583 epStatus[0] = FENepStatus;
\r
7584 for( i=0; i< nrCastlingRights; i++ )
\r
7585 castlingRights[0][i] = FENcastlingRights[i];
\r
7587 if (blackPlaysFirst) {
\r
7588 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7589 CopyBoard(boards[1], initial_position);
\r
7590 strcpy(moveList[0], "");
\r
7591 strcpy(parseList[0], "");
\r
7592 timeRemaining[0][1] = whiteTimeRemaining;
\r
7593 timeRemaining[1][1] = blackTimeRemaining;
\r
7594 if (commentList[0] != NULL) {
\r
7595 commentList[1] = commentList[0];
\r
7596 commentList[0] = NULL;
\r
7599 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7601 yyboardindex = forwardMostMove;
\r
7602 free(gameInfo.fen);
\r
7603 gameInfo.fen = NULL;
\r
7606 yyboardindex = forwardMostMove;
\r
7607 cm = (ChessMove) yylex();
\r
7609 /* Handle comments interspersed among the tags */
\r
7610 while (cm == Comment) {
\r
7612 if (appData.debugMode)
\r
7613 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7615 if (*p == '{' || *p == '[' || *p == '(') {
\r
7616 p[strlen(p) - 1] = NULLCHAR;
\r
7619 while (*p == '\n') p++;
\r
7620 AppendComment(currentMove, p);
\r
7621 yyboardindex = forwardMostMove;
\r
7622 cm = (ChessMove) yylex();
\r
7626 /* don't rely on existence of Event tag since if game was
\r
7627 * pasted from clipboard the Event tag may not exist
\r
7629 if (numPGNTags > 0){
\r
7631 if (gameInfo.variant == VariantNormal) {
\r
7632 gameInfo.variant = StringToVariant(gameInfo.event);
\r
7635 if( appData.autoDisplayTags ) {
\r
7636 tags = PGNTags(&gameInfo);
\r
7637 TagsPopUp(tags, CmailMsg());
\r
7642 /* Make something up, but don't display it now */
\r
7647 if (cm == PositionDiagram) {
\r
7650 Board initial_position;
\r
7652 if (appData.debugMode)
\r
7653 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
7655 if (!startedFromSetupPosition) {
\r
7657 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
7658 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
7668 initial_position[i][j++] = CharToPiece(*p);
\r
7671 while (*p == ' ' || *p == '\t' ||
\r
7672 *p == '\n' || *p == '\r') p++;
\r
7674 if (strncmp(p, "black", strlen("black"))==0)
\r
7675 blackPlaysFirst = TRUE;
\r
7677 blackPlaysFirst = FALSE;
\r
7678 startedFromSetupPosition = TRUE;
\r
7680 CopyBoard(boards[0], initial_position);
\r
7681 if (blackPlaysFirst) {
\r
7682 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7683 CopyBoard(boards[1], initial_position);
\r
7684 strcpy(moveList[0], "");
\r
7685 strcpy(parseList[0], "");
\r
7686 timeRemaining[0][1] = whiteTimeRemaining;
\r
7687 timeRemaining[1][1] = blackTimeRemaining;
\r
7688 if (commentList[0] != NULL) {
\r
7689 commentList[1] = commentList[0];
\r
7690 commentList[0] = NULL;
\r
7693 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7696 yyboardindex = forwardMostMove;
\r
7697 cm = (ChessMove) yylex();
\r
7700 if (first.pr == NoProc) {
\r
7701 StartChessProgram(&first);
\r
7703 InitChessProgram(&first);
\r
7704 SendToProgram("force\n", &first);
\r
7705 if (startedFromSetupPosition) {
\r
7706 SendBoard(&first, forwardMostMove);
\r
7707 DisplayBothClocks();
\r
7710 while (cm == Comment) {
\r
7712 if (appData.debugMode)
\r
7713 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7715 if (*p == '{' || *p == '[' || *p == '(') {
\r
7716 p[strlen(p) - 1] = NULLCHAR;
\r
7719 while (*p == '\n') p++;
\r
7720 AppendComment(currentMove, p);
\r
7721 yyboardindex = forwardMostMove;
\r
7722 cm = (ChessMove) yylex();
\r
7725 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
7726 cm == WhiteWins || cm == BlackWins ||
\r
7727 cm == GameIsDrawn || cm == GameUnfinished) {
\r
7728 DisplayMessage("", "No moves in game");
\r
7729 if (cmailMsgLoaded) {
\r
7730 if (appData.debugMode)
\r
7731 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
7732 ClearHighlights();
\r
7735 DrawPosition(FALSE, boards[currentMove]);
\r
7736 DisplayBothClocks();
\r
7737 gameMode = EditGame;
\r
7739 gameFileFP = NULL;
\r
7744 if (commentList[currentMove] != NULL) {
\r
7745 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
7746 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7749 if (!matchMode && appData.timeDelay != 0)
\r
7750 DrawPosition(FALSE, boards[currentMove]);
\r
7752 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
7753 programStats.ok_to_send = 1;
\r
7756 /* if the first token after the PGN tags is a move
\r
7757 * and not move number 1, retrieve it from the parser
\r
7759 if (cm != MoveNumberOne)
\r
7760 LoadGameOneMove(cm);
\r
7762 /* load the remaining moves from the file */
\r
7763 while (LoadGameOneMove((ChessMove)0)) {
\r
7764 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7765 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7768 /* rewind to the start of the game */
\r
7769 currentMove = backwardMostMove;
\r
7771 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7773 if (oldGameMode == AnalyzeFile ||
\r
7774 oldGameMode == AnalyzeMode) {
\r
7775 AnalyzeFileEvent();
\r
7778 if (matchMode || appData.timeDelay == 0) {
\r
7780 gameMode = EditGame;
\r
7782 } else if (appData.timeDelay > 0) {
\r
7783 AutoPlayGameLoop();
\r
7786 if (appData.debugMode)
\r
7787 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
7791 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
7793 ReloadPosition(offset)
\r
7796 int positionNumber = lastLoadPositionNumber + offset;
\r
7797 if (lastLoadPositionFP == NULL) {
\r
7798 DisplayError("No position has been loaded yet", 0);
\r
7801 if (positionNumber <= 0) {
\r
7802 DisplayError("Can't back up any further", 0);
\r
7805 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
7806 lastLoadPositionTitle);
\r
7809 /* Load the nth position from the given file */
\r
7811 LoadPositionFromFile(filename, n, title)
\r
7817 char buf[MSG_SIZ];
\r
7819 if (strcmp(filename, "-") == 0) {
\r
7820 return LoadPosition(stdin, n, "stdin");
\r
7822 f = fopen(filename, "rb");
\r
7824 sprintf(buf, "Can't open \"%s\"", filename);
\r
7825 DisplayError(buf, errno);
\r
7828 return LoadPosition(f, n, title);
\r
7833 /* Load the nth position from the given open file, and close it */
\r
7835 LoadPosition(f, positionNumber, title)
\r
7837 int positionNumber;
\r
7840 char *p, line[MSG_SIZ];
\r
7841 Board initial_position;
\r
7842 int i, j, fenMode, pn;
\r
7844 if (gameMode == Training )
\r
7845 SetTrainingModeOff();
\r
7847 if (gameMode != BeginningOfGame) {
\r
7848 Reset(FALSE, TRUE);
\r
7850 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
7851 fclose(lastLoadPositionFP);
\r
7853 if (positionNumber == 0) positionNumber = 1;
\r
7854 lastLoadPositionFP = f;
\r
7855 lastLoadPositionNumber = positionNumber;
\r
7856 strcpy(lastLoadPositionTitle, title);
\r
7857 if (first.pr == NoProc) {
\r
7858 StartChessProgram(&first);
\r
7859 InitChessProgram(&first);
\r
7861 pn = positionNumber;
\r
7862 if (positionNumber < 0) {
\r
7863 /* Negative position number means to seek to that byte offset */
\r
7864 if (fseek(f, -positionNumber, 0) == -1) {
\r
7865 DisplayError("Can't seek on position file", 0);
\r
7870 if (fseek(f, 0, 0) == -1) {
\r
7871 if (f == lastLoadPositionFP ?
\r
7872 positionNumber == lastLoadPositionNumber + 1 :
\r
7873 positionNumber == 1) {
\r
7876 DisplayError("Can't seek on position file", 0);
\r
7881 /* See if this file is FEN or old-style xboard */
\r
7882 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7883 DisplayError("Position not found in file", 0);
\r
7886 switch (line[0]) {
\r
7887 case '#': case 'x':
\r
7891 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
7892 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
7893 case '1': case '2': case '3': case '4': case '5': case '6':
\r
7894 case '7': case '8': case '9':
\r
7896 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
7897 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
7898 case 'C': case 'W': case 'c': case 'w':
\r
7905 if (fenMode || line[0] == '#') pn--;
\r
7907 /* skip postions before number pn */
\r
7908 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7909 Reset(TRUE, TRUE);
\r
7910 DisplayError("Position not found in file", 0);
\r
7913 if (fenMode || line[0] == '#') pn--;
\r
7918 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
7919 DisplayError("Bad FEN position in file", 0);
\r
7923 (void) fgets(line, MSG_SIZ, f);
\r
7924 (void) fgets(line, MSG_SIZ, f);
\r
7926 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
7927 (void) fgets(line, MSG_SIZ, f);
\r
7928 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
7931 initial_position[i][j++] = CharToPiece(*p);
\r
7935 blackPlaysFirst = FALSE;
\r
7937 (void) fgets(line, MSG_SIZ, f);
\r
7938 if (strncmp(line, "black", strlen("black"))==0)
\r
7939 blackPlaysFirst = TRUE;
\r
7942 startedFromSetupPosition = TRUE;
\r
7944 SendToProgram("force\n", &first);
\r
7945 CopyBoard(boards[0], initial_position);
\r
7946 /* [HGM] copy FEN attributes as well */
\r
7948 initialRulePlies = FENrulePlies;
\r
7949 epStatus[0] = FENepStatus;
\r
7950 for( i=0; i< nrCastlingRights; i++ )
\r
7951 castlingRights[0][i] = FENcastlingRights[i];
\r
7953 if (blackPlaysFirst) {
\r
7954 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7955 strcpy(moveList[0], "");
\r
7956 strcpy(parseList[0], "");
\r
7957 CopyBoard(boards[1], initial_position);
\r
7958 DisplayMessage("", "Black to play");
\r
7960 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7961 DisplayMessage("", "White to play");
\r
7963 SendBoard(&first, forwardMostMove);
\r
7965 if (positionNumber > 1) {
\r
7966 sprintf(line, "%s %d", title, positionNumber);
\r
7967 DisplayTitle(line);
\r
7969 DisplayTitle(title);
\r
7971 gameMode = EditGame;
\r
7974 timeRemaining[0][1] = whiteTimeRemaining;
\r
7975 timeRemaining[1][1] = blackTimeRemaining;
\r
7976 DrawPosition(FALSE, boards[currentMove]);
\r
7983 CopyPlayerNameIntoFileName(dest, src)
\r
7984 char **dest, *src;
\r
7986 while (*src != NULLCHAR && *src != ',') {
\r
7987 if (*src == ' ') {
\r
7991 *(*dest)++ = *src++;
\r
7996 char *DefaultFileName(ext)
\r
7999 static char def[MSG_SIZ];
\r
8002 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
8004 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
8006 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
8010 def[0] = NULLCHAR;
\r
8015 /* Save the current game to the given file */
\r
8017 SaveGameToFile(filename, append)
\r
8022 char buf[MSG_SIZ];
\r
8024 if (strcmp(filename, "-") == 0) {
\r
8025 return SaveGame(stdout, 0, NULL);
\r
8027 f = fopen(filename, append ? "a" : "w");
\r
8029 sprintf(buf, "Can't open \"%s\"", filename);
\r
8030 DisplayError(buf, errno);
\r
8033 return SaveGame(f, 0, NULL);
\r
8042 static char buf[MSG_SIZ];
\r
8045 p = strchr(str, ' ');
\r
8046 if (p == NULL) return str;
\r
8047 strncpy(buf, str, p - str);
\r
8048 buf[p - str] = NULLCHAR;
\r
8052 #define PGN_MAX_LINE 75
\r
8054 #define PGN_SIDE_WHITE 0
\r
8055 #define PGN_SIDE_BLACK 1
\r
8058 static int FindFirstMoveOutOfBook( int side )
\r
8062 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
8063 int index = backwardMostMove;
\r
8064 int has_book_hit = 0;
\r
8066 if( (index % 2) != side ) {
\r
8070 while( index < forwardMostMove ) {
\r
8071 /* Check to see if engine is in book */
\r
8072 int depth = pvInfoList[index].depth;
\r
8073 int score = pvInfoList[index].score;
\r
8076 if( depth <= 2 ) {
\r
8079 else if( score == 0 && depth == 63 ) {
\r
8080 in_book = 1; /* Zappa */
\r
8082 else if( score == 2 && depth == 99 ) {
\r
8083 in_book = 1; /* Abrok */
\r
8086 has_book_hit += in_book;
\r
8102 void GetOutOfBookInfo( char * buf )
\r
8106 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8108 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
8109 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
8113 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
8114 for( i=0; i<2; i++ ) {
\r
8118 if( i > 0 && oob[0] >= 0 ) {
\r
8119 strcat( buf, " " );
\r
8122 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
8123 sprintf( buf+strlen(buf), "%s%.2f",
\r
8124 pvInfoList[idx].score >= 0 ? "+" : "",
\r
8125 pvInfoList[idx].score / 100.0 );
\r
8131 /* Save game in PGN style and close the file */
\r
8136 int i, offset, linelen, newblock;
\r
8140 int movelen, numlen, blank;
\r
8141 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
8143 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8145 tm = time((time_t *) NULL);
\r
8147 PrintPGNTags(f, &gameInfo);
\r
8149 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8150 char *fen = PositionToFEN(backwardMostMove, 1);
\r
8151 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
8152 fprintf(f, "\n{--------------\n");
\r
8153 PrintPosition(f, backwardMostMove);
\r
8154 fprintf(f, "--------------}\n");
\r
8158 /* [AS] Out of book annotation */
\r
8159 if( appData.saveOutOfBookInfo ) {
\r
8162 GetOutOfBookInfo( buf );
\r
8164 if( buf[0] != '\0' ) {
\r
8165 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8172 i = backwardMostMove;
\r
8176 while (i < forwardMostMove) {
\r
8177 /* Print comments preceding this move */
\r
8178 if (commentList[i] != NULL) {
\r
8179 if (linelen > 0) fprintf(f, "\n");
\r
8180 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8185 /* Format move number */
\r
8186 if ((i % 2) == 0) {
\r
8187 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8190 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
8192 numtext[0] = NULLCHAR;
\r
8195 numlen = strlen(numtext);
\r
8198 /* Print move number */
\r
8199 blank = linelen > 0 && numlen > 0;
\r
8200 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
8209 fprintf(f, numtext);
\r
8210 linelen += numlen;
\r
8213 movetext = SavePart(parseList[i]);
\r
8215 /* [AS] Add PV info if present */
\r
8216 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
8217 /* [HGM] add time */
\r
8218 char buf[MSG_SIZ]; int seconds = 0;
\r
8220 if(i >= backwardMostMove) {
\r
8221 /* take the time that changed */
\r
8222 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
8224 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
8227 if (appData.debugMode) {
\r
8228 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
8229 timeRemaining[0][i+1], timeRemaining[0][i],
\r
8230 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
8234 if( seconds < 0 ) buf[0] = 0; else
\r
8235 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
8236 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
8238 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
8240 pvInfoList[i].score >= 0 ? "+" : "",
\r
8241 pvInfoList[i].score / 100.0,
\r
8242 pvInfoList[i].depth,
\r
8244 movetext = move_buffer;
\r
8247 movelen = strlen(movetext);
\r
8250 blank = linelen > 0 && movelen > 0;
\r
8251 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
8260 fprintf(f, movetext);
\r
8261 linelen += movelen;
\r
8266 /* Start a new line */
\r
8267 if (linelen > 0) fprintf(f, "\n");
\r
8269 /* Print comments after last move */
\r
8270 if (commentList[i] != NULL) {
\r
8271 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8274 /* Print result */
\r
8275 if (gameInfo.resultDetails != NULL &&
\r
8276 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8277 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
8278 PGNResult(gameInfo.result));
\r
8280 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8287 /* Save game in old style and close the file */
\r
8289 SaveGameOldStyle(f)
\r
8295 tm = time((time_t *) NULL);
\r
8297 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
8298 PrintOpponents(f);
\r
8300 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8301 fprintf(f, "\n[--------------\n");
\r
8302 PrintPosition(f, backwardMostMove);
\r
8303 fprintf(f, "--------------]\n");
\r
8308 i = backwardMostMove;
\r
8309 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8311 while (i < forwardMostMove) {
\r
8312 if (commentList[i] != NULL) {
\r
8313 fprintf(f, "[%s]\n", commentList[i]);
\r
8316 if ((i % 2) == 1) {
\r
8317 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
8320 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
8322 if (commentList[i] != NULL) {
\r
8326 if (i >= forwardMostMove) {
\r
8330 fprintf(f, "%s\n", parseList[i]);
\r
8335 if (commentList[i] != NULL) {
\r
8336 fprintf(f, "[%s]\n", commentList[i]);
\r
8339 /* This isn't really the old style, but it's close enough */
\r
8340 if (gameInfo.resultDetails != NULL &&
\r
8341 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8342 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
8343 gameInfo.resultDetails);
\r
8345 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8352 /* Save the current game to open file f and close the file */
\r
8354 SaveGame(f, dummy, dummy2)
\r
8359 if (gameMode == EditPosition) EditPositionDone();
\r
8360 if (appData.oldSaveStyle)
\r
8361 return SaveGameOldStyle(f);
\r
8363 return SaveGamePGN(f);
\r
8366 /* Save the current position to the given file */
\r
8368 SavePositionToFile(filename)
\r
8372 char buf[MSG_SIZ];
\r
8374 if (strcmp(filename, "-") == 0) {
\r
8375 return SavePosition(stdout, 0, NULL);
\r
8377 f = fopen(filename, "a");
\r
8379 sprintf(buf, "Can't open \"%s\"", filename);
\r
8380 DisplayError(buf, errno);
\r
8383 SavePosition(f, 0, NULL);
\r
8389 /* Save the current position to the given open file and close the file */
\r
8391 SavePosition(f, dummy, dummy2)
\r
8399 if (appData.oldSaveStyle) {
\r
8400 tm = time((time_t *) NULL);
\r
8402 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
8403 PrintOpponents(f);
\r
8404 fprintf(f, "[--------------\n");
\r
8405 PrintPosition(f, currentMove);
\r
8406 fprintf(f, "--------------]\n");
\r
8408 fen = PositionToFEN(currentMove, 1);
\r
8409 fprintf(f, "%s\n", fen);
\r
8417 ReloadCmailMsgEvent(unregister)
\r
8421 static char *inFilename = NULL;
\r
8422 static char *outFilename;
\r
8424 struct stat inbuf, outbuf;
\r
8427 /* Any registered moves are unregistered if unregister is set, */
\r
8428 /* i.e. invoked by the signal handler */
\r
8430 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8431 cmailMoveRegistered[i] = FALSE;
\r
8432 if (cmailCommentList[i] != NULL) {
\r
8433 free(cmailCommentList[i]);
\r
8434 cmailCommentList[i] = NULL;
\r
8437 nCmailMovesRegistered = 0;
\r
8440 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8441 cmailResult[i] = CMAIL_NOT_RESULT;
\r
8443 nCmailResults = 0;
\r
8445 if (inFilename == NULL) {
\r
8446 /* Because the filenames are static they only get malloced once */
\r
8447 /* and they never get freed */
\r
8448 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
8449 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
8451 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
8452 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
8455 status = stat(outFilename, &outbuf);
\r
8457 cmailMailedMove = FALSE;
\r
8459 status = stat(inFilename, &inbuf);
\r
8460 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
8463 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
8464 counts the games, notes how each one terminated, etc.
\r
8466 It would be nice to remove this kludge and instead gather all
\r
8467 the information while building the game list. (And to keep it
\r
8468 in the game list nodes instead of having a bunch of fixed-size
\r
8469 parallel arrays.) Note this will require getting each game's
\r
8470 termination from the PGN tags, as the game list builder does
\r
8471 not process the game moves. --mann
\r
8473 cmailMsgLoaded = TRUE;
\r
8474 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
8476 /* Load first game in the file or popup game menu */
\r
8477 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
8479 #endif /* !WIN32 */
\r
8487 char string[MSG_SIZ];
\r
8489 if ( cmailMailedMove
\r
8490 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
8491 return TRUE; /* Allow free viewing */
\r
8494 /* Unregister move to ensure that we don't leave RegisterMove */
\r
8495 /* with the move registered when the conditions for registering no */
\r
8497 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8498 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8499 nCmailMovesRegistered --;
\r
8501 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
8503 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
8504 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
8508 if (cmailOldMove == -1) {
\r
8509 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
8513 if (currentMove > cmailOldMove + 1) {
\r
8514 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
8518 if (currentMove < cmailOldMove) {
\r
8519 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
8523 if (forwardMostMove > currentMove) {
\r
8524 /* Silently truncate extra moves */
\r
8528 if ( (currentMove == cmailOldMove + 1)
\r
8529 || ( (currentMove == cmailOldMove)
\r
8530 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
8531 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
8532 if (gameInfo.result != GameUnfinished) {
\r
8533 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
8536 if (commentList[currentMove] != NULL) {
\r
8537 cmailCommentList[lastLoadGameNumber - 1]
\r
8538 = StrSave(commentList[currentMove]);
\r
8540 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
8542 if (appData.debugMode)
\r
8543 fprintf(debugFP, "Saving %s for game %d\n",
\r
8544 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8547 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
8549 f = fopen(string, "w");
\r
8550 if (appData.oldSaveStyle) {
\r
8551 SaveGameOldStyle(f); /* also closes the file */
\r
8553 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
8554 f = fopen(string, "w");
\r
8555 SavePosition(f, 0, NULL); /* also closes the file */
\r
8557 fprintf(f, "{--------------\n");
\r
8558 PrintPosition(f, currentMove);
\r
8559 fprintf(f, "--------------}\n\n");
\r
8561 SaveGame(f, 0, NULL); /* also closes the file*/
\r
8564 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
8565 nCmailMovesRegistered ++;
\r
8566 } else if (nCmailGames == 1) {
\r
8567 DisplayError("You have not made a move yet", 0);
\r
8578 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
8579 FILE *commandOutput;
\r
8580 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
8581 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
8587 if (! cmailMsgLoaded) {
\r
8588 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
8592 if (nCmailGames == nCmailResults) {
\r
8593 DisplayError("No unfinished games", 0);
\r
8597 #if CMAIL_PROHIBIT_REMAIL
\r
8598 if (cmailMailedMove) {
\r
8599 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
8600 DisplayError(msg, 0);
\r
8605 if (! (cmailMailedMove || RegisterMove())) return;
\r
8607 if ( cmailMailedMove
\r
8608 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
8609 sprintf(string, partCommandString,
\r
8610 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
8611 commandOutput = popen(string, "rb");
\r
8613 if (commandOutput == NULL) {
\r
8614 DisplayError("Failed to invoke cmail", 0);
\r
8616 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
8617 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
8619 if (nBuffers > 1) {
\r
8620 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
8621 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
8622 nBytes = MSG_SIZ - 1;
\r
8624 (void) memcpy(msg, buffer, nBytes);
\r
8626 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
8628 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
8629 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
8632 for (i = 0; i < nCmailGames; i ++) {
\r
8633 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
8638 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
8640 sprintf(buffer, "%s/%s.%s.archive",
\r
8642 appData.cmailGameName,
\r
8644 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
8645 cmailMsgLoaded = FALSE;
\r
8649 DisplayInformation(msg);
\r
8650 pclose(commandOutput);
\r
8653 if ((*cmailMsg) != '\0') {
\r
8654 DisplayInformation(cmailMsg);
\r
8659 #endif /* !WIN32 */
\r
8668 int prependComma = 0;
\r
8670 char string[MSG_SIZ]; /* Space for game-list */
\r
8673 if (!cmailMsgLoaded) return "";
\r
8675 if (cmailMailedMove) {
\r
8676 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
8678 /* Create a list of games left */
\r
8679 sprintf(string, "[");
\r
8680 for (i = 0; i < nCmailGames; i ++) {
\r
8681 if (! ( cmailMoveRegistered[i]
\r
8682 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
8683 if (prependComma) {
\r
8684 sprintf(number, ",%d", i + 1);
\r
8686 sprintf(number, "%d", i + 1);
\r
8690 strcat(string, number);
\r
8693 strcat(string, "]");
\r
8695 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
8696 switch (nCmailGames) {
\r
8699 "Still need to make move for game\n");
\r
8704 "Still need to make moves for both games\n");
\r
8709 "Still need to make moves for all %d games\n",
\r
8714 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
8717 "Still need to make a move for game %s\n",
\r
8722 if (nCmailResults == nCmailGames) {
\r
8723 sprintf(cmailMsg, "No unfinished games\n");
\r
8725 sprintf(cmailMsg, "Ready to send mail\n");
\r
8731 "Still need to make moves for games %s\n",
\r
8737 #endif /* WIN32 */
\r
8743 if (gameMode == Training)
\r
8744 SetTrainingModeOff();
\r
8746 Reset(TRUE, TRUE);
\r
8747 cmailMsgLoaded = FALSE;
\r
8748 if (appData.icsActive) {
\r
8749 SendToICS(ics_prefix);
\r
8750 SendToICS("refresh\n");
\r
8754 static int exiting = 0;
\r
8761 if (exiting > 2) {
\r
8762 /* Give up on clean exit */
\r
8765 if (exiting > 1) {
\r
8766 /* Keep trying for clean exit */
\r
8770 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
8772 if (telnetISR != NULL) {
\r
8773 RemoveInputSource(telnetISR);
\r
8775 if (icsPR != NoProc) {
\r
8776 DestroyChildProcess(icsPR, TRUE);
\r
8778 /* Save game if resource set and not already saved by GameEnds() */
\r
8779 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
\r
8780 if (*appData.saveGameFile != NULLCHAR) {
\r
8781 SaveGameToFile(appData.saveGameFile, TRUE);
\r
8782 } else if (appData.autoSaveGames) {
\r
8785 if (*appData.savePositionFile != NULLCHAR) {
\r
8786 SavePositionToFile(appData.savePositionFile);
\r
8789 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8791 /* Kill off chess programs */
\r
8792 if (first.pr != NoProc) {
\r
8793 ExitAnalyzeMode();
\r
8795 DoSleep( appData.delayBeforeQuit );
\r
8796 SendToProgram("quit\n", &first);
\r
8797 DoSleep( appData.delayAfterQuit );
\r
8798 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
8800 if (second.pr != NoProc) {
\r
8801 DoSleep( appData.delayBeforeQuit );
\r
8802 SendToProgram("quit\n", &second);
\r
8803 DoSleep( appData.delayAfterQuit );
\r
8804 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
8806 if (first.isr != NULL) {
\r
8807 RemoveInputSource(first.isr);
\r
8809 if (second.isr != NULL) {
\r
8810 RemoveInputSource(second.isr);
\r
8813 ShutDownFrontEnd();
\r
8820 if (appData.debugMode)
\r
8821 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
8825 if (gameMode == MachinePlaysWhite ||
\r
8826 gameMode == MachinePlaysBlack) {
\r
8829 DisplayBothClocks();
\r
8831 if (gameMode == PlayFromGameFile) {
\r
8832 if (appData.timeDelay >= 0)
\r
8833 AutoPlayGameLoop();
\r
8834 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
8835 Reset(FALSE, TRUE);
\r
8836 SendToICS(ics_prefix);
\r
8837 SendToICS("refresh\n");
\r
8838 } else if (currentMove < forwardMostMove) {
\r
8839 ForwardInner(forwardMostMove);
\r
8841 pauseExamInvalid = FALSE;
\r
8843 switch (gameMode) {
\r
8846 case IcsExamining:
\r
8847 pauseExamForwardMostMove = forwardMostMove;
\r
8848 pauseExamInvalid = FALSE;
\r
8849 /* fall through */
\r
8850 case IcsObserving:
\r
8851 case IcsPlayingWhite:
\r
8852 case IcsPlayingBlack:
\r
8856 case PlayFromGameFile:
\r
8857 (void) StopLoadGameTimer();
\r
8861 case BeginningOfGame:
\r
8862 if (appData.icsActive) return;
\r
8863 /* else fall through */
\r
8864 case MachinePlaysWhite:
\r
8865 case MachinePlaysBlack:
\r
8866 case TwoMachinesPlay:
\r
8867 if (forwardMostMove == 0)
\r
8868 return; /* don't pause if no one has moved */
\r
8869 if ((gameMode == MachinePlaysWhite &&
\r
8870 !WhiteOnMove(forwardMostMove)) ||
\r
8871 (gameMode == MachinePlaysBlack &&
\r
8872 WhiteOnMove(forwardMostMove))) {
\r
8883 EditCommentEvent()
\r
8885 char title[MSG_SIZ];
\r
8887 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
8888 strcpy(title, "Edit comment");
\r
8890 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
8891 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
8892 parseList[currentMove - 1]);
\r
8895 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
8902 char *tags = PGNTags(&gameInfo);
\r
8903 EditTagsPopUp(tags);
\r
8908 AnalyzeModeEvent()
\r
8910 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
8913 if (gameMode != AnalyzeFile) {
\r
8915 if (gameMode != EditGame) return;
\r
8916 ResurrectChessProgram();
\r
8917 SendToProgram("analyze\n", &first);
\r
8918 first.analyzing = TRUE;
\r
8919 /*first.maybeThinking = TRUE;*/
\r
8920 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8921 AnalysisPopUp("Analysis",
\r
8922 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8924 gameMode = AnalyzeMode;
\r
8929 StartAnalysisClock();
\r
8930 GetTimeMark(&lastNodeCountTime);
\r
8931 lastNodeCount = 0;
\r
8935 AnalyzeFileEvent()
\r
8937 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
8940 if (gameMode != AnalyzeMode) {
\r
8942 if (gameMode != EditGame) return;
\r
8943 ResurrectChessProgram();
\r
8944 SendToProgram("analyze\n", &first);
\r
8945 first.analyzing = TRUE;
\r
8946 /*first.maybeThinking = TRUE;*/
\r
8947 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8948 AnalysisPopUp("Analysis",
\r
8949 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8951 gameMode = AnalyzeFile;
\r
8956 StartAnalysisClock();
\r
8957 GetTimeMark(&lastNodeCountTime);
\r
8958 lastNodeCount = 0;
\r
8962 MachineWhiteEvent()
\r
8964 char buf[MSG_SIZ];
\r
8966 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
8970 if (gameMode == PlayFromGameFile ||
\r
8971 gameMode == TwoMachinesPlay ||
\r
8972 gameMode == Training ||
\r
8973 gameMode == AnalyzeMode ||
\r
8974 gameMode == EndOfGame)
\r
8977 if (gameMode == EditPosition)
\r
8978 EditPositionDone();
\r
8980 if (!WhiteOnMove(currentMove)) {
\r
8981 DisplayError("It is not White's turn", 0);
\r
8985 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
8986 ExitAnalyzeMode();
\r
8988 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
8989 gameMode == AnalyzeFile)
\r
8992 ResurrectChessProgram(); /* in case it isn't running */
\r
8993 gameMode = MachinePlaysWhite;
\r
8997 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8998 DisplayTitle(buf);
\r
8999 if (first.sendName) {
\r
9000 sprintf(buf, "name %s\n", gameInfo.black);
\r
9001 SendToProgram(buf, &first);
\r
9003 if (first.sendTime) {
\r
9004 if (first.useColors) {
\r
9005 SendToProgram("black\n", &first); /*gnu kludge*/
\r
9007 SendTimeRemaining(&first, TRUE);
\r
9009 if (first.useColors) {
\r
9010 SendToProgram("white\ngo\n", &first);
\r
9012 SendToProgram("go\n", &first);
\r
9014 SetMachineThinkingEnables();
\r
9015 first.maybeThinking = TRUE;
\r
9018 if (appData.autoFlipView && !flipView) {
\r
9019 flipView = !flipView;
\r
9020 DrawPosition(FALSE, NULL);
\r
9025 MachineBlackEvent()
\r
9027 char buf[MSG_SIZ];
\r
9029 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
9033 if (gameMode == PlayFromGameFile ||
\r
9034 gameMode == TwoMachinesPlay ||
\r
9035 gameMode == Training ||
\r
9036 gameMode == AnalyzeMode ||
\r
9037 gameMode == EndOfGame)
\r
9040 if (gameMode == EditPosition)
\r
9041 EditPositionDone();
\r
9043 if (WhiteOnMove(currentMove)) {
\r
9044 DisplayError("It is not Black's turn", 0);
\r
9048 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9049 ExitAnalyzeMode();
\r
9051 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9052 gameMode == AnalyzeFile)
\r
9055 ResurrectChessProgram(); /* in case it isn't running */
\r
9056 gameMode = MachinePlaysBlack;
\r
9060 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9061 DisplayTitle(buf);
\r
9062 if (first.sendName) {
\r
9063 sprintf(buf, "name %s\n", gameInfo.white);
\r
9064 SendToProgram(buf, &first);
\r
9066 if (first.sendTime) {
\r
9067 if (first.useColors) {
\r
9068 SendToProgram("white\n", &first); /*gnu kludge*/
\r
9070 SendTimeRemaining(&first, FALSE);
\r
9072 if (first.useColors) {
\r
9073 SendToProgram("black\ngo\n", &first);
\r
9075 SendToProgram("go\n", &first);
\r
9077 SetMachineThinkingEnables();
\r
9078 first.maybeThinking = TRUE;
\r
9081 if (appData.autoFlipView && flipView) {
\r
9082 flipView = !flipView;
\r
9083 DrawPosition(FALSE, NULL);
\r
9089 DisplayTwoMachinesTitle()
\r
9091 char buf[MSG_SIZ];
\r
9092 if (appData.matchGames > 0) {
\r
9093 if (first.twoMachinesColor[0] == 'w') {
\r
9094 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9095 gameInfo.white, gameInfo.black,
\r
9096 first.matchWins, second.matchWins,
\r
9097 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9099 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9100 gameInfo.white, gameInfo.black,
\r
9101 second.matchWins, first.matchWins,
\r
9102 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9105 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9107 DisplayTitle(buf);
\r
9111 TwoMachinesEvent P((void))
\r
9114 char buf[MSG_SIZ];
\r
9115 ChessProgramState *onmove;
\r
9117 if (appData.noChessProgram) return;
\r
9119 switch (gameMode) {
\r
9120 case TwoMachinesPlay:
\r
9122 case MachinePlaysWhite:
\r
9123 case MachinePlaysBlack:
\r
9124 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9125 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9128 /* fall through */
\r
9129 case BeginningOfGame:
\r
9130 case PlayFromGameFile:
\r
9133 if (gameMode != EditGame) return;
\r
9135 case EditPosition:
\r
9136 EditPositionDone();
\r
9140 ExitAnalyzeMode();
\r
9147 forwardMostMove = currentMove;
\r
9148 ResurrectChessProgram(); /* in case first program isn't running */
\r
9150 if (second.pr == NULL) {
\r
9151 StartChessProgram(&second);
\r
9152 if (second.protocolVersion == 1) {
\r
9153 TwoMachinesEventIfReady();
\r
9155 /* kludge: allow timeout for initial "feature" command */
\r
9157 DisplayMessage("", "Starting second chess program");
\r
9158 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9162 DisplayMessage("", "");
\r
9163 InitChessProgram(&second);
\r
9164 SendToProgram("force\n", &second);
\r
9165 if (startedFromSetupPosition) {
\r
9166 SendBoard(&second, backwardMostMove);
\r
9168 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9169 SendMoveToProgram(i, &second);
\r
9172 gameMode = TwoMachinesPlay;
\r
9176 DisplayTwoMachinesTitle();
\r
9178 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
9184 SendToProgram(first.computerString, &first);
\r
9185 if (first.sendName) {
\r
9186 sprintf(buf, "name %s\n", second.tidy);
\r
9187 SendToProgram(buf, &first);
\r
9189 SendToProgram(second.computerString, &second);
\r
9190 if (second.sendName) {
\r
9191 sprintf(buf, "name %s\n", first.tidy);
\r
9192 SendToProgram(buf, &second);
\r
9195 if (!first.sendTime || !second.sendTime) {
\r
9197 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9198 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9200 if (onmove->sendTime) {
\r
9201 if (onmove->useColors) {
\r
9202 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
9204 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
9206 if (onmove->useColors) {
\r
9207 SendToProgram(onmove->twoMachinesColor, onmove);
\r
9209 SendToProgram("go\n", onmove);
\r
9210 onmove->maybeThinking = TRUE;
\r
9211 SetMachineThinkingEnables();
\r
9219 if (gameMode == Training) {
\r
9220 SetTrainingModeOff();
\r
9221 gameMode = PlayFromGameFile;
\r
9222 DisplayMessage("", "Training mode off");
\r
9224 gameMode = Training;
\r
9225 animateTraining = appData.animate;
\r
9227 /* make sure we are not already at the end of the game */
\r
9228 if (currentMove < forwardMostMove) {
\r
9229 SetTrainingModeOn();
\r
9230 DisplayMessage("", "Training mode on");
\r
9232 gameMode = PlayFromGameFile;
\r
9233 DisplayError("Already at end of game", 0);
\r
9242 if (!appData.icsActive) return;
\r
9243 switch (gameMode) {
\r
9244 case IcsPlayingWhite:
\r
9245 case IcsPlayingBlack:
\r
9246 case IcsObserving:
\r
9248 case BeginningOfGame:
\r
9249 case IcsExamining:
\r
9255 case EditPosition:
\r
9256 EditPositionDone();
\r
9261 ExitAnalyzeMode();
\r
9269 gameMode = IcsIdle;
\r
9280 switch (gameMode) {
\r
9282 SetTrainingModeOff();
\r
9284 case MachinePlaysWhite:
\r
9285 case MachinePlaysBlack:
\r
9286 case BeginningOfGame:
\r
9287 SendToProgram("force\n", &first);
\r
9288 SetUserThinkingEnables();
\r
9290 case PlayFromGameFile:
\r
9291 (void) StopLoadGameTimer();
\r
9292 if (gameFileFP != NULL) {
\r
9293 gameFileFP = NULL;
\r
9296 case EditPosition:
\r
9297 EditPositionDone();
\r
9301 ExitAnalyzeMode();
\r
9302 SendToProgram("force\n", &first);
\r
9304 case TwoMachinesPlay:
\r
9305 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9306 ResurrectChessProgram();
\r
9307 SetUserThinkingEnables();
\r
9310 ResurrectChessProgram();
\r
9312 case IcsPlayingBlack:
\r
9313 case IcsPlayingWhite:
\r
9314 DisplayError("Warning: You are still playing a game", 0);
\r
9316 case IcsObserving:
\r
9317 DisplayError("Warning: You are still observing a game", 0);
\r
9319 case IcsExamining:
\r
9320 DisplayError("Warning: You are still examining a game", 0);
\r
9331 first.offeredDraw = second.offeredDraw = 0;
\r
9333 if (gameMode == PlayFromGameFile) {
\r
9334 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9335 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9339 if (gameMode == MachinePlaysWhite ||
\r
9340 gameMode == MachinePlaysBlack ||
\r
9341 gameMode == TwoMachinesPlay ||
\r
9342 gameMode == EndOfGame) {
\r
9343 i = forwardMostMove;
\r
9344 while (i > currentMove) {
\r
9345 SendToProgram("undo\n", &first);
\r
9348 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9349 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9350 DisplayBothClocks();
\r
9351 if (whiteFlag || blackFlag) {
\r
9352 whiteFlag = blackFlag = 0;
\r
9357 gameMode = EditGame;
\r
9364 EditPositionEvent()
\r
9366 if (gameMode == EditPosition) {
\r
9372 if (gameMode != EditGame) return;
\r
9374 gameMode = EditPosition;
\r
9377 if (currentMove > 0)
\r
9378 CopyBoard(boards[0], boards[currentMove]);
\r
9380 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
9382 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9383 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9390 if (first.analysisSupport && first.analyzing) {
\r
9391 SendToProgram("exit\n", &first);
\r
9392 first.analyzing = FALSE;
\r
9394 AnalysisPopDown();
\r
9395 thinkOutput[0] = NULLCHAR;
\r
9399 EditPositionDone()
\r
9401 startedFromSetupPosition = TRUE;
\r
9402 InitChessProgram(&first);
\r
9403 SendToProgram("force\n", &first);
\r
9404 if (blackPlaysFirst) {
\r
9405 strcpy(moveList[0], "");
\r
9406 strcpy(parseList[0], "");
\r
9407 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9408 CopyBoard(boards[1], boards[0]);
\r
9410 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9412 SendBoard(&first, forwardMostMove);
\r
9414 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9415 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9416 gameMode = EditGame;
\r
9418 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9419 ClearHighlights(); /* [AS] */
\r
9422 /* Pause for `ms' milliseconds */
\r
9423 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9433 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
9436 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9438 SendMultiLineToICS(buf)
\r
9441 char temp[MSG_SIZ+1], *p;
\r
9444 len = strlen(buf);
\r
9445 if (len > MSG_SIZ)
\r
9448 strncpy(temp, buf, len);
\r
9453 if (*p == '\n' || *p == '\r')
\r
9458 strcat(temp, "\n");
\r
9460 SendToPlayer(temp, strlen(temp));
\r
9464 SetWhiteToPlayEvent()
\r
9466 if (gameMode == EditPosition) {
\r
9467 blackPlaysFirst = FALSE;
\r
9468 DisplayBothClocks(); /* works because currentMove is 0 */
\r
9469 } else if (gameMode == IcsExamining) {
\r
9470 SendToICS(ics_prefix);
\r
9471 SendToICS("tomove white\n");
\r
9476 SetBlackToPlayEvent()
\r
9478 if (gameMode == EditPosition) {
\r
9479 blackPlaysFirst = TRUE;
\r
9480 currentMove = 1; /* kludge */
\r
9481 DisplayBothClocks();
\r
9483 } else if (gameMode == IcsExamining) {
\r
9484 SendToICS(ics_prefix);
\r
9485 SendToICS("tomove black\n");
\r
9490 EditPositionMenuEvent(selection, x, y)
\r
9491 ChessSquare selection;
\r
9494 char buf[MSG_SIZ];
\r
9495 ChessSquare piece = boards[0][y][x];
\r
9497 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
9499 switch (selection) {
\r
9501 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
9502 SendToICS(ics_prefix);
\r
9503 SendToICS("bsetup clear\n");
\r
9504 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
9505 SendToICS(ics_prefix);
\r
9506 SendToICS("clearboard\n");
\r
9508 for (x = 0; x < BOARD_WIDTH; x++) {
\r
9509 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
9510 if (gameMode == IcsExamining) {
\r
9511 if (boards[currentMove][y][x] != EmptySquare) {
\r
9512 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
9513 AAA + x, ONE + y);
\r
9517 boards[0][y][x] = EmptySquare;
\r
9522 if (gameMode == EditPosition) {
\r
9523 DrawPosition(FALSE, boards[0]);
\r
9528 SetWhiteToPlayEvent();
\r
9532 SetBlackToPlayEvent();
\r
9536 if (gameMode == IcsExamining) {
\r
9537 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
9540 boards[0][y][x] = EmptySquare;
\r
9541 DrawPosition(FALSE, boards[0]);
\r
9545 case PromotePiece:
\r
9546 if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||
\r
9547 piece >= (int)BlackPawn && piece < (int)BlackWazir ) {
\r
9548 selection = (ChessSquare) (PROMOTED piece);
\r
9549 } else if(piece == EmptySquare) selection = WhiteWazir;
\r
9550 else selection = (ChessSquare)((int)piece - 1);
\r
9551 goto defaultlabel;
\r
9554 if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||
\r
9555 piece >= (int)BlackUnicorn && piece < (int)BlackKing ) {
\r
9556 selection = (ChessSquare) (DEMOTED piece);
\r
9557 } else if( piece == WhiteKing || piece == BlackKing )
\r
9558 selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);
\r
9559 else if(piece == EmptySquare) selection = BlackWazir;
\r
9560 else selection = (ChessSquare)((int)piece + 1);
\r
9561 goto defaultlabel;
\r
9565 if(gameInfo.variant == VariantShatranj ||
\r
9566 gameInfo.variant == VariantXiangqi ||
\r
9567 gameInfo.variant == VariantCourier )
\r
9568 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
9569 goto defaultlabel;
\r
9573 if(gameInfo.variant == VariantXiangqi)
\r
9574 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
9575 if(gameInfo.variant == VariantKnightmate)
\r
9576 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
9579 if (gameMode == IcsExamining) {
\r
9580 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
9581 PieceToChar(selection), AAA + x, ONE + y);
\r
9584 boards[0][y][x] = selection;
\r
9585 DrawPosition(FALSE, boards[0]);
\r
9593 DropMenuEvent(selection, x, y)
\r
9594 ChessSquare selection;
\r
9597 ChessMove moveType;
\r
9599 switch (gameMode) {
\r
9600 case IcsPlayingWhite:
\r
9601 case MachinePlaysBlack:
\r
9602 if (!WhiteOnMove(currentMove)) {
\r
9603 DisplayMoveError("It is Black's turn");
\r
9606 moveType = WhiteDrop;
\r
9608 case IcsPlayingBlack:
\r
9609 case MachinePlaysWhite:
\r
9610 if (WhiteOnMove(currentMove)) {
\r
9611 DisplayMoveError("It is White's turn");
\r
9614 moveType = BlackDrop;
\r
9617 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
9623 if (moveType == BlackDrop && selection < BlackPawn) {
\r
9624 selection = (ChessSquare) ((int) selection
\r
9625 + (int) BlackPawn - (int) WhitePawn);
\r
9627 if (boards[currentMove][y][x] != EmptySquare) {
\r
9628 DisplayMoveError("That square is occupied");
\r
9632 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
9638 /* Accept a pending offer of any kind from opponent */
\r
9640 if (appData.icsActive) {
\r
9641 SendToICS(ics_prefix);
\r
9642 SendToICS("accept\n");
\r
9643 } else if (cmailMsgLoaded) {
\r
9644 if (currentMove == cmailOldMove &&
\r
9645 commentList[cmailOldMove] != NULL &&
\r
9646 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9647 "Black offers a draw" : "White offers a draw")) {
\r
9649 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9650 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9652 DisplayError("There is no pending offer on this move", 0);
\r
9653 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9656 /* Not used for offers from chess program */
\r
9663 /* Decline a pending offer of any kind from opponent */
\r
9665 if (appData.icsActive) {
\r
9666 SendToICS(ics_prefix);
\r
9667 SendToICS("decline\n");
\r
9668 } else if (cmailMsgLoaded) {
\r
9669 if (currentMove == cmailOldMove &&
\r
9670 commentList[cmailOldMove] != NULL &&
\r
9671 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9672 "Black offers a draw" : "White offers a draw")) {
\r
9674 AppendComment(cmailOldMove, "Draw declined");
\r
9675 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
9678 DisplayError("There is no pending offer on this move", 0);
\r
9681 /* Not used for offers from chess program */
\r
9688 /* Issue ICS rematch command */
\r
9689 if (appData.icsActive) {
\r
9690 SendToICS(ics_prefix);
\r
9691 SendToICS("rematch\n");
\r
9698 /* Call your opponent's flag (claim a win on time) */
\r
9699 if (appData.icsActive) {
\r
9700 SendToICS(ics_prefix);
\r
9701 SendToICS("flag\n");
\r
9703 switch (gameMode) {
\r
9706 case MachinePlaysWhite:
\r
9709 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9712 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
9714 DisplayError("Your opponent is not out of time", 0);
\r
9717 case MachinePlaysBlack:
\r
9720 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9723 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
9725 DisplayError("Your opponent is not out of time", 0);
\r
9735 /* Offer draw or accept pending draw offer from opponent */
\r
9737 if (appData.icsActive) {
\r
9738 /* Note: tournament rules require draw offers to be
\r
9739 made after you make your move but before you punch
\r
9740 your clock. Currently ICS doesn't let you do that;
\r
9741 instead, you immediately punch your clock after making
\r
9742 a move, but you can offer a draw at any time. */
\r
9744 SendToICS(ics_prefix);
\r
9745 SendToICS("draw\n");
\r
9746 } else if (cmailMsgLoaded) {
\r
9747 if (currentMove == cmailOldMove &&
\r
9748 commentList[cmailOldMove] != NULL &&
\r
9749 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9750 "Black offers a draw" : "White offers a draw")) {
\r
9751 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9752 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9753 } else if (currentMove == cmailOldMove + 1) {
\r
9754 char *offer = WhiteOnMove(cmailOldMove) ?
\r
9755 "White offers a draw" : "Black offers a draw";
\r
9756 AppendComment(currentMove, offer);
\r
9757 DisplayComment(currentMove - 1, offer);
\r
9758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
9760 DisplayError("You must make your move before offering a draw", 0);
\r
9761 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9763 } else if (first.offeredDraw) {
\r
9764 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
9766 if (first.sendDrawOffers) {
\r
9767 SendToProgram("draw\n", &first);
\r
9768 userOfferedDraw = TRUE;
\r
9776 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
9778 if (appData.icsActive) {
\r
9779 SendToICS(ics_prefix);
\r
9780 SendToICS("adjourn\n");
\r
9782 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
9790 /* Offer Abort or accept pending Abort offer from opponent */
\r
9792 if (appData.icsActive) {
\r
9793 SendToICS(ics_prefix);
\r
9794 SendToICS("abort\n");
\r
9796 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
9803 /* Resign. You can do this even if it's not your turn. */
\r
9805 if (appData.icsActive) {
\r
9806 SendToICS(ics_prefix);
\r
9807 SendToICS("resign\n");
\r
9809 switch (gameMode) {
\r
9810 case MachinePlaysWhite:
\r
9811 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9813 case MachinePlaysBlack:
\r
9814 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9817 if (cmailMsgLoaded) {
\r
9819 if (WhiteOnMove(cmailOldMove)) {
\r
9820 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9822 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9824 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
9835 StopObservingEvent()
\r
9837 /* Stop observing current games */
\r
9838 SendToICS(ics_prefix);
\r
9839 SendToICS("unobserve\n");
\r
9843 StopExaminingEvent()
\r
9845 /* Stop observing current game */
\r
9846 SendToICS(ics_prefix);
\r
9847 SendToICS("unexamine\n");
\r
9851 ForwardInner(target)
\r
9856 if (appData.debugMode)
\r
9857 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
9858 target, currentMove, forwardMostMove);
\r
9860 if (gameMode == EditPosition)
\r
9863 if (gameMode == PlayFromGameFile && !pausing)
\r
9866 if (gameMode == IcsExamining && pausing)
\r
9867 limit = pauseExamForwardMostMove;
\r
9869 limit = forwardMostMove;
\r
9871 if (target > limit) target = limit;
\r
9873 if (target > 0 && moveList[target - 1][0]) {
\r
9874 int fromX, fromY, toX, toY;
\r
9875 toX = moveList[target - 1][2] - AAA;
\r
9876 toY = moveList[target - 1][3] - ONE;
\r
9877 if (moveList[target - 1][1] == '@') {
\r
9878 if (appData.highlightLastMove) {
\r
9879 SetHighlights(-1, -1, toX, toY);
\r
9882 fromX = moveList[target - 1][0] - AAA;
\r
9883 fromY = moveList[target - 1][1] - ONE;
\r
9884 if (target == currentMove + 1) {
\r
9885 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
9887 if (appData.highlightLastMove) {
\r
9888 SetHighlights(fromX, fromY, toX, toY);
\r
9892 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9893 gameMode == Training || gameMode == PlayFromGameFile ||
\r
9894 gameMode == AnalyzeFile) {
\r
9895 while (currentMove < target) {
\r
9896 SendMoveToProgram(currentMove++, &first);
\r
9899 currentMove = target;
\r
9902 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9903 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9904 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9906 DisplayBothClocks();
\r
9907 DisplayMove(currentMove - 1);
\r
9908 DrawPosition(FALSE, boards[currentMove]);
\r
9909 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9910 if (commentList[currentMove] && !matchMode && gameMode != Training) {
\r
9911 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9919 if (gameMode == IcsExamining && !pausing) {
\r
9920 SendToICS(ics_prefix);
\r
9921 SendToICS("forward\n");
\r
9923 ForwardInner(currentMove + 1);
\r
9930 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9931 /* to optimze, we temporarily turn off analysis mode while we feed
\r
9932 * the remaining moves to the engine. Otherwise we get analysis output
\r
9933 * after each move.
\r
9935 if (first.analysisSupport) {
\r
9936 SendToProgram("exit\nforce\n", &first);
\r
9937 first.analyzing = FALSE;
\r
9941 if (gameMode == IcsExamining && !pausing) {
\r
9942 SendToICS(ics_prefix);
\r
9943 SendToICS("forward 999999\n");
\r
9945 ForwardInner(forwardMostMove);
\r
9948 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9949 /* we have fed all the moves, so reactivate analysis mode */
\r
9950 SendToProgram("analyze\n", &first);
\r
9951 first.analyzing = TRUE;
\r
9952 /*first.maybeThinking = TRUE;*/
\r
9953 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9958 BackwardInner(target)
\r
9961 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
9963 if (appData.debugMode)
\r
9964 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
9965 target, currentMove, forwardMostMove);
\r
9967 if (gameMode == EditPosition) return;
\r
9968 if (currentMove <= backwardMostMove) {
\r
9969 ClearHighlights();
\r
9970 DrawPosition(full_redraw, boards[currentMove]);
\r
9973 if (gameMode == PlayFromGameFile && !pausing)
\r
9976 if (moveList[target][0]) {
\r
9977 int fromX, fromY, toX, toY;
\r
9978 toX = moveList[target][2] - AAA;
\r
9979 toY = moveList[target][3] - ONE;
\r
9980 if (moveList[target][1] == '@') {
\r
9981 if (appData.highlightLastMove) {
\r
9982 SetHighlights(-1, -1, toX, toY);
\r
9985 fromX = moveList[target][0] - AAA;
\r
9986 fromY = moveList[target][1] - ONE;
\r
9987 if (target == currentMove - 1) {
\r
9988 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
9990 if (appData.highlightLastMove) {
\r
9991 SetHighlights(fromX, fromY, toX, toY);
\r
9995 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
9996 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
9997 while (currentMove > target) {
\r
9998 SendToProgram("undo\n", &first);
\r
10002 currentMove = target;
\r
10005 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10006 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10007 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10009 DisplayBothClocks();
\r
10010 DisplayMove(currentMove - 1);
\r
10011 DrawPosition(full_redraw, boards[currentMove]);
\r
10012 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10013 if (commentList[currentMove] != NULL) {
\r
10014 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10021 if (gameMode == IcsExamining && !pausing) {
\r
10022 SendToICS(ics_prefix);
\r
10023 SendToICS("backward\n");
\r
10025 BackwardInner(currentMove - 1);
\r
10032 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10033 /* to optimze, we temporarily turn off analysis mode while we undo
\r
10034 * all the moves. Otherwise we get analysis output after each undo.
\r
10036 if (first.analysisSupport) {
\r
10037 SendToProgram("exit\nforce\n", &first);
\r
10038 first.analyzing = FALSE;
\r
10042 if (gameMode == IcsExamining && !pausing) {
\r
10043 SendToICS(ics_prefix);
\r
10044 SendToICS("backward 999999\n");
\r
10046 BackwardInner(backwardMostMove);
\r
10049 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10050 /* we have fed all the moves, so reactivate analysis mode */
\r
10051 SendToProgram("analyze\n", &first);
\r
10052 first.analyzing = TRUE;
\r
10053 /*first.maybeThinking = TRUE;*/
\r
10054 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10059 ToNrEvent(int to)
\r
10061 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
10062 if (to >= forwardMostMove) to = forwardMostMove;
\r
10063 if (to <= backwardMostMove) to = backwardMostMove;
\r
10064 if (to < currentMove) {
\r
10065 BackwardInner(to);
\r
10067 ForwardInner(to);
\r
10074 if (gameMode != IcsExamining) {
\r
10075 DisplayError("You are not examining a game", 0);
\r
10079 DisplayError("You can't revert while pausing", 0);
\r
10082 SendToICS(ics_prefix);
\r
10083 SendToICS("revert\n");
\r
10087 RetractMoveEvent()
\r
10089 switch (gameMode) {
\r
10090 case MachinePlaysWhite:
\r
10091 case MachinePlaysBlack:
\r
10092 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10093 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10096 if (forwardMostMove < 2) return;
\r
10097 currentMove = forwardMostMove = forwardMostMove - 2;
\r
10098 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10099 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10100 DisplayBothClocks();
\r
10101 DisplayMove(currentMove - 1);
\r
10102 ClearHighlights();/*!! could figure this out*/
\r
10103 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
10104 SendToProgram("remove\n", &first);
\r
10105 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
10108 case BeginningOfGame:
\r
10112 case IcsPlayingWhite:
\r
10113 case IcsPlayingBlack:
\r
10114 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
10115 SendToICS(ics_prefix);
\r
10116 SendToICS("takeback 2\n");
\r
10118 SendToICS(ics_prefix);
\r
10119 SendToICS("takeback 1\n");
\r
10128 ChessProgramState *cps;
\r
10130 switch (gameMode) {
\r
10131 case MachinePlaysWhite:
\r
10132 if (!WhiteOnMove(forwardMostMove)) {
\r
10133 DisplayError("It is your turn", 0);
\r
10138 case MachinePlaysBlack:
\r
10139 if (WhiteOnMove(forwardMostMove)) {
\r
10140 DisplayError("It is your turn", 0);
\r
10145 case TwoMachinesPlay:
\r
10146 if (WhiteOnMove(forwardMostMove) ==
\r
10147 (first.twoMachinesColor[0] == 'w')) {
\r
10153 case BeginningOfGame:
\r
10157 SendToProgram("?\n", cps);
\r
10161 TruncateGameEvent()
\r
10164 if (gameMode != EditGame) return;
\r
10171 if (forwardMostMove > currentMove) {
\r
10172 if (gameInfo.resultDetails != NULL) {
\r
10173 free(gameInfo.resultDetails);
\r
10174 gameInfo.resultDetails = NULL;
\r
10175 gameInfo.result = GameUnfinished;
\r
10177 forwardMostMove = currentMove;
\r
10178 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
10186 if (appData.noChessProgram) return;
\r
10187 switch (gameMode) {
\r
10188 case MachinePlaysWhite:
\r
10189 if (WhiteOnMove(forwardMostMove)) {
\r
10190 DisplayError("Wait until your turn", 0);
\r
10194 case BeginningOfGame:
\r
10195 case MachinePlaysBlack:
\r
10196 if (!WhiteOnMove(forwardMostMove)) {
\r
10197 DisplayError("Wait until your turn", 0);
\r
10202 DisplayError("No hint available", 0);
\r
10205 SendToProgram("hint\n", &first);
\r
10206 hintRequested = TRUE;
\r
10212 if (appData.noChessProgram) return;
\r
10213 switch (gameMode) {
\r
10214 case MachinePlaysWhite:
\r
10215 if (WhiteOnMove(forwardMostMove)) {
\r
10216 DisplayError("Wait until your turn", 0);
\r
10220 case BeginningOfGame:
\r
10221 case MachinePlaysBlack:
\r
10222 if (!WhiteOnMove(forwardMostMove)) {
\r
10223 DisplayError("Wait until your turn", 0);
\r
10227 case EditPosition:
\r
10228 EditPositionDone();
\r
10230 case TwoMachinesPlay:
\r
10235 SendToProgram("bk\n", &first);
\r
10236 bookOutput[0] = NULLCHAR;
\r
10237 bookRequested = TRUE;
\r
10243 char *tags = PGNTags(&gameInfo);
\r
10244 TagsPopUp(tags, CmailMsg());
\r
10248 /* end button procedures */
\r
10251 PrintPosition(fp, move)
\r
10257 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
10258 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
10259 char c = PieceToChar(boards[move][i][j]);
\r
10260 fputc(c == 'x' ? '.' : c, fp);
\r
10261 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
10264 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
10265 fprintf(fp, "white to play\n");
\r
10267 fprintf(fp, "black to play\n");
\r
10271 PrintOpponents(fp)
\r
10274 if (gameInfo.white != NULL) {
\r
10275 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
10277 fprintf(fp, "\n");
\r
10281 /* Find last component of program's own name, using some heuristics */
\r
10283 TidyProgramName(prog, host, buf)
\r
10284 char *prog, *host, buf[MSG_SIZ];
\r
10287 int local = (strcmp(host, "localhost") == 0);
\r
10288 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
10290 while (*p == ' ') p++;
\r
10293 if (*prog == '"' || *prog == '\'') {
\r
10294 q = strchr(prog + 1, *prog);
\r
10296 q = strchr(prog, ' ');
\r
10298 if (q == NULL) q = prog + strlen(prog);
\r
10300 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
10302 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
10303 memcpy(buf, p, q - p);
\r
10304 buf[q - p] = NULLCHAR;
\r
10306 strcat(buf, "@");
\r
10307 strcat(buf, host);
\r
10312 TimeControlTagValue()
\r
10314 char buf[MSG_SIZ];
\r
10315 if (!appData.clockMode) {
\r
10316 strcpy(buf, "-");
\r
10317 } else if (movesPerSession > 0) {
\r
10318 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
10319 } else if (timeIncrement == 0) {
\r
10320 sprintf(buf, "%ld", timeControl/1000);
\r
10322 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
10324 return StrSave(buf);
\r
10330 /* This routine is used only for certain modes */
\r
10331 VariantClass v = gameInfo.variant;
\r
10332 ClearGameInfo(&gameInfo);
\r
10333 gameInfo.variant = v;
\r
10335 switch (gameMode) {
\r
10336 case MachinePlaysWhite:
\r
10337 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10338 gameInfo.site = StrSave(HostName());
\r
10339 gameInfo.date = PGNDate();
\r
10340 gameInfo.round = StrSave("-");
\r
10341 gameInfo.white = StrSave(first.tidy);
\r
10342 gameInfo.black = StrSave(UserName());
\r
10343 gameInfo.timeControl = TimeControlTagValue();
\r
10346 case MachinePlaysBlack:
\r
10347 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10348 gameInfo.site = StrSave(HostName());
\r
10349 gameInfo.date = PGNDate();
\r
10350 gameInfo.round = StrSave("-");
\r
10351 gameInfo.white = StrSave(UserName());
\r
10352 gameInfo.black = StrSave(first.tidy);
\r
10353 gameInfo.timeControl = TimeControlTagValue();
\r
10356 case TwoMachinesPlay:
\r
10357 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10358 gameInfo.site = StrSave(HostName());
\r
10359 gameInfo.date = PGNDate();
\r
10360 if (matchGame > 0) {
\r
10361 char buf[MSG_SIZ];
\r
10362 sprintf(buf, "%d", matchGame);
\r
10363 gameInfo.round = StrSave(buf);
\r
10365 gameInfo.round = StrSave("-");
\r
10367 if (first.twoMachinesColor[0] == 'w') {
\r
10368 gameInfo.white = StrSave(first.tidy);
\r
10369 gameInfo.black = StrSave(second.tidy);
\r
10371 gameInfo.white = StrSave(second.tidy);
\r
10372 gameInfo.black = StrSave(first.tidy);
\r
10374 gameInfo.timeControl = TimeControlTagValue();
\r
10378 gameInfo.event = StrSave("Edited game");
\r
10379 gameInfo.site = StrSave(HostName());
\r
10380 gameInfo.date = PGNDate();
\r
10381 gameInfo.round = StrSave("-");
\r
10382 gameInfo.white = StrSave("-");
\r
10383 gameInfo.black = StrSave("-");
\r
10386 case EditPosition:
\r
10387 gameInfo.event = StrSave("Edited position");
\r
10388 gameInfo.site = StrSave(HostName());
\r
10389 gameInfo.date = PGNDate();
\r
10390 gameInfo.round = StrSave("-");
\r
10391 gameInfo.white = StrSave("-");
\r
10392 gameInfo.black = StrSave("-");
\r
10395 case IcsPlayingWhite:
\r
10396 case IcsPlayingBlack:
\r
10397 case IcsObserving:
\r
10398 case IcsExamining:
\r
10401 case PlayFromGameFile:
\r
10402 gameInfo.event = StrSave("Game from non-PGN file");
\r
10403 gameInfo.site = StrSave(HostName());
\r
10404 gameInfo.date = PGNDate();
\r
10405 gameInfo.round = StrSave("-");
\r
10406 gameInfo.white = StrSave("?");
\r
10407 gameInfo.black = StrSave("?");
\r
10416 ReplaceComment(index, text)
\r
10422 while (*text == '\n') text++;
\r
10423 len = strlen(text);
\r
10424 while (len > 0 && text[len - 1] == '\n') len--;
\r
10426 if (commentList[index] != NULL)
\r
10427 free(commentList[index]);
\r
10430 commentList[index] = NULL;
\r
10433 commentList[index] = (char *) malloc(len + 2);
\r
10434 strncpy(commentList[index], text, len);
\r
10435 commentList[index][len] = '\n';
\r
10436 commentList[index][len + 1] = NULLCHAR;
\r
10449 if (ch == '\r') continue;
\r
10451 } while (ch != '\0');
\r
10455 AppendComment(index, text)
\r
10462 GetInfoFromComment( index, text );
\r
10465 while (*text == '\n') text++;
\r
10466 len = strlen(text);
\r
10467 while (len > 0 && text[len - 1] == '\n') len--;
\r
10469 if (len == 0) return;
\r
10471 if (commentList[index] != NULL) {
\r
10472 old = commentList[index];
\r
10473 oldlen = strlen(old);
\r
10474 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
10475 strcpy(commentList[index], old);
\r
10477 strncpy(&commentList[index][oldlen], text, len);
\r
10478 commentList[index][oldlen + len] = '\n';
\r
10479 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
10481 commentList[index] = (char *) malloc(len + 2);
\r
10482 strncpy(commentList[index], text, len);
\r
10483 commentList[index][len] = '\n';
\r
10484 commentList[index][len + 1] = NULLCHAR;
\r
10488 static char * FindStr( char * text, char * sub_text )
\r
10490 char * result = strstr( text, sub_text );
\r
10492 if( result != NULL ) {
\r
10493 result += strlen( sub_text );
\r
10499 /* [AS] Try to extract PV info from PGN comment */
\r
10500 void GetInfoFromComment( int index, char * text )
\r
10502 if( text != NULL && index > 0 ) {
\r
10506 char * s_eval = FindStr( text, "[%eval " );
\r
10507 char * s_emt = FindStr( text, "[%emt " );
\r
10509 if( s_eval != NULL || s_emt != NULL ) {
\r
10513 if( s_eval != NULL ) {
\r
10514 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
10518 if( delim != ']' ) {
\r
10523 if( s_emt != NULL ) {
\r
10527 /* We expect something like: [+|-]nnn.nn/dd */
\r
10528 char * sep = strchr( text, '/' );
\r
10529 int score_lo = 0;
\r
10531 if( sep == NULL || sep < (text+4) ) {
\r
10535 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
10539 if( score_lo < 0 || score_lo >= 100 ) {
\r
10543 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
10546 if( depth <= 0 ) {
\r
10554 pvInfoList[index-1].depth = depth;
\r
10555 pvInfoList[index-1].score = score;
\r
10556 pvInfoList[index-1].time = time;
\r
10561 SendToProgram(message, cps)
\r
10563 ChessProgramState *cps;
\r
10565 int count, outCount, error;
\r
10566 char buf[MSG_SIZ];
\r
10568 if (cps->pr == NULL) return;
\r
10571 if (appData.debugMode) {
\r
10573 GetTimeMark(&now);
\r
10574 fprintf(debugFP, "%ld >%-6s: %s",
\r
10575 SubtractTimeMarks(&now, &programStartTime),
\r
10576 cps->which, message);
\r
10579 count = strlen(message);
\r
10580 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
10581 if (outCount < count && !exiting) {
\r
10582 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
10583 DisplayFatalError(buf, error, 1);
\r
10588 ReceiveFromProgram(isr, closure, message, count, error)
\r
10589 InputSourceRef isr;
\r
10590 VOIDSTAR closure;
\r
10596 char buf[MSG_SIZ];
\r
10597 ChessProgramState *cps = (ChessProgramState *)closure;
\r
10599 if (isr != cps->isr) return; /* Killed intentionally */
\r
10600 if (count <= 0) {
\r
10601 if (count == 0) {
\r
10603 "Error: %s chess program (%s) exited unexpectedly",
\r
10604 cps->which, cps->program);
\r
10605 RemoveInputSource(cps->isr);
\r
10606 DisplayFatalError(buf, 0, 1);
\r
10609 "Error reading from %s chess program (%s)",
\r
10610 cps->which, cps->program);
\r
10611 RemoveInputSource(cps->isr);
\r
10613 /* [AS] Program is misbehaving badly... kill it */
\r
10614 if( count == -2 ) {
\r
10615 DestroyChildProcess( cps->pr, 9 );
\r
10616 cps->pr = NoProc;
\r
10619 DisplayFatalError(buf, error, 1);
\r
10621 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10625 if ((end_str = strchr(message, '\r')) != NULL)
\r
10626 *end_str = NULLCHAR;
\r
10627 if ((end_str = strchr(message, '\n')) != NULL)
\r
10628 *end_str = NULLCHAR;
\r
10630 if (appData.debugMode) {
\r
10632 GetTimeMark(&now);
\r
10633 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
10634 SubtractTimeMarks(&now, &programStartTime),
\r
10635 cps->which, message);
\r
10637 HandleMachineMove(message, cps);
\r
10642 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
10643 ChessProgramState *cps;
\r
10644 int mps, inc, sd, st;
\r
10647 char buf[MSG_SIZ];
\r
10648 int seconds = (tc / 1000) % 60;
\r
10650 if( timeControl_2 > 0 ) {
\r
10651 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
10652 tc = timeControl_2;
\r
10657 /* Set exact time per move, normally using st command */
\r
10658 if (cps->stKludge) {
\r
10659 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
10660 seconds = st % 60;
\r
10661 if (seconds == 0) {
\r
10662 sprintf(buf, "level 1 %d\n", st/60);
\r
10664 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
10667 sprintf(buf, "st %d\n", st);
\r
10670 /* Set conventional or incremental time control, using level command */
\r
10671 if (seconds == 0) {
\r
10672 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
10673 Fixed in later versions, but still avoid :seconds
\r
10674 when seconds is 0. */
\r
10675 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
10677 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
10678 seconds, inc/1000);
\r
10681 SendToProgram(buf, cps);
\r
10683 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
10684 /* Orthogonally, limit search to given depth */
\r
10686 if (cps->sdKludge) {
\r
10687 sprintf(buf, "depth\n%d\n", sd);
\r
10689 sprintf(buf, "sd %d\n", sd);
\r
10691 SendToProgram(buf, cps);
\r
10696 SendTimeRemaining(cps, machineWhite)
\r
10697 ChessProgramState *cps;
\r
10698 int /*boolean*/ machineWhite;
\r
10700 char message[MSG_SIZ];
\r
10701 long time, otime;
\r
10703 /* Note: this routine must be called when the clocks are stopped
\r
10704 or when they have *just* been set or switched; otherwise
\r
10705 it will be off by the time since the current tick started.
\r
10707 if (machineWhite) {
\r
10708 time = whiteTimeRemaining / 10;
\r
10709 otime = blackTimeRemaining / 10;
\r
10711 time = blackTimeRemaining / 10;
\r
10712 otime = whiteTimeRemaining / 10;
\r
10714 if (time <= 0) time = 1;
\r
10715 if (otime <= 0) otime = 1;
\r
10717 sprintf(message, "time %ld\n", time);
\r
10718 SendToProgram(message, cps);
\r
10720 sprintf(message, "otim %ld\n", otime);
\r
10721 SendToProgram(message, cps);
\r
10725 BoolFeature(p, name, loc, cps)
\r
10729 ChessProgramState *cps;
\r
10731 char buf[MSG_SIZ];
\r
10732 int len = strlen(name);
\r
10734 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10736 sscanf(*p, "%d", &val);
\r
10737 *loc = (val != 0);
\r
10738 while (**p && **p != ' ') (*p)++;
\r
10739 sprintf(buf, "accepted %s\n", name);
\r
10740 SendToProgram(buf, cps);
\r
10747 IntFeature(p, name, loc, cps)
\r
10751 ChessProgramState *cps;
\r
10753 char buf[MSG_SIZ];
\r
10754 int len = strlen(name);
\r
10755 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10757 sscanf(*p, "%d", loc);
\r
10758 while (**p && **p != ' ') (*p)++;
\r
10759 sprintf(buf, "accepted %s\n", name);
\r
10760 SendToProgram(buf, cps);
\r
10767 StringFeature(p, name, loc, cps)
\r
10771 ChessProgramState *cps;
\r
10773 char buf[MSG_SIZ];
\r
10774 int len = strlen(name);
\r
10775 if (strncmp((*p), name, len) == 0
\r
10776 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
10778 sscanf(*p, "%[^\"]", loc);
\r
10779 while (**p && **p != '\"') (*p)++;
\r
10780 if (**p == '\"') (*p)++;
\r
10781 sprintf(buf, "accepted %s\n", name);
\r
10782 SendToProgram(buf, cps);
\r
10789 FeatureDone(cps, val)
\r
10790 ChessProgramState* cps;
\r
10793 DelayedEventCallback cb = GetDelayedEvent();
\r
10794 if ((cb == InitBackEnd3 && cps == &first) ||
\r
10795 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
10796 CancelDelayedEvent();
\r
10797 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
10799 cps->initDone = val;
\r
10802 /* Parse feature command from engine */
\r
10804 ParseFeatures(args, cps)
\r
10806 ChessProgramState *cps;
\r
10811 char buf[MSG_SIZ];
\r
10814 while (*p == ' ') p++;
\r
10815 if (*p == NULLCHAR) return;
\r
10817 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
10818 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
10819 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
10820 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
10821 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
10822 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
10823 /* Engine can disable reuse, but can't enable it if user said no */
\r
10824 if (!val) cps->reuse = FALSE;
\r
10827 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
10828 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
10829 if (gameMode == TwoMachinesPlay) {
\r
10830 DisplayTwoMachinesTitle();
\r
10832 DisplayTitle("");
\r
10836 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
10837 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
10838 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
10839 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
10840 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
10841 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
10842 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
10843 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
10844 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
10845 if (IntFeature(&p, "done", &val, cps)) {
\r
10846 FeatureDone(cps, val);
\r
10849 /* Added by Tord: */
\r
10850 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
10851 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
10852 /* End of additions by Tord */
\r
10854 /* unknown feature: complain and skip */
\r
10856 while (*q && *q != '=') q++;
\r
10857 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
10858 SendToProgram(buf, cps);
\r
10862 if (*p == '\"') {
\r
10864 while (*p && *p != '\"') p++;
\r
10865 if (*p == '\"') p++;
\r
10867 while (*p && *p != ' ') p++;
\r
10875 PeriodicUpdatesEvent(newState)
\r
10878 if (newState == appData.periodicUpdates)
\r
10881 appData.periodicUpdates=newState;
\r
10883 /* Display type changes, so update it now */
\r
10884 DisplayAnalysis();
\r
10886 /* Get the ball rolling again... */
\r
10888 AnalysisPeriodicEvent(1);
\r
10889 StartAnalysisClock();
\r
10894 PonderNextMoveEvent(newState)
\r
10897 if (newState == appData.ponderNextMove) return;
\r
10898 if (gameMode == EditPosition) EditPositionDone();
\r
10900 SendToProgram("hard\n", &first);
\r
10901 if (gameMode == TwoMachinesPlay) {
\r
10902 SendToProgram("hard\n", &second);
\r
10905 SendToProgram("easy\n", &first);
\r
10906 thinkOutput[0] = NULLCHAR;
\r
10907 if (gameMode == TwoMachinesPlay) {
\r
10908 SendToProgram("easy\n", &second);
\r
10911 appData.ponderNextMove = newState;
\r
10915 ShowThinkingEvent(newState)
\r
10918 if (newState == appData.showThinking) return;
\r
10919 if (gameMode == EditPosition) EditPositionDone();
\r
10921 SendToProgram("post\n", &first);
\r
10922 if (gameMode == TwoMachinesPlay) {
\r
10923 SendToProgram("post\n", &second);
\r
10926 SendToProgram("nopost\n", &first);
\r
10927 thinkOutput[0] = NULLCHAR;
\r
10928 if (gameMode == TwoMachinesPlay) {
\r
10929 SendToProgram("nopost\n", &second);
\r
10932 appData.showThinking = newState;
\r
10936 AskQuestionEvent(title, question, replyPrefix, which)
\r
10937 char *title; char *question; char *replyPrefix; char *which;
\r
10939 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
10940 if (pr == NoProc) return;
\r
10941 AskQuestion(title, question, replyPrefix, pr);
\r
10945 DisplayMove(moveNumber)
\r
10948 char message[MSG_SIZ];
\r
10949 char res[MSG_SIZ];
\r
10950 char cpThinkOutput[MSG_SIZ];
\r
10952 if (moveNumber == forwardMostMove - 1 ||
\r
10953 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10955 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
10957 if (strchr(cpThinkOutput, '\n')) {
\r
10958 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
10961 *cpThinkOutput = NULLCHAR;
\r
10964 /* [AS] Hide thinking from human user */
\r
10965 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
10966 *cpThinkOutput = NULLCHAR;
\r
10967 if( thinkOutput[0] != NULLCHAR ) {
\r
10970 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
10971 cpThinkOutput[i] = '.';
\r
10973 cpThinkOutput[i] = NULLCHAR;
\r
10974 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
10978 if (moveNumber == forwardMostMove - 1 &&
\r
10979 gameInfo.resultDetails != NULL) {
\r
10980 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
10981 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
10983 sprintf(res, " {%s} %s",
\r
10984 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
10987 res[0] = NULLCHAR;
\r
10990 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
10991 DisplayMessage(res, cpThinkOutput);
\r
10993 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
10994 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
10995 parseList[moveNumber], res);
\r
10996 DisplayMessage(message, cpThinkOutput);
\r
11001 DisplayAnalysisText(text)
\r
11004 char buf[MSG_SIZ];
\r
11006 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11007 sprintf(buf, "Analysis (%s)", first.tidy);
\r
11008 AnalysisPopUp(buf, text);
\r
11013 only_one_move(str)
\r
11016 while (*str && isspace(*str)) ++str;
\r
11017 while (*str && !isspace(*str)) ++str;
\r
11018 if (!*str) return 1;
\r
11019 while (*str && isspace(*str)) ++str;
\r
11020 if (!*str) return 1;
\r
11025 DisplayAnalysis()
\r
11027 char buf[MSG_SIZ];
\r
11028 char lst[MSG_SIZ / 2];
\r
11030 static char *xtra[] = { "", " (--)", " (++)" };
\r
11033 if (programStats.time == 0) {
\r
11034 programStats.time = 1;
\r
11037 if (programStats.got_only_move) {
\r
11038 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
11040 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
11042 nps = (((double)programStats.nodes) /
\r
11043 (((double)programStats.time)/100.0));
\r
11045 cs = programStats.time % 100;
\r
11046 s = programStats.time / 100;
\r
11047 h = (s / (60*60));
\r
11052 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
11053 if (programStats.move_name[0] != NULLCHAR) {
\r
11054 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11055 programStats.depth,
\r
11056 programStats.nr_moves-programStats.moves_left,
\r
11057 programStats.nr_moves, programStats.move_name,
\r
11058 ((float)programStats.score)/100.0, lst,
\r
11059 only_one_move(lst)?
\r
11060 xtra[programStats.got_fail] : "",
\r
11061 programStats.nodes, (int)nps, h, m, s, cs);
\r
11063 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11064 programStats.depth,
\r
11065 programStats.nr_moves-programStats.moves_left,
\r
11066 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
11068 only_one_move(lst)?
\r
11069 xtra[programStats.got_fail] : "",
\r
11070 programStats.nodes, (int)nps, h, m, s, cs);
\r
11073 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11074 programStats.depth,
\r
11075 ((float)programStats.score)/100.0,
\r
11077 only_one_move(lst)?
\r
11078 xtra[programStats.got_fail] : "",
\r
11079 programStats.nodes, (int)nps, h, m, s, cs);
\r
11082 DisplayAnalysisText(buf);
\r
11086 DisplayComment(moveNumber, text)
\r
11090 char title[MSG_SIZ];
\r
11092 if( appData.autoDisplayComment ) {
\r
11093 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11094 strcpy(title, "Comment");
\r
11096 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
11097 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11098 parseList[moveNumber]);
\r
11101 CommentPopUp(title, text);
\r
11105 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
11106 * might be busy thinking or pondering. It can be omitted if your
\r
11107 * gnuchess is configured to stop thinking immediately on any user
\r
11108 * input. However, that gnuchess feature depends on the FIONREAD
\r
11109 * ioctl, which does not work properly on some flavors of Unix.
\r
11113 ChessProgramState *cps;
\r
11116 if (!cps->useSigint) return;
\r
11117 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
11118 switch (gameMode) {
\r
11119 case MachinePlaysWhite:
\r
11120 case MachinePlaysBlack:
\r
11121 case TwoMachinesPlay:
\r
11122 case IcsPlayingWhite:
\r
11123 case IcsPlayingBlack:
\r
11124 case AnalyzeMode:
\r
11125 case AnalyzeFile:
\r
11126 /* Skip if we know it isn't thinking */
\r
11127 if (!cps->maybeThinking) return;
\r
11128 if (appData.debugMode)
\r
11129 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
11130 InterruptChildProcess(cps->pr);
\r
11131 cps->maybeThinking = FALSE;
\r
11136 #endif /*ATTENTION*/
\r
11142 if (whiteTimeRemaining <= 0) {
\r
11143 if (!whiteFlag) {
\r
11144 whiteFlag = TRUE;
\r
11145 if (appData.icsActive) {
\r
11146 if (appData.autoCallFlag &&
\r
11147 gameMode == IcsPlayingBlack && !blackFlag) {
\r
11148 SendToICS(ics_prefix);
\r
11149 SendToICS("flag\n");
\r
11153 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11155 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
11156 if (appData.autoCallFlag) {
\r
11157 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
11164 if (blackTimeRemaining <= 0) {
\r
11165 if (!blackFlag) {
\r
11166 blackFlag = TRUE;
\r
11167 if (appData.icsActive) {
\r
11168 if (appData.autoCallFlag &&
\r
11169 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
11170 SendToICS(ics_prefix);
\r
11171 SendToICS("flag\n");
\r
11175 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11177 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
11178 if (appData.autoCallFlag) {
\r
11179 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
11190 CheckTimeControl()
\r
11192 if (!appData.clockMode || appData.icsActive ||
\r
11193 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
11195 if (timeIncrement >= 0) {
\r
11196 if (WhiteOnMove(forwardMostMove)) {
\r
11197 blackTimeRemaining += timeIncrement;
\r
11199 whiteTimeRemaining += timeIncrement;
\r
11203 * add time to clocks when time control is achieved
\r
11205 if (movesPerSession) {
\r
11206 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
\r
11208 /* White made time control */
\r
11209 whiteTimeRemaining += GetTimeControlForWhite();
\r
11212 /* Black made time control */
\r
11213 blackTimeRemaining += GetTimeControlForBlack();
\r
11222 DisplayBothClocks()
\r
11224 int wom = gameMode == EditPosition ?
\r
11225 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
11226 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
11227 DisplayBlackClock(blackTimeRemaining, !wom);
\r
11231 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
11232 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
11233 to use other calls if you don't. Clocks will be less accurate if
\r
11234 you have neither ftime nor gettimeofday.
\r
11237 /* Get the current time as a TimeMark */
\r
11242 #if HAVE_GETTIMEOFDAY
\r
11244 struct timeval timeVal;
\r
11245 struct timezone timeZone;
\r
11247 gettimeofday(&timeVal, &timeZone);
\r
11248 tm->sec = (long) timeVal.tv_sec;
\r
11249 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
11251 #else /*!HAVE_GETTIMEOFDAY*/
\r
11254 #include <sys/timeb.h>
\r
11255 struct timeb timeB;
\r
11258 tm->sec = (long) timeB.time;
\r
11259 tm->ms = (int) timeB.millitm;
\r
11261 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
11262 tm->sec = (long) time(NULL);
\r
11268 /* Return the difference in milliseconds between two
\r
11269 time marks. We assume the difference will fit in a long!
\r
11272 SubtractTimeMarks(tm2, tm1)
\r
11273 TimeMark *tm2, *tm1;
\r
11275 return 1000L*(tm2->sec - tm1->sec) +
\r
11276 (long) (tm2->ms - tm1->ms);
\r
11281 * Code to manage the game clocks.
\r
11283 * In tournament play, black starts the clock and then white makes a move.
\r
11284 * We give the human user a slight advantage if he is playing white---the
\r
11285 * clocks don't run until he makes his first move, so it takes zero time.
\r
11286 * Also, we don't account for network lag, so we could get out of sync
\r
11287 * with GNU Chess's clock -- but then, referees are always right.
\r
11290 static TimeMark tickStartTM;
\r
11291 static long intendedTickLength;
\r
11294 NextTickLength(timeRemaining)
\r
11295 long timeRemaining;
\r
11297 long nominalTickLength, nextTickLength;
\r
11299 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
11300 nominalTickLength = 100L;
\r
11302 nominalTickLength = 1000L;
\r
11303 nextTickLength = timeRemaining % nominalTickLength;
\r
11304 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
11306 return nextTickLength;
\r
11309 /* Adjust clock one minute up or down */
\r
11311 AdjustClock(Boolean which, int dir)
\r
11313 if(which) blackTimeRemaining += 60000*dir;
\r
11314 else whiteTimeRemaining += 60000*dir;
\r
11315 DisplayBothClocks();
\r
11318 /* Stop clocks and reset to a fresh time control */
\r
11322 (void) StopClockTimer();
\r
11323 if (appData.icsActive) {
\r
11324 whiteTimeRemaining = blackTimeRemaining = 0;
\r
11326 whiteTimeRemaining = GetTimeControlForWhite();
\r
11327 blackTimeRemaining = GetTimeControlForBlack();
\r
11329 if (whiteFlag || blackFlag) {
\r
11330 DisplayTitle("");
\r
11331 whiteFlag = blackFlag = FALSE;
\r
11333 DisplayBothClocks();
\r
11336 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
11338 /* Decrement running clock by amount of time that has passed */
\r
11340 DecrementClocks()
\r
11342 long timeRemaining;
\r
11343 long lastTickLength, fudge;
\r
11346 if (!appData.clockMode) return;
\r
11347 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11349 GetTimeMark(&now);
\r
11351 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11353 /* Fudge if we woke up a little too soon */
\r
11354 fudge = intendedTickLength - lastTickLength;
\r
11355 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
11357 if (WhiteOnMove(forwardMostMove)) {
\r
11358 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
11359 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
11360 WhiteOnMove(currentMove));
\r
11362 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
11363 DisplayBlackClock(blackTimeRemaining - fudge,
\r
11364 !WhiteOnMove(currentMove));
\r
11367 if (CheckFlags()) return;
\r
11369 tickStartTM = now;
\r
11370 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
11371 StartClockTimer(intendedTickLength);
\r
11373 /* if the time remaining has fallen below the alarm threshold, sound the
\r
11374 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
11375 * with increment) the time remaining has increased to a level above the
\r
11376 * threshold, reset the alarm so it can sound again.
\r
11379 if (appData.icsActive && appData.icsAlarm) {
\r
11381 /* make sure we are dealing with the user's clock */
\r
11382 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
11383 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
11386 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
11387 alarmSounded = FALSE;
\r
11388 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
11389 PlayAlarmSound();
\r
11390 alarmSounded = TRUE;
\r
11396 /* A player has just moved, so stop the previously running
\r
11397 clock and (if in clock mode) start the other one.
\r
11398 We redisplay both clocks in case we're in ICS mode, because
\r
11399 ICS gives us an update to both clocks after every move.
\r
11400 Note that this routine is called *after* forwardMostMove
\r
11401 is updated, so the last fractional tick must be subtracted
\r
11402 from the color that is *not* on move now.
\r
11407 long lastTickLength;
\r
11409 int flagged = FALSE;
\r
11411 GetTimeMark(&now);
\r
11413 if (StopClockTimer() && appData.clockMode) {
\r
11414 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11415 if (WhiteOnMove(forwardMostMove)) {
\r
11416 blackTimeRemaining -= lastTickLength;
\r
11418 whiteTimeRemaining -= lastTickLength;
\r
11420 /* [HGM] save time for PGN file if engine did not give it */
\r
11421 if(pvInfoList[forwardMostMove-1].time == -1)
\r
11422 pvInfoList[forwardMostMove-1].time = lastTickLength/100;
\r
11423 flagged = CheckFlags();
\r
11425 CheckTimeControl();
\r
11427 if (flagged || !appData.clockMode) return;
\r
11429 switch (gameMode) {
\r
11430 case MachinePlaysBlack:
\r
11431 case MachinePlaysWhite:
\r
11432 case BeginningOfGame:
\r
11433 if (pausing) return;
\r
11437 case PlayFromGameFile:
\r
11438 case IcsExamining:
\r
11445 tickStartTM = now;
\r
11446 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11447 whiteTimeRemaining : blackTimeRemaining);
\r
11448 StartClockTimer(intendedTickLength);
\r
11452 /* Stop both clocks */
\r
11456 long lastTickLength;
\r
11459 if (!StopClockTimer()) return;
\r
11460 if (!appData.clockMode) return;
\r
11462 GetTimeMark(&now);
\r
11464 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11465 if (WhiteOnMove(forwardMostMove)) {
\r
11466 whiteTimeRemaining -= lastTickLength;
\r
11467 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
11469 blackTimeRemaining -= lastTickLength;
\r
11470 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
11475 /* Start clock of player on move. Time may have been reset, so
\r
11476 if clock is already running, stop and restart it. */
\r
11480 (void) StopClockTimer(); /* in case it was running already */
\r
11481 DisplayBothClocks();
\r
11482 if (CheckFlags()) return;
\r
11484 if (!appData.clockMode) return;
\r
11485 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11487 GetTimeMark(&tickStartTM);
\r
11488 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11489 whiteTimeRemaining : blackTimeRemaining);
\r
11490 StartClockTimer(intendedTickLength);
\r
11497 long second, minute, hour, day;
\r
11499 static char buf[32];
\r
11501 if (ms > 0 && ms <= 9900) {
\r
11502 /* convert milliseconds to tenths, rounding up */
\r
11503 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
11505 sprintf(buf, " %03.1f ", tenths/10.0);
\r
11509 /* convert milliseconds to seconds, rounding up */
\r
11510 /* use floating point to avoid strangeness of integer division
\r
11511 with negative dividends on many machines */
\r
11512 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
11514 if (second < 0) {
\r
11516 second = -second;
\r
11519 day = second / (60 * 60 * 24);
\r
11520 second = second % (60 * 60 * 24);
\r
11521 hour = second / (60 * 60);
\r
11522 second = second % (60 * 60);
\r
11523 minute = second / 60;
\r
11524 second = second % 60;
\r
11527 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
11528 sign, day, hour, minute, second);
\r
11529 else if (hour > 0)
\r
11530 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
11532 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
11539 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
11542 StrStr(string, match)
\r
11543 char *string, *match;
\r
11547 length = strlen(match);
\r
11549 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
11550 if (!strncmp(match, string, length))
\r
11557 StrCaseStr(string, match)
\r
11558 char *string, *match;
\r
11560 int i, j, length;
\r
11562 length = strlen(match);
\r
11564 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
11565 for (j = 0; j < length; j++) {
\r
11566 if (ToLower(match[j]) != ToLower(string[j]))
\r
11569 if (j == length) return string;
\r
11575 #ifndef _amigados
\r
11577 StrCaseCmp(s1, s2)
\r
11583 c1 = ToLower(*s1++);
\r
11584 c2 = ToLower(*s2++);
\r
11585 if (c1 > c2) return 1;
\r
11586 if (c1 < c2) return -1;
\r
11587 if (c1 == NULLCHAR) return 0;
\r
11596 return isupper(c) ? tolower(c) : c;
\r
11604 return islower(c) ? toupper(c) : c;
\r
11606 #endif /* !_amigados */
\r
11614 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
11621 StrSavePtr(s, savePtr)
\r
11622 char *s, **savePtr;
\r
11627 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
11628 strcpy(*savePtr, s);
\r
11630 return(*savePtr);
\r
11638 char buf[MSG_SIZ];
\r
11640 clock = time((time_t *)NULL);
\r
11641 tm = localtime(&clock);
\r
11642 sprintf(buf, "%04d.%02d.%02d",
\r
11643 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
11644 return StrSave(buf);
\r
11649 PositionToFEN(move, useFEN960)
\r
11653 int i, j, fromX, fromY, toX, toY;
\r
11658 ChessSquare piece;
\r
11660 whiteToPlay = (gameMode == EditPosition) ?
\r
11661 !blackPlaysFirst : (move % 2 == 0);
\r
11664 /* Piece placement data */
\r
11665 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11667 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11668 if (boards[move][i][j] == EmptySquare) {
\r
11670 } else { ChessSquare piece = boards[move][i][j];
\r
11671 if (emptycount > 0) {
\r
11672 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11673 *p++ = '0' + emptycount;
\r
11674 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11677 if(gameInfo.variant == VariantShogi) {
\r
11678 /* [HGM] write Shogi promoted pieces as +<unpromoted> */
\r
11679 if( (int)piece > (int) WhiteCannon && (int)piece < (int) WhiteKing ||
\r
11680 (int)piece > (int) BlackCannon && (int)piece < (int) BlackKing ) {
\r
11682 piece = (ChessSquare)(DEMOTED piece);
\r
11685 *p++ = PieceToChar(piece);
\r
11686 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantBughouse) {
\r
11687 /* [HGM] flag Crazyhouse promoted pieces */
\r
11688 if( (int)piece > (int) WhiteQueen && (int)piece < (int) WhiteKing ||
\r
11689 (int)piece > (int) BlackQueen && (int)piece < (int) BlackKing ) {
\r
11690 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
11696 if (emptycount > 0) {
\r
11697 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11698 *p++ = '0' + emptycount;
\r
11699 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11706 /* Active color */
\r
11707 *p++ = whiteToPlay ? 'w' : 'b';
\r
11710 /* HACK: we don't keep track of castling availability, so fake it! */
\r
11711 /* Tord! please fix with the aid of castlingRights[move][...] */
\r
11713 /* PUSH Fabien & Tord */
\r
11715 /* Declare all potential FRC castling rights (conservative) */
\r
11716 /* outermost rook on each side of the king */
\r
11718 if( gameInfo.variant == VariantFischeRandom ) {
\r
11723 /* White castling rights */
\r
11725 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11727 if (boards[move][0][fk] == WhiteKing) {
\r
11729 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11730 if (boards[move][0][fr] == WhiteRook) {
\r
11731 *p++ = useFEN960 ? 'A' + fr : 'K';
\r
11736 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11737 if (boards[move][0][fr] == WhiteRook) {
\r
11738 *p++ = useFEN960 ? 'A' + fr : 'Q';
\r
11745 /* Black castling rights */
\r
11747 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11749 if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {
\r
11751 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11752 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11753 *p++ = useFEN960 ? 'a' + fr : 'k';
\r
11758 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11759 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11760 *p++ = useFEN960 ? 'a' + fr : 'q';
\r
11767 if (q == p) *p++ = '-'; /* No castling rights */
\r
11773 #ifdef OLDCASTLINGCODE
\r
11774 if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {
\r
11775 if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';
\r
11776 if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';
\r
11778 if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {
\r
11779 if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';
\r
11780 if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';
\r
11783 /* [HGM] write true castling rights */
\r
11784 if( nrCastlingRights == 6 ) {
\r
11785 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
11786 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
11787 if(castlingRights[move][1] == BOARD_LEFT &&
\r
11788 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
11789 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
11790 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
11791 if(castlingRights[move][4] == BOARD_LEFT &&
\r
11792 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
11795 if (q == p) *p++ = '-';
\r
11799 /* POP Fabien & Tord */
\r
11801 /* En passant target square */
\r
11802 if (move > backwardMostMove) {
\r
11803 fromX = moveList[move - 1][0] - AAA;
\r
11804 fromY = moveList[move - 1][1] - ONE;
\r
11805 toX = moveList[move - 1][2] - AAA;
\r
11806 toY = moveList[move - 1][3] - ONE;
\r
11807 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
11808 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
11809 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
11811 /* 2-square pawn move just happened */
\r
11812 *p++ = toX + AAA;
\r
11813 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
11821 /* [HGM] print Crazyhouse holdings */
\r
11822 if( gameInfo.variant == VariantCrazyhouse ) {
\r
11823 *p++ = ' '; q = p;
\r
11824 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
11825 piece = boards[move][i][BOARD_WIDTH-1];
\r
11826 if( piece != EmptySquare )
\r
11827 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
11828 *p++ = PieceToChar(piece);
\r
11830 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
11831 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
11832 if( piece != EmptySquare )
\r
11833 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
11834 *p++ = PieceToChar(piece);
\r
11837 if( q == p ) *p++ = '-';
\r
11841 /* [HGM] find reversible plies */
\r
11842 { int i = 0, j=move;
\r
11844 if (appData.debugMode) { int k;
\r
11845 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
11846 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
11847 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
11851 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
11852 if( j == backwardMostMove ) i += initialRulePlies;
\r
11853 sprintf(p, " %d", i);
\r
11854 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
11856 /* Fullmove number */
\r
11857 sprintf(p, " %d", (move / 2) + 1);
\r
11859 return StrSave(buf);
\r
11863 ParseFEN(board, blackPlaysFirst, fen)
\r
11865 int *blackPlaysFirst;
\r
11871 ChessSquare piece;
\r
11875 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
11876 if(gameInfo.holdingsWidth) {
\r
11877 for(i=0; i<BOARD_HEIGHT; i++) {
\r
11878 board[i][0] = EmptySquare; /* black holdings */
\r
11879 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
11880 board[i][1] = (ChessSquare) 0; /* black counts */
\r
11881 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
11885 /* Piece placement data */
\r
11886 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11889 if (*p == '/' || *p == ' ') {
\r
11890 if (*p == '/') p++;
\r
11891 emptycount = gameInfo.boardWidth - j;
\r
11892 while (emptycount--)
\r
11893 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11895 #if(BOARD_SIZE >= 10)
\r
11896 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
11897 p++; emptycount=10;
\r
11898 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11899 while (emptycount--)
\r
11900 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11902 } else if (isdigit(*p)) {
\r
11903 emptycount = *p++ - '0';
\r
11904 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
11905 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11906 while (emptycount--)
\r
11907 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11908 } else if (*p == '+' || isalpha(*p)) {
\r
11909 if (j >= gameInfo.boardWidth) return FALSE;
\r
11910 if(*p=='+') { piece = (ChessSquare) (PROMOTED CharToPiece(*++p) ); p++; }
\r
11911 else piece = CharToPiece(*p++);
\r
11912 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
11913 piece = (ChessSquare) (PROMOTED piece);
\r
11916 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
11922 while (*p == '/' || *p == ' ') p++;
\r
11924 /* Active color */
\r
11927 *blackPlaysFirst = FALSE;
\r
11930 *blackPlaysFirst = TRUE;
\r
11936 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
11937 /* return the extra info in global variiables */
\r
11939 /* set defaults in case FEN is incomplete */
\r
11940 FENepStatus = EP_UNKNOWN;
\r
11941 for(i=0; i<nrCastlingRights; i++ ) {
\r
11942 FENcastlingRights[i] = initialRights[i];
\r
11943 } /* assume possible unless obviously impossible */
\r
11944 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
11945 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
11946 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
11947 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
11948 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
11949 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
11950 FENrulePlies = 0;
\r
11952 while(*p==' ') p++;
\r
11954 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11955 /* castling indicator present, so default is no castlings */
\r
11956 for(i=0; i<nrCastlingRights; i++ ) {
\r
11957 FENcastlingRights[i] = -1;
\r
11960 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11963 FENcastlingRights[0] = BOARD_RGHT-1;
\r
11964 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11967 FENcastlingRights[1] = BOARD_LEFT;
\r
11968 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11971 FENcastlingRights[3] = BOARD_RGHT-1;
\r
11972 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11975 FENcastlingRights[4] = BOARD_LEFT;
\r
11976 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11982 while(*p==' ') p++;
\r
11986 p++; FENepStatus = EP_NONE;
\r
11988 char c = *p++ - AAA;
\r
11990 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
11991 if(*p >= '0' && *p <='9') *p++;
\r
11995 /* [HGM] look for Crazyhouse holdings here */
\r
11996 while(*p==' ') p++;
\r
11997 if( !isdigit(*p) ) {
\r
11998 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
11999 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
12000 /* if we would allow FEN reading to set board size, we would */
\r
12001 /* have to add holdings and shift the board read so far here */
\r
12002 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
12004 if((int) piece >= (int) BlackPawn ) {
\r
12005 i = (int)piece - (int)BlackPawn;
\r
12006 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12007 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
12008 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
12010 i = (int)piece - (int)WhitePawn;
\r
12011 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12012 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
12013 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
12021 if(sscanf(p, "%d", &i) == 1) {
\r
12022 FENrulePlies = i; /* 50-move ply counter */
\r
12023 /* (The move number is still ignored) */
\r
12030 EditPositionPasteFEN(char *fen)
\r
12032 if (fen != NULL) {
\r
12033 Board initial_position;
\r
12035 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
12036 DisplayError("Bad FEN position in clipboard", 0);
\r
12039 int savedBlackPlaysFirst = blackPlaysFirst;
\r
12040 EditPositionEvent();
\r
12041 blackPlaysFirst = savedBlackPlaysFirst;
\r
12042 CopyBoard(boards[0], initial_position);
\r
12043 /* [HGM] copy FEN attributes as well */
\r
12045 initialRulePlies = FENrulePlies;
\r
12046 epStatus[0] = FENepStatus;
\r
12047 for( i=0; i<nrCastlingRights; i++ )
\r
12048 castlingRights[0][i] = FENcastlingRights[i];
\r
12050 EditPositionDone();
\r
12051 DisplayBothClocks();
\r
12052 DrawPosition(FALSE, boards[currentMove]);
\r