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_", &i, &i) == 2 ||
\r
1371 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1372 while( *e++ != '_');
\r
1375 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1376 if (StrCaseStr(e, variantNames[i])) {
\r
1377 v = (VariantClass) i;
\r
1384 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1385 || StrCaseStr(e, "wild/fr")) {
\r
1386 v = VariantFischeRandom;
\r
1387 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1388 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1390 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1391 if (isdigit(*p)) {
\r
1397 case 0: /* FICS only, actually */
\r
1399 /* Castling legal even if K starts on d-file */
\r
1400 v = VariantWildCastle;
\r
1405 /* Castling illegal even if K & R happen to start in
\r
1406 normal positions. */
\r
1407 v = VariantNoCastle;
\r
1420 /* Castling legal iff K & R start in normal positions */
\r
1421 v = VariantNormal;
\r
1426 /* Special wilds for position setup; unclear what to do here */
\r
1427 v = VariantLoadable;
\r
1430 /* Bizarre ICC game */
\r
1431 v = VariantTwoKings;
\r
1434 v = VariantKriegspiel;
\r
1437 v = VariantLosers;
\r
1440 v = VariantFischeRandom;
\r
1443 v = VariantCrazyhouse;
\r
1446 v = VariantBughouse;
\r
1449 v = Variant3Check;
\r
1452 /* Not quite the same as FICS suicide! */
\r
1453 v = VariantGiveaway;
\r
1456 v = VariantAtomic;
\r
1459 v = VariantShatranj;
\r
1462 /* Temporary names for future ICC types. The name *will* change in
\r
1463 the next xboard/WinBoard release after ICC defines it. */
\r
1492 v = VariantXiangqi;
\r
1495 v = VariantCourier;
\r
1498 v = VariantGothic;
\r
1501 v = VariantCapablanca;
\r
1504 v = VariantKnightmate;
\r
1510 v = VariantShowgi;
\r
1514 /* Found "wild" or "w" in the string but no number;
\r
1515 must assume it's normal chess. */
\r
1516 v = VariantNormal;
\r
1519 sprintf(buf, "Unknown wild type %d", wnum);
\r
1520 DisplayError(buf, 0);
\r
1521 v = VariantUnknown;
\r
1526 if (appData.debugMode) {
\r
1527 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1528 e, wnum, VariantName(v));
\r
1533 static int leftover_start = 0, leftover_len = 0;
\r
1534 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1536 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1537 advance *index beyond it, and set leftover_start to the new value of
\r
1538 *index; else return FALSE. If pattern contains the character '*', it
\r
1539 matches any sequence of characters not containing '\r', '\n', or the
\r
1540 character following the '*' (if any), and the matched sequence(s) are
\r
1541 copied into star_match.
\r
1544 looking_at(buf, index, pattern)
\r
1549 char *bufp = &buf[*index], *patternp = pattern;
\r
1550 int star_count = 0;
\r
1551 char *matchp = star_match[0];
\r
1554 if (*patternp == NULLCHAR) {
\r
1555 *index = leftover_start = bufp - buf;
\r
1556 *matchp = NULLCHAR;
\r
1559 if (*bufp == NULLCHAR) return FALSE;
\r
1560 if (*patternp == '*') {
\r
1561 if (*bufp == *(patternp + 1)) {
\r
1562 *matchp = NULLCHAR;
\r
1563 matchp = star_match[++star_count];
\r
1567 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1569 if (*patternp == NULLCHAR)
\r
1574 *matchp++ = *bufp++;
\r
1578 if (*patternp != *bufp) return FALSE;
\r
1585 SendToPlayer(data, length)
\r
1589 int error, outCount;
\r
1590 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1591 if (outCount < length) {
\r
1592 DisplayFatalError("Error writing to display", error, 1);
\r
1597 PackHolding(packed, holding)
\r
1601 char *p = holding;
\r
1603 int runlength = 0;
\r
1609 switch (runlength) {
\r
1620 sprintf(q, "%d", runlength);
\r
1632 /* Telnet protocol requests from the front end */
\r
1634 TelnetRequest(ddww, option)
\r
1635 unsigned char ddww, option;
\r
1637 unsigned char msg[3];
\r
1638 int outCount, outError;
\r
1640 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1642 if (appData.debugMode) {
\r
1643 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1659 sprintf(buf1, "%d", ddww);
\r
1664 optionStr = "ECHO";
\r
1668 sprintf(buf2, "%d", option);
\r
1671 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1676 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1677 if (outCount < 3) {
\r
1678 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1685 if (!appData.icsActive) return;
\r
1686 TelnetRequest(TN_DO, TN_ECHO);
\r
1692 if (!appData.icsActive) return;
\r
1693 TelnetRequest(TN_DONT, TN_ECHO);
\r
1697 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1699 /* put the holdings sent to us by the server on the board holdings area */
\r
1700 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1702 ChessSquare piece;
\r
1704 if(gameInfo.holdingsWidth < 2) return;
\r
1706 if( (int)lowestPiece >= BlackPawn ) {
\r
1707 holdingsColumn = 0;
\r
1709 holdingsStartRow = BOARD_HEIGHT-1;
\r
1712 holdingsColumn = BOARD_WIDTH-1;
\r
1713 countsColumn = BOARD_WIDTH-2;
\r
1714 holdingsStartRow = 0;
\r
1718 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1719 board[i][holdingsColumn] = EmptySquare;
\r
1720 board[i][countsColumn] = (ChessSquare) 0;
\r
1722 while( (p=*holdings++) != NULLCHAR ) {
\r
1723 piece = CharToPiece( ToUpper(p) );
\r
1724 if(piece == EmptySquare) continue;
\r
1725 /*j = (int) piece - (int) WhitePawn;*/
\r
1726 j = PieceToNumber(piece);
\r
1727 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1728 if(j < 0) continue; /* should not happen */
\r
1729 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1730 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1731 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1737 VariantSwitch(Board board, VariantClass newVariant)
\r
1739 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1740 if(gameInfo.variant == newVariant) return;
\r
1742 /* [HGM] This routine is called each time an assignment is made to
\r
1743 * gameInfo.variant during a game, to make sure the board sizes
\r
1744 * are set to match the new variant. If that means adding or deleting
\r
1745 * holdings, we shift the playing board accordingly
\r
1748 if (appData.debugMode) {
\r
1749 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1750 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1751 setbuf(debugFP, NULL);
\r
1753 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1754 switch(newVariant) {
\r
1755 case VariantShogi:
\r
1756 case VariantShowgi:
\r
1757 newWidth = 9; newHeight = 9;
\r
1758 gameInfo.holdingsSize = 7;
\r
1759 case VariantBughouse:
\r
1760 case VariantCrazyhouse:
\r
1761 newHoldingsWidth = 2; break;
\r
1763 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1766 if(newWidth != gameInfo.boardWidth ||
\r
1767 newHeight != gameInfo.boardHeight ||
\r
1768 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1770 /* shift position to new playing area, if needed */
\r
1771 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1772 for(i=0; i<BOARD_HEIGHT; i++)
\r
1773 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1774 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1776 for(i=0; i<newHeight; i++) {
\r
1777 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1778 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1780 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1781 for(i=0; i<BOARD_HEIGHT; i++)
\r
1782 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1783 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1787 gameInfo.boardWidth = newWidth;
\r
1788 gameInfo.boardHeight = newHeight;
\r
1789 gameInfo.holdingsWidth = newHoldingsWidth;
\r
1790 gameInfo.variant = newVariant;
\r
1791 InitDrawingSizes(-2, 0);
\r
1792 } else gameInfo.variant = newVariant;
\r
1795 static int loggedOn = FALSE;
\r
1797 /*-- Game start info cache: --*/
\r
1799 char gs_kind[MSG_SIZ];
\r
1800 static char player1Name[128] = "";
\r
1801 static char player2Name[128] = "";
\r
1802 static int player1Rating = -1;
\r
1803 static int player2Rating = -1;
\r
1804 /*----------------------------*/
\r
1806 ColorClass curColor = ColorNormal;
\r
1809 read_from_ics(isr, closure, data, count, error)
\r
1810 InputSourceRef isr;
\r
1816 #define BUF_SIZE 8192
\r
1817 #define STARTED_NONE 0
\r
1818 #define STARTED_MOVES 1
\r
1819 #define STARTED_BOARD 2
\r
1820 #define STARTED_OBSERVE 3
\r
1821 #define STARTED_HOLDINGS 4
\r
1822 #define STARTED_CHATTER 5
\r
1823 #define STARTED_COMMENT 6
\r
1824 #define STARTED_MOVES_NOHIDE 7
\r
1826 static int started = STARTED_NONE;
\r
1827 static char parse[20000];
\r
1828 static int parse_pos = 0;
\r
1829 static char buf[BUF_SIZE + 1];
\r
1830 static int firstTime = TRUE, intfSet = FALSE;
\r
1831 static ColorClass prevColor = ColorNormal;
\r
1832 static int savingComment = FALSE;
\r
1841 if (appData.debugMode) {
\r
1843 fprintf(debugFP, "<ICS: ");
\r
1844 show_bytes(debugFP, data, count);
\r
1845 fprintf(debugFP, "\n");
\r
1851 /* If last read ended with a partial line that we couldn't parse,
\r
1852 prepend it to the new read and try again. */
\r
1853 if (leftover_len > 0) {
\r
1854 for (i=0; i<leftover_len; i++)
\r
1855 buf[i] = buf[leftover_start + i];
\r
1858 /* Copy in new characters, removing nulls and \r's */
\r
1859 buf_len = leftover_len;
\r
1860 for (i = 0; i < count; i++) {
\r
1861 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1862 buf[buf_len++] = data[i];
\r
1865 buf[buf_len] = NULLCHAR;
\r
1866 next_out = leftover_len;
\r
1867 leftover_start = 0;
\r
1870 while (i < buf_len) {
\r
1871 /* Deal with part of the TELNET option negotiation
\r
1872 protocol. We refuse to do anything beyond the
\r
1873 defaults, except that we allow the WILL ECHO option,
\r
1874 which ICS uses to turn off password echoing when we are
\r
1875 directly connected to it. We reject this option
\r
1876 if localLineEditing mode is on (always on in xboard)
\r
1877 and we are talking to port 23, which might be a real
\r
1878 telnet server that will try to keep WILL ECHO on permanently.
\r
1880 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1881 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1882 unsigned char option;
\r
1884 switch ((unsigned char) buf[++i]) {
\r
1886 if (appData.debugMode)
\r
1887 fprintf(debugFP, "\n<WILL ");
\r
1888 switch (option = (unsigned char) buf[++i]) {
\r
1890 if (appData.debugMode)
\r
1891 fprintf(debugFP, "ECHO ");
\r
1892 /* Reply only if this is a change, according
\r
1893 to the protocol rules. */
\r
1894 if (remoteEchoOption) break;
\r
1895 if (appData.localLineEditing &&
\r
1896 atoi(appData.icsPort) == TN_PORT) {
\r
1897 TelnetRequest(TN_DONT, TN_ECHO);
\r
1900 TelnetRequest(TN_DO, TN_ECHO);
\r
1901 remoteEchoOption = TRUE;
\r
1905 if (appData.debugMode)
\r
1906 fprintf(debugFP, "%d ", option);
\r
1907 /* Whatever this is, we don't want it. */
\r
1908 TelnetRequest(TN_DONT, option);
\r
1913 if (appData.debugMode)
\r
1914 fprintf(debugFP, "\n<WONT ");
\r
1915 switch (option = (unsigned char) buf[++i]) {
\r
1917 if (appData.debugMode)
\r
1918 fprintf(debugFP, "ECHO ");
\r
1919 /* Reply only if this is a change, according
\r
1920 to the protocol rules. */
\r
1921 if (!remoteEchoOption) break;
\r
1923 TelnetRequest(TN_DONT, TN_ECHO);
\r
1924 remoteEchoOption = FALSE;
\r
1927 if (appData.debugMode)
\r
1928 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1929 /* Whatever this is, it must already be turned
\r
1930 off, because we never agree to turn on
\r
1931 anything non-default, so according to the
\r
1932 protocol rules, we don't reply. */
\r
1937 if (appData.debugMode)
\r
1938 fprintf(debugFP, "\n<DO ");
\r
1939 switch (option = (unsigned char) buf[++i]) {
\r
1941 /* Whatever this is, we refuse to do it. */
\r
1942 if (appData.debugMode)
\r
1943 fprintf(debugFP, "%d ", option);
\r
1944 TelnetRequest(TN_WONT, option);
\r
1949 if (appData.debugMode)
\r
1950 fprintf(debugFP, "\n<DONT ");
\r
1951 switch (option = (unsigned char) buf[++i]) {
\r
1953 if (appData.debugMode)
\r
1954 fprintf(debugFP, "%d ", option);
\r
1955 /* Whatever this is, we are already not doing
\r
1956 it, because we never agree to do anything
\r
1957 non-default, so according to the protocol
\r
1958 rules, we don't reply. */
\r
1963 if (appData.debugMode)
\r
1964 fprintf(debugFP, "\n<IAC ");
\r
1965 /* Doubled IAC; pass it through */
\r
1969 if (appData.debugMode)
\r
1970 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1971 /* Drop all other telnet commands on the floor */
\r
1974 if (oldi > next_out)
\r
1975 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1976 if (++i > next_out)
\r
1981 /* OK, this at least will *usually* work */
\r
1982 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1986 if (loggedOn && !intfSet) {
\r
1987 if (ics_type == ICS_ICC) {
\r
1989 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1992 } else if (ics_type == ICS_CHESSNET) {
\r
1993 sprintf(str, "/style 12\n");
\r
1995 strcpy(str, "alias $ @\n$set interface ");
\r
1996 strcat(str, programVersion);
\r
1997 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1999 strcat(str, "$iset nohighlight 1\n");
\r
2001 strcat(str, "$iset lock 1\n$style 12\n");
\r
2007 if (started == STARTED_COMMENT) {
\r
2008 /* Accumulate characters in comment */
\r
2009 parse[parse_pos++] = buf[i];
\r
2010 if (buf[i] == '\n') {
\r
2011 parse[parse_pos] = NULLCHAR;
\r
2012 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2013 started = STARTED_NONE;
\r
2015 /* Don't match patterns against characters in chatter */
\r
2020 if (started == STARTED_CHATTER) {
\r
2021 if (buf[i] != '\n') {
\r
2022 /* Don't match patterns against characters in chatter */
\r
2026 started = STARTED_NONE;
\r
2029 /* Kludge to deal with rcmd protocol */
\r
2030 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2031 DisplayFatalError(&buf[1], 0, 1);
\r
2034 firstTime = FALSE;
\r
2037 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2038 ics_type = ICS_ICC;
\r
2040 if (appData.debugMode)
\r
2041 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2044 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2045 ics_type = ICS_FICS;
\r
2047 if (appData.debugMode)
\r
2048 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2051 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2052 ics_type = ICS_CHESSNET;
\r
2054 if (appData.debugMode)
\r
2055 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2060 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2061 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2062 looking_at(buf, &i, "will be \"*\""))) {
\r
2063 strcpy(ics_handle, star_match[0]);
\r
2067 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2068 char buf[MSG_SIZ];
\r
2069 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2070 DisplayIcsInteractionTitle(buf);
\r
2071 have_set_title = TRUE;
\r
2074 /* skip finger notes */
\r
2075 if (started == STARTED_NONE &&
\r
2076 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2077 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2078 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2079 started = STARTED_CHATTER;
\r
2084 /* skip formula vars */
\r
2085 if (started == STARTED_NONE &&
\r
2086 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2087 started = STARTED_CHATTER;
\r
2093 if (appData.zippyTalk || appData.zippyPlay) {
\r
2095 if (ZippyControl(buf, &i) ||
\r
2096 ZippyConverse(buf, &i) ||
\r
2097 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2103 if (/* Don't color "message" or "messages" output */
\r
2104 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2105 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2106 looking_at(buf, &i, "--* (*:*): ") ||
\r
2107 /* Regular tells and says */
\r
2108 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2109 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2110 looking_at(buf, &i, "* says: ") ||
\r
2111 /* Message notifications (same color as tells) */
\r
2112 looking_at(buf, &i, "* has left a message ") ||
\r
2113 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2114 /* Whispers and kibitzes */
\r
2115 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2116 looking_at(buf, &i, "* kibitzes: ") ||
\r
2117 /* Channel tells */
\r
2118 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2120 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2121 /* Avoid "tells you:" spoofs in channels */
\r
2124 if (star_match[0][0] == NULLCHAR ||
\r
2125 strchr(star_match[0], ' ') ||
\r
2126 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2127 /* Reject bogus matches */
\r
2130 if (appData.colorize) {
\r
2131 if (oldi > next_out) {
\r
2132 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2137 Colorize(ColorTell, FALSE);
\r
2138 curColor = ColorTell;
\r
2141 Colorize(ColorKibitz, FALSE);
\r
2142 curColor = ColorKibitz;
\r
2145 p = strrchr(star_match[1], '(');
\r
2147 p = star_match[1];
\r
2151 if (atoi(p) == 1) {
\r
2152 Colorize(ColorChannel1, FALSE);
\r
2153 curColor = ColorChannel1;
\r
2155 Colorize(ColorChannel, FALSE);
\r
2156 curColor = ColorChannel;
\r
2160 curColor = ColorNormal;
\r
2164 if (started == STARTED_NONE && appData.autoComment &&
\r
2165 (gameMode == IcsObserving ||
\r
2166 gameMode == IcsPlayingWhite ||
\r
2167 gameMode == IcsPlayingBlack)) {
\r
2168 parse_pos = i - oldi;
\r
2169 memcpy(parse, &buf[oldi], parse_pos);
\r
2170 parse[parse_pos] = NULLCHAR;
\r
2171 started = STARTED_COMMENT;
\r
2172 savingComment = TRUE;
\r
2174 started = STARTED_CHATTER;
\r
2175 savingComment = FALSE;
\r
2182 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2183 looking_at(buf, &i, "* c-shouts: ")) {
\r
2184 if (appData.colorize) {
\r
2185 if (oldi > next_out) {
\r
2186 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2189 Colorize(ColorSShout, FALSE);
\r
2190 curColor = ColorSShout;
\r
2193 started = STARTED_CHATTER;
\r
2197 if (looking_at(buf, &i, "--->")) {
\r
2202 if (looking_at(buf, &i, "* shouts: ") ||
\r
2203 looking_at(buf, &i, "--> ")) {
\r
2204 if (appData.colorize) {
\r
2205 if (oldi > next_out) {
\r
2206 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2209 Colorize(ColorShout, FALSE);
\r
2210 curColor = ColorShout;
\r
2213 started = STARTED_CHATTER;
\r
2217 if (looking_at( buf, &i, "Challenge:")) {
\r
2218 if (appData.colorize) {
\r
2219 if (oldi > next_out) {
\r
2220 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2223 Colorize(ColorChallenge, FALSE);
\r
2224 curColor = ColorChallenge;
\r
2230 if (looking_at(buf, &i, "* offers you") ||
\r
2231 looking_at(buf, &i, "* offers to be") ||
\r
2232 looking_at(buf, &i, "* would like to") ||
\r
2233 looking_at(buf, &i, "* requests to") ||
\r
2234 looking_at(buf, &i, "Your opponent offers") ||
\r
2235 looking_at(buf, &i, "Your opponent requests")) {
\r
2237 if (appData.colorize) {
\r
2238 if (oldi > next_out) {
\r
2239 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2242 Colorize(ColorRequest, FALSE);
\r
2243 curColor = ColorRequest;
\r
2248 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2249 if (appData.colorize) {
\r
2250 if (oldi > next_out) {
\r
2251 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2254 Colorize(ColorSeek, FALSE);
\r
2255 curColor = ColorSeek;
\r
2261 if (looking_at(buf, &i, "\\ ")) {
\r
2262 if (prevColor != ColorNormal) {
\r
2263 if (oldi > next_out) {
\r
2264 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2267 Colorize(prevColor, TRUE);
\r
2268 curColor = prevColor;
\r
2270 if (savingComment) {
\r
2271 parse_pos = i - oldi;
\r
2272 memcpy(parse, &buf[oldi], parse_pos);
\r
2273 parse[parse_pos] = NULLCHAR;
\r
2274 started = STARTED_COMMENT;
\r
2276 started = STARTED_CHATTER;
\r
2281 if (looking_at(buf, &i, "Black Strength :") ||
\r
2282 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2283 looking_at(buf, &i, "<10>") ||
\r
2284 looking_at(buf, &i, "#@#")) {
\r
2285 /* Wrong board style */
\r
2287 SendToICS(ics_prefix);
\r
2288 SendToICS("set style 12\n");
\r
2289 SendToICS(ics_prefix);
\r
2290 SendToICS("refresh\n");
\r
2294 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2296 have_sent_ICS_logon = 1;
\r
2300 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2301 (looking_at(buf, &i, "\n<12> ") ||
\r
2302 looking_at(buf, &i, "<12> "))) {
\r
2304 if (oldi > next_out) {
\r
2305 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2308 started = STARTED_BOARD;
\r
2313 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2314 looking_at(buf, &i, "<b1> ")) {
\r
2315 if (oldi > next_out) {
\r
2316 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2319 started = STARTED_HOLDINGS;
\r
2324 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2326 /* Header for a move list -- first line */
\r
2328 switch (ics_getting_history) {
\r
2330 switch (gameMode) {
\r
2332 case BeginningOfGame:
\r
2333 /* User typed "moves" or "oldmoves" while we
\r
2334 were idle. Pretend we asked for these
\r
2335 moves and soak them up so user can step
\r
2336 through them and/or save them.
\r
2338 Reset(FALSE, TRUE);
\r
2339 gameMode = IcsObserving;
\r
2342 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2344 case EditGame: /*?*/
\r
2345 case EditPosition: /*?*/
\r
2346 /* Should above feature work in these modes too? */
\r
2347 /* For now it doesn't */
\r
2348 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2351 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2356 /* Is this the right one? */
\r
2357 if (gameInfo.white && gameInfo.black &&
\r
2358 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2359 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2361 ics_getting_history = H_GOT_REQ_HEADER;
\r
2364 case H_GOT_REQ_HEADER:
\r
2365 case H_GOT_UNREQ_HEADER:
\r
2366 case H_GOT_UNWANTED_HEADER:
\r
2367 case H_GETTING_MOVES:
\r
2368 /* Should not happen */
\r
2369 DisplayError("Error gathering move list: two headers", 0);
\r
2370 ics_getting_history = H_FALSE;
\r
2374 /* Save player ratings into gameInfo if needed */
\r
2375 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2376 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2377 (gameInfo.whiteRating == -1 ||
\r
2378 gameInfo.blackRating == -1)) {
\r
2380 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2381 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2382 if (appData.debugMode)
\r
2383 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2384 gameInfo.whiteRating, gameInfo.blackRating);
\r
2389 if (looking_at(buf, &i,
\r
2390 "* * match, initial time: * minute*, increment: * second")) {
\r
2391 /* Header for a move list -- second line */
\r
2392 /* Initial board will follow if this is a wild game */
\r
2393 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2394 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2395 gameInfo.event = StrSave(str);
\r
2396 /* [HGM] we switched variant. Translate boards if needed. */
\r
2397 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2401 if (looking_at(buf, &i, "Move ")) {
\r
2402 /* Beginning of a move list */
\r
2403 switch (ics_getting_history) {
\r
2405 /* Normally should not happen */
\r
2406 /* Maybe user hit reset while we were parsing */
\r
2409 /* Happens if we are ignoring a move list that is not
\r
2410 * the one we just requested. Common if the user
\r
2411 * tries to observe two games without turning off
\r
2414 case H_GETTING_MOVES:
\r
2415 /* Should not happen */
\r
2416 DisplayError("Error gathering move list: nested", 0);
\r
2417 ics_getting_history = H_FALSE;
\r
2419 case H_GOT_REQ_HEADER:
\r
2420 ics_getting_history = H_GETTING_MOVES;
\r
2421 started = STARTED_MOVES;
\r
2423 if (oldi > next_out) {
\r
2424 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2427 case H_GOT_UNREQ_HEADER:
\r
2428 ics_getting_history = H_GETTING_MOVES;
\r
2429 started = STARTED_MOVES_NOHIDE;
\r
2432 case H_GOT_UNWANTED_HEADER:
\r
2433 ics_getting_history = H_FALSE;
\r
2439 if (looking_at(buf, &i, "% ") ||
\r
2440 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2441 && looking_at(buf, &i, "}*"))) {
\r
2442 savingComment = FALSE;
\r
2443 switch (started) {
\r
2444 case STARTED_MOVES:
\r
2445 case STARTED_MOVES_NOHIDE:
\r
2446 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2447 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2448 ParseGameHistory(parse);
\r
2450 if (appData.zippyPlay && first.initDone) {
\r
2451 FeedMovesToProgram(&first, forwardMostMove);
\r
2452 if (gameMode == IcsPlayingWhite) {
\r
2453 if (WhiteOnMove(forwardMostMove)) {
\r
2454 if (first.sendTime) {
\r
2455 if (first.useColors) {
\r
2456 SendToProgram("black\n", &first);
\r
2458 SendTimeRemaining(&first, TRUE);
\r
2460 if (first.useColors) {
\r
2461 SendToProgram("white\ngo\n", &first);
\r
2463 SendToProgram("go\n", &first);
\r
2465 first.maybeThinking = TRUE;
\r
2467 if (first.usePlayother) {
\r
2468 if (first.sendTime) {
\r
2469 SendTimeRemaining(&first, TRUE);
\r
2471 SendToProgram("playother\n", &first);
\r
2472 firstMove = FALSE;
\r
2477 } else if (gameMode == IcsPlayingBlack) {
\r
2478 if (!WhiteOnMove(forwardMostMove)) {
\r
2479 if (first.sendTime) {
\r
2480 if (first.useColors) {
\r
2481 SendToProgram("white\n", &first);
\r
2483 SendTimeRemaining(&first, FALSE);
\r
2485 if (first.useColors) {
\r
2486 SendToProgram("black\ngo\n", &first);
\r
2488 SendToProgram("go\n", &first);
\r
2490 first.maybeThinking = TRUE;
\r
2492 if (first.usePlayother) {
\r
2493 if (first.sendTime) {
\r
2494 SendTimeRemaining(&first, FALSE);
\r
2496 SendToProgram("playother\n", &first);
\r
2497 firstMove = FALSE;
\r
2505 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2506 /* Moves came from oldmoves or moves command
\r
2507 while we weren't doing anything else.
\r
2509 currentMove = forwardMostMove;
\r
2510 ClearHighlights();/*!!could figure this out*/
\r
2511 flipView = appData.flipView;
\r
2512 DrawPosition(FALSE, boards[currentMove]);
\r
2513 DisplayBothClocks();
\r
2514 sprintf(str, "%s vs. %s",
\r
2515 gameInfo.white, gameInfo.black);
\r
2516 DisplayTitle(str);
\r
2517 gameMode = IcsIdle;
\r
2519 /* Moves were history of an active game */
\r
2520 if (gameInfo.resultDetails != NULL) {
\r
2521 free(gameInfo.resultDetails);
\r
2522 gameInfo.resultDetails = NULL;
\r
2525 HistorySet(parseList, backwardMostMove,
\r
2526 forwardMostMove, currentMove-1);
\r
2527 DisplayMove(currentMove - 1);
\r
2528 if (started == STARTED_MOVES) next_out = i;
\r
2529 started = STARTED_NONE;
\r
2530 ics_getting_history = H_FALSE;
\r
2533 case STARTED_OBSERVE:
\r
2534 started = STARTED_NONE;
\r
2535 SendToICS(ics_prefix);
\r
2536 SendToICS("refresh\n");
\r
2545 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2546 started == STARTED_HOLDINGS ||
\r
2547 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2548 /* Accumulate characters in move list or board */
\r
2549 parse[parse_pos++] = buf[i];
\r
2552 /* Start of game messages. Mostly we detect start of game
\r
2553 when the first board image arrives. On some versions
\r
2554 of the ICS, though, we need to do a "refresh" after starting
\r
2555 to observe in order to get the current board right away. */
\r
2556 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2557 started = STARTED_OBSERVE;
\r
2561 /* Handle auto-observe */
\r
2562 if (appData.autoObserve &&
\r
2563 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2564 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2566 /* Choose the player that was highlighted, if any. */
\r
2567 if (star_match[0][0] == '\033' ||
\r
2568 star_match[1][0] != '\033') {
\r
2569 player = star_match[0];
\r
2571 player = star_match[2];
\r
2573 sprintf(str, "%sobserve %s\n",
\r
2574 ics_prefix, StripHighlightAndTitle(player));
\r
2577 /* Save ratings from notify string */
\r
2578 strcpy(player1Name, star_match[0]);
\r
2579 player1Rating = string_to_rating(star_match[1]);
\r
2580 strcpy(player2Name, star_match[2]);
\r
2581 player2Rating = string_to_rating(star_match[3]);
\r
2583 if (appData.debugMode)
\r
2585 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2586 player1Name, player1Rating,
\r
2587 player2Name, player2Rating);
\r
2592 /* Deal with automatic examine mode after a game,
\r
2593 and with IcsObserving -> IcsExamining transition */
\r
2594 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2595 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2597 int gamenum = atoi(star_match[0]);
\r
2598 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2599 gamenum == ics_gamenum) {
\r
2600 /* We were already playing or observing this game;
\r
2601 no need to refetch history */
\r
2602 gameMode = IcsExamining;
\r
2604 pauseExamForwardMostMove = forwardMostMove;
\r
2605 } else if (currentMove < forwardMostMove) {
\r
2606 ForwardInner(forwardMostMove);
\r
2609 /* I don't think this case really can happen */
\r
2610 SendToICS(ics_prefix);
\r
2611 SendToICS("refresh\n");
\r
2616 /* Error messages */
\r
2617 if (ics_user_moved) {
\r
2618 if (looking_at(buf, &i, "Illegal move") ||
\r
2619 looking_at(buf, &i, "Not a legal move") ||
\r
2620 looking_at(buf, &i, "Your king is in check") ||
\r
2621 looking_at(buf, &i, "It isn't your turn") ||
\r
2622 looking_at(buf, &i, "It is not your move")) {
\r
2623 /* Illegal move */
\r
2624 ics_user_moved = 0;
\r
2625 if (forwardMostMove > backwardMostMove) {
\r
2626 currentMove = --forwardMostMove;
\r
2627 DisplayMove(currentMove - 1); /* before DMError */
\r
2628 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2629 DrawPosition(FALSE, boards[currentMove]);
\r
2631 DisplayBothClocks();
\r
2637 if (looking_at(buf, &i, "still have time") ||
\r
2638 looking_at(buf, &i, "not out of time") ||
\r
2639 looking_at(buf, &i, "either player is out of time") ||
\r
2640 looking_at(buf, &i, "has timeseal; checking")) {
\r
2641 /* We must have called his flag a little too soon */
\r
2642 whiteFlag = blackFlag = FALSE;
\r
2646 if (looking_at(buf, &i, "added * seconds to") ||
\r
2647 looking_at(buf, &i, "seconds were added to")) {
\r
2648 /* Update the clocks */
\r
2649 SendToICS(ics_prefix);
\r
2650 SendToICS("refresh\n");
\r
2654 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2655 ics_clock_paused = TRUE;
\r
2660 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2661 ics_clock_paused = FALSE;
\r
2666 /* Grab player ratings from the Creating: message.
\r
2667 Note we have to check for the special case when
\r
2668 the ICS inserts things like [white] or [black]. */
\r
2669 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2670 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2672 0 player 1 name (not necessarily white)
\r
2674 2 empty, white, or black (IGNORED)
\r
2675 3 player 2 name (not necessarily black)
\r
2678 The names/ratings are sorted out when the game
\r
2679 actually starts (below).
\r
2681 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2682 player1Rating = string_to_rating(star_match[1]);
\r
2683 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2684 player2Rating = string_to_rating(star_match[4]);
\r
2686 if (appData.debugMode)
\r
2688 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2689 player1Name, player1Rating,
\r
2690 player2Name, player2Rating);
\r
2695 /* Improved generic start/end-of-game messages */
\r
2696 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2697 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2698 /* If tkind == 0: */
\r
2699 /* star_match[0] is the game number */
\r
2700 /* [1] is the white player's name */
\r
2701 /* [2] is the black player's name */
\r
2702 /* For end-of-game: */
\r
2703 /* [3] is the reason for the game end */
\r
2704 /* [4] is a PGN end game-token, preceded by " " */
\r
2705 /* For start-of-game: */
\r
2706 /* [3] begins with "Creating" or "Continuing" */
\r
2707 /* [4] is " *" or empty (don't care). */
\r
2708 int gamenum = atoi(star_match[0]);
\r
2709 char *whitename, *blackname, *why, *endtoken;
\r
2710 ChessMove endtype = (ChessMove) 0;
\r
2713 whitename = star_match[1];
\r
2714 blackname = star_match[2];
\r
2715 why = star_match[3];
\r
2716 endtoken = star_match[4];
\r
2718 whitename = star_match[1];
\r
2719 blackname = star_match[3];
\r
2720 why = star_match[5];
\r
2721 endtoken = star_match[6];
\r
2724 /* Game start messages */
\r
2725 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2726 strncmp(why, "Continuing ", 11) == 0) {
\r
2727 gs_gamenum = gamenum;
\r
2728 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2730 if (appData.zippyPlay) {
\r
2731 ZippyGameStart(whitename, blackname);
\r
2737 /* Game end messages */
\r
2738 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2739 ics_gamenum != gamenum) {
\r
2742 while (endtoken[0] == ' ') endtoken++;
\r
2743 switch (endtoken[0]) {
\r
2746 endtype = GameUnfinished;
\r
2749 endtype = BlackWins;
\r
2752 if (endtoken[1] == '/')
\r
2753 endtype = GameIsDrawn;
\r
2755 endtype = WhiteWins;
\r
2758 GameEnds(endtype, why, GE_ICS);
\r
2760 if (appData.zippyPlay && first.initDone) {
\r
2761 ZippyGameEnd(endtype, why);
\r
2762 if (first.pr == NULL) {
\r
2763 /* Start the next process early so that we'll
\r
2764 be ready for the next challenge */
\r
2765 StartChessProgram(&first);
\r
2767 /* Send "new" early, in case this command takes
\r
2768 a long time to finish, so that we'll be ready
\r
2769 for the next challenge. */
\r
2770 Reset(TRUE, TRUE);
\r
2776 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2777 looking_at(buf, &i, "no longer observing game *") ||
\r
2778 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2779 if (gameMode == IcsObserving &&
\r
2780 atoi(star_match[0]) == ics_gamenum)
\r
2783 gameMode = IcsIdle;
\r
2785 ics_user_moved = FALSE;
\r
2790 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2791 if (gameMode == IcsExamining &&
\r
2792 atoi(star_match[0]) == ics_gamenum)
\r
2794 gameMode = IcsIdle;
\r
2796 ics_user_moved = FALSE;
\r
2801 /* Advance leftover_start past any newlines we find,
\r
2802 so only partial lines can get reparsed */
\r
2803 if (looking_at(buf, &i, "\n")) {
\r
2804 prevColor = curColor;
\r
2805 if (curColor != ColorNormal) {
\r
2806 if (oldi > next_out) {
\r
2807 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2810 Colorize(ColorNormal, FALSE);
\r
2811 curColor = ColorNormal;
\r
2813 if (started == STARTED_BOARD) {
\r
2814 started = STARTED_NONE;
\r
2815 parse[parse_pos] = NULLCHAR;
\r
2816 ParseBoard12(parse);
\r
2817 ics_user_moved = 0;
\r
2819 /* Send premove here */
\r
2820 if (appData.premove) {
\r
2821 char str[MSG_SIZ];
\r
2822 if (currentMove == 0 &&
\r
2823 gameMode == IcsPlayingWhite &&
\r
2824 appData.premoveWhite) {
\r
2825 sprintf(str, "%s%s\n", ics_prefix,
\r
2826 appData.premoveWhiteText);
\r
2827 if (appData.debugMode)
\r
2828 fprintf(debugFP, "Sending premove:\n");
\r
2830 } else if (currentMove == 1 &&
\r
2831 gameMode == IcsPlayingBlack &&
\r
2832 appData.premoveBlack) {
\r
2833 sprintf(str, "%s%s\n", ics_prefix,
\r
2834 appData.premoveBlackText);
\r
2835 if (appData.debugMode)
\r
2836 fprintf(debugFP, "Sending premove:\n");
\r
2838 } else if (gotPremove) {
\r
2840 ClearPremoveHighlights();
\r
2841 if (appData.debugMode)
\r
2842 fprintf(debugFP, "Sending premove:\n");
\r
2843 UserMoveEvent(premoveFromX, premoveFromY,
\r
2844 premoveToX, premoveToY,
\r
2845 premovePromoChar);
\r
2849 /* Usually suppress following prompt */
\r
2850 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2851 if (looking_at(buf, &i, "*% ")) {
\r
2852 savingComment = FALSE;
\r
2856 } else if (started == STARTED_HOLDINGS) {
\r
2858 char new_piece[MSG_SIZ];
\r
2859 started = STARTED_NONE;
\r
2860 parse[parse_pos] = NULLCHAR;
\r
2861 if (appData.debugMode)
\r
2862 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
2863 parse, currentMove);
\r
2864 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2865 gamenum == ics_gamenum) {
\r
2866 if (gameInfo.variant == VariantNormal) {
\r
2867 /* [HGM] We seem to switch variant during a game!
\r
2868 * Presumably no holdings were displayed, so we have
\r
2869 * to move the position two files to the right to
\r
2870 * create room for them!
\r
2872 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
2873 /* Get a move list just to see the header, which
\r
2874 will tell us whether this is really bug or zh */
\r
2875 if (ics_getting_history == H_FALSE) {
\r
2876 ics_getting_history = H_REQUESTED;
\r
2877 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2881 new_piece[0] = NULLCHAR;
\r
2882 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2883 &gamenum, white_holding, black_holding,
\r
2885 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2886 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2887 /* [HGM] copy holdings to board holdings area */
\r
2888 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2889 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2891 if (appData.zippyPlay && first.initDone) {
\r
2892 ZippyHoldings(white_holding, black_holding,
\r
2896 if (tinyLayout || smallLayout) {
\r
2897 char wh[16], bh[16];
\r
2898 PackHolding(wh, white_holding);
\r
2899 PackHolding(bh, black_holding);
\r
2900 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2901 gameInfo.white, gameInfo.black);
\r
2903 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2904 gameInfo.white, white_holding,
\r
2905 gameInfo.black, black_holding);
\r
2908 DrawPosition(FALSE, boards[currentMove]);
\r
2909 DisplayTitle(str);
\r
2911 /* Suppress following prompt */
\r
2912 if (looking_at(buf, &i, "*% ")) {
\r
2913 savingComment = FALSE;
\r
2920 i++; /* skip unparsed character and loop back */
\r
2923 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2924 started != STARTED_HOLDINGS && i > next_out) {
\r
2925 SendToPlayer(&buf[next_out], i - next_out);
\r
2929 leftover_len = buf_len - leftover_start;
\r
2930 /* if buffer ends with something we couldn't parse,
\r
2931 reparse it after appending the next read */
\r
2933 } else if (count == 0) {
\r
2934 RemoveInputSource(isr);
\r
2935 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2937 DisplayFatalError("Error reading from ICS", error, 1);
\r
2942 /* Board style 12 looks like this:
\r
2944 <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
2946 * The "<12> " is stripped before it gets to this routine. The two
\r
2947 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2948 * some chess servers may not have them, or may have only the first.
\r
2949 * Additional trailing fields may be added in the future.
\r
2952 #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
2954 #define RELATION_OBSERVING_PLAYED 0
\r
2955 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2956 #define RELATION_PLAYING_MYMOVE 1
\r
2957 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2958 #define RELATION_EXAMINING 2
\r
2959 #define RELATION_ISOLATED_BOARD -3
\r
2960 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2963 ParseBoard12(string)
\r
2966 GameMode newGameMode;
\r
2967 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2968 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2969 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2970 char to_play, board_chars[72];
\r
2971 char move_str[500], str[500], elapsed_time[500];
\r
2972 char black[32], white[32];
\r
2974 int prevMove = currentMove;
\r
2976 ChessMove moveType;
\r
2977 int fromX, fromY, toX, toY;
\r
2980 fromX = fromY = toX = toY = -1;
\r
2984 if (appData.debugMode)
\r
2985 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2987 move_str[0] = NULLCHAR;
\r
2988 elapsed_time[0] = NULLCHAR;
\r
2989 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2990 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2991 &gamenum, white, black, &relation, &basetime, &increment,
\r
2992 &white_stren, &black_stren, &white_time, &black_time,
\r
2993 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2997 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2998 DisplayError(str, 0);
\r
3002 /* Convert the move number to internal form */
\r
3003 moveNum = (moveNum - 1) * 2;
\r
3004 if (to_play == 'B') moveNum++;
\r
3005 if (moveNum >= MAX_MOVES) {
\r
3006 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3011 switch (relation) {
\r
3012 case RELATION_OBSERVING_PLAYED:
\r
3013 case RELATION_OBSERVING_STATIC:
\r
3014 if (gamenum == -1) {
\r
3015 /* Old ICC buglet */
\r
3016 relation = RELATION_OBSERVING_STATIC;
\r
3018 newGameMode = IcsObserving;
\r
3020 case RELATION_PLAYING_MYMOVE:
\r
3021 case RELATION_PLAYING_NOTMYMOVE:
\r
3023 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3024 IcsPlayingWhite : IcsPlayingBlack;
\r
3026 case RELATION_EXAMINING:
\r
3027 newGameMode = IcsExamining;
\r
3029 case RELATION_ISOLATED_BOARD:
\r
3031 /* Just display this board. If user was doing something else,
\r
3032 we will forget about it until the next board comes. */
\r
3033 newGameMode = IcsIdle;
\r
3035 case RELATION_STARTING_POSITION:
\r
3036 newGameMode = gameMode;
\r
3040 /* Modify behavior for initial board display on move listing
\r
3043 switch (ics_getting_history) {
\r
3047 case H_GOT_REQ_HEADER:
\r
3048 case H_GOT_UNREQ_HEADER:
\r
3049 /* This is the initial position of the current game */
\r
3050 gamenum = ics_gamenum;
\r
3051 moveNum = 0; /* old ICS bug workaround */
\r
3052 if (to_play == 'B') {
\r
3053 startedFromSetupPosition = TRUE;
\r
3054 blackPlaysFirst = TRUE;
\r
3056 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3057 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3058 if (currentMove == 0) currentMove = 1;
\r
3060 newGameMode = gameMode;
\r
3061 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3063 case H_GOT_UNWANTED_HEADER:
\r
3064 /* This is an initial board that we don't want */
\r
3066 case H_GETTING_MOVES:
\r
3067 /* Should not happen */
\r
3068 DisplayError("Error gathering move list: extra board", 0);
\r
3069 ics_getting_history = H_FALSE;
\r
3073 /* Take action if this is the first board of a new game, or of a
\r
3074 different game than is currently being displayed. */
\r
3075 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3076 relation == RELATION_ISOLATED_BOARD) {
\r
3078 /* Forget the old game and get the history (if any) of the new one */
\r
3079 if (gameMode != BeginningOfGame) {
\r
3080 Reset(FALSE, TRUE);
\r
3083 if (appData.autoRaiseBoard) BoardToTop();
\r
3085 if (gamenum == -1) {
\r
3086 newGameMode = IcsIdle;
\r
3087 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3088 appData.getMoveList) {
\r
3089 /* Need to get game history */
\r
3090 ics_getting_history = H_REQUESTED;
\r
3091 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3095 /* Initially flip the board to have black on the bottom if playing
\r
3096 black or if the ICS flip flag is set, but let the user change
\r
3097 it with the Flip View button. */
\r
3098 flipView = appData.autoFlipView ?
\r
3099 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3102 /* Done with values from previous mode; copy in new ones */
\r
3103 gameMode = newGameMode;
\r
3105 ics_gamenum = gamenum;
\r
3106 if (gamenum == gs_gamenum) {
\r
3107 int klen = strlen(gs_kind);
\r
3108 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3109 sprintf(str, "ICS %s", gs_kind);
\r
3110 gameInfo.event = StrSave(str);
\r
3112 gameInfo.event = StrSave("ICS game");
\r
3114 gameInfo.site = StrSave(appData.icsHost);
\r
3115 gameInfo.date = PGNDate();
\r
3116 gameInfo.round = StrSave("-");
\r
3117 gameInfo.white = StrSave(white);
\r
3118 gameInfo.black = StrSave(black);
\r
3119 timeControl = basetime * 60 * 1000;
\r
3120 timeControl_2 = 0;
\r
3121 timeIncrement = increment * 1000;
\r
3122 movesPerSession = 0;
\r
3123 gameInfo.timeControl = TimeControlTagValue();
\r
3124 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3125 if (appData.debugMode) {
\r
3126 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3127 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3128 setbuf(debugFP, NULL);
\r
3131 gameInfo.outOfBook = NULL;
\r
3133 /* Do we have the ratings? */
\r
3134 if (strcmp(player1Name, white) == 0 &&
\r
3135 strcmp(player2Name, black) == 0) {
\r
3136 if (appData.debugMode)
\r
3137 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3138 player1Rating, player2Rating);
\r
3139 gameInfo.whiteRating = player1Rating;
\r
3140 gameInfo.blackRating = player2Rating;
\r
3141 } else if (strcmp(player2Name, white) == 0 &&
\r
3142 strcmp(player1Name, black) == 0) {
\r
3143 if (appData.debugMode)
\r
3144 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3145 player2Rating, player1Rating);
\r
3146 gameInfo.whiteRating = player2Rating;
\r
3147 gameInfo.blackRating = player1Rating;
\r
3149 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3151 /* Silence shouts if requested */
\r
3152 if (appData.quietPlay &&
\r
3153 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3154 SendToICS(ics_prefix);
\r
3155 SendToICS("set shout 0\n");
\r
3159 /* Deal with midgame name changes */
\r
3161 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3162 if (gameInfo.white) free(gameInfo.white);
\r
3163 gameInfo.white = StrSave(white);
\r
3165 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3166 if (gameInfo.black) free(gameInfo.black);
\r
3167 gameInfo.black = StrSave(black);
\r
3171 /* Throw away game result if anything actually changes in examine mode */
\r
3172 if (gameMode == IcsExamining && !newGame) {
\r
3173 gameInfo.result = GameUnfinished;
\r
3174 if (gameInfo.resultDetails != NULL) {
\r
3175 free(gameInfo.resultDetails);
\r
3176 gameInfo.resultDetails = NULL;
\r
3180 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3181 in if they are in a different variation than we are. */
\r
3182 if (pauseExamInvalid) return;
\r
3183 if (pausing && gameMode == IcsExamining) {
\r
3184 if (moveNum <= pauseExamForwardMostMove) {
\r
3185 pauseExamInvalid = TRUE;
\r
3186 forwardMostMove = pauseExamForwardMostMove;
\r
3191 /* Parse the board */
\r
3192 for (k = 0; k < 8; k++) {
\r
3193 for (j = 0; j < 8; j++)
\r
3194 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3195 if(gameInfo.holdingsWidth > 1) {
\r
3196 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3197 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3200 CopyBoard(boards[moveNum], board);
\r
3201 if (moveNum == 0) {
\r
3202 startedFromSetupPosition =
\r
3203 !CompareBoards(board, initialPosition);
\r
3206 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3207 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3208 /* This was an initial position from a move list, not
\r
3209 the current position */
\r
3213 /* Update currentMove and known move number limits */
\r
3214 newMove = newGame || moveNum > forwardMostMove;
\r
3216 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3217 if (gameMode == IcsExamining && moveNum == 0) {
\r
3218 /* Workaround for ICS limitation: we are not told the wild
\r
3219 type when starting to examine a game. But if we ask for
\r
3220 the move list, the move list header will tell us */
\r
3221 ics_getting_history = H_REQUESTED;
\r
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3225 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3226 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3227 forwardMostMove = moveNum;
\r
3228 if (!pausing || currentMove > forwardMostMove)
\r
3229 currentMove = forwardMostMove;
\r
3231 /* New part of history that is not contiguous with old part */
\r
3232 if (pausing && gameMode == IcsExamining) {
\r
3233 pauseExamInvalid = TRUE;
\r
3234 forwardMostMove = pauseExamForwardMostMove;
\r
3237 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3238 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3239 ics_getting_history = H_REQUESTED;
\r
3240 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3245 /* Update the clocks */
\r
3246 if (strchr(elapsed_time, '.')) {
\r
3247 /* Time is in ms */
\r
3248 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3249 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3251 /* Time is in seconds */
\r
3252 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3253 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3258 if (appData.zippyPlay && newGame &&
\r
3259 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3260 gameMode != IcsExamining)
\r
3261 ZippyFirstBoard(moveNum, basetime, increment);
\r
3264 /* Put the move on the move list, first converting
\r
3265 to canonical algebraic form. */
\r
3266 if (moveNum > 0) {
\r
3267 if (appData.debugMode) {
\r
3268 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3269 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3270 setbuf(debugFP, NULL);
\r
3272 if (moveNum <= backwardMostMove) {
\r
3273 /* We don't know what the board looked like before
\r
3274 this move. Punt. */
\r
3275 strcpy(parseList[moveNum - 1], move_str);
\r
3276 strcat(parseList[moveNum - 1], " ");
\r
3277 strcat(parseList[moveNum - 1], elapsed_time);
\r
3278 moveList[moveNum - 1][0] = NULLCHAR;
\r
3279 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3280 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3281 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3282 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3283 fromY, fromX, toY, toX, promoChar,
\r
3284 parseList[moveNum-1]);
\r
3285 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3286 castlingRights[moveNum]) ) {
\r
3288 case MT_STALEMATE:
\r
3292 if(gameInfo.variant != VariantShogi)
\r
3293 strcat(parseList[moveNum - 1], "+");
\r
3295 case MT_CHECKMATE:
\r
3296 strcat(parseList[moveNum - 1], "#");
\r
3299 strcat(parseList[moveNum - 1], " ");
\r
3300 strcat(parseList[moveNum - 1], elapsed_time);
\r
3301 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3302 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3303 strcat(moveList[moveNum - 1], "\n");
\r
3304 } else if (strcmp(move_str, "none") == 0) {
\r
3305 /* Again, we don't know what the board looked like;
\r
3306 this is really the start of the game. */
\r
3307 parseList[moveNum - 1][0] = NULLCHAR;
\r
3308 moveList[moveNum - 1][0] = NULLCHAR;
\r
3309 backwardMostMove = moveNum;
\r
3310 startedFromSetupPosition = TRUE;
\r
3311 fromX = fromY = toX = toY = -1;
\r
3313 /* Move from ICS was illegal!? Punt. */
\r
3315 if (appData.testLegality && appData.debugMode) {
\r
3316 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3317 DisplayError(str, 0);
\r
3320 strcpy(parseList[moveNum - 1], move_str);
\r
3321 strcat(parseList[moveNum - 1], " ");
\r
3322 strcat(parseList[moveNum - 1], elapsed_time);
\r
3323 moveList[moveNum - 1][0] = NULLCHAR;
\r
3324 fromX = fromY = toX = toY = -1;
\r
3326 if (appData.debugMode) {
\r
3327 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3328 setbuf(debugFP, NULL);
\r
3332 /* Send move to chess program (BEFORE animating it). */
\r
3333 if (appData.zippyPlay && !newGame && newMove &&
\r
3334 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3336 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3337 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3338 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3339 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3341 DisplayError(str, 0);
\r
3343 if (first.sendTime) {
\r
3344 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3346 SendMoveToProgram(moveNum - 1, &first);
\r
3348 firstMove = FALSE;
\r
3349 if (first.useColors) {
\r
3350 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3352 "black\ngo\n", &first);
\r
3354 SendToProgram("go\n", &first);
\r
3356 first.maybeThinking = TRUE;
\r
3359 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3360 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3361 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3362 DisplayError(str, 0);
\r
3364 SendMoveToProgram(moveNum - 1, &first);
\r
3371 if (moveNum > 0 && !gotPremove) {
\r
3372 /* If move comes from a remote source, animate it. If it
\r
3373 isn't remote, it will have already been animated. */
\r
3374 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3375 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3377 if (!pausing && appData.highlightLastMove) {
\r
3378 SetHighlights(fromX, fromY, toX, toY);
\r
3382 /* Start the clocks */
\r
3383 whiteFlag = blackFlag = FALSE;
\r
3384 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3385 if (ticking == 0) {
\r
3386 ics_clock_paused = TRUE;
\r
3388 } else if (ticking == 1) {
\r
3389 ics_clock_paused = FALSE;
\r
3391 if (gameMode == IcsIdle ||
\r
3392 relation == RELATION_OBSERVING_STATIC ||
\r
3393 relation == RELATION_EXAMINING ||
\r
3395 DisplayBothClocks();
\r
3399 /* Display opponents and material strengths */
\r
3400 if (gameInfo.variant != VariantBughouse &&
\r
3401 gameInfo.variant != VariantCrazyhouse) {
\r
3402 if (tinyLayout || smallLayout) {
\r
3403 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3404 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3405 basetime, increment);
\r
3407 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3408 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3409 basetime, increment);
\r
3411 DisplayTitle(str);
\r
3415 /* Display the board */
\r
3418 if (appData.premove)
\r
3419 if (!gotPremove ||
\r
3420 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3421 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3422 ClearPremoveHighlights();
\r
3424 DrawPosition(FALSE, boards[currentMove]);
\r
3425 DisplayMove(moveNum - 1);
\r
3426 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3430 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3434 GetMoveListEvent()
\r
3436 char buf[MSG_SIZ];
\r
3437 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3438 ics_getting_history = H_REQUESTED;
\r
3439 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3445 AnalysisPeriodicEvent(force)
\r
3448 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3449 && !force) || !appData.periodicUpdates)
\r
3452 /* Send . command to Crafty to collect stats */
\r
3453 SendToProgram(".\n", &first);
\r
3455 /* Don't send another until we get a response (this makes
\r
3456 us stop sending to old Crafty's which don't understand
\r
3457 the "." command (sending illegal cmds resets node count & time,
\r
3458 which looks bad)) */
\r
3459 programStats.ok_to_send = 0;
\r
3463 SendMoveToProgram(moveNum, cps)
\r
3465 ChessProgramState *cps;
\r
3467 char buf[MSG_SIZ];
\r
3468 if (cps->useUsermove) {
\r
3469 SendToProgram("usermove ", cps);
\r
3471 if (cps->useSAN) {
\r
3473 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3474 int len = space - parseList[moveNum];
\r
3475 memcpy(buf, parseList[moveNum], len);
\r
3476 buf[len++] = '\n';
\r
3477 buf[len] = NULLCHAR;
\r
3479 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3481 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3482 if(BOARD_HEIGHT>9) {
\r
3484 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3486 SendToProgram(buf, cps);
\r
3488 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3489 * the engine. It would be nice to have a better way to identify castle
\r
3491 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3492 int fromX = moveList[moveNum][0] - AAA;
\r
3493 int fromY = moveList[moveNum][1] - ONE;
\r
3494 int toX = moveList[moveNum][2] - AAA;
\r
3495 int toY = moveList[moveNum][3] - ONE;
\r
3496 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3497 && boards[currentMove][toY][toX] == WhiteRook)
\r
3498 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3499 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3500 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3501 else SendToProgram("O-O-O\n", cps);
\r
3503 else SendToProgram(moveList[moveNum], cps);
\r
3505 else SendToProgram(moveList[moveNum], cps);
\r
3506 /* End of additions by Tord */
\r
3511 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3512 ChessMove moveType;
\r
3513 int fromX, fromY, toX, toY;
\r
3515 char user_move[MSG_SIZ];
\r
3517 switch (moveType) {
\r
3519 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3520 (int)moveType, fromX, fromY, toX, toY);
\r
3521 DisplayError(user_move + strlen("say "), 0);
\r
3523 case WhiteKingSideCastle:
\r
3524 case BlackKingSideCastle:
\r
3525 case WhiteQueenSideCastleWild:
\r
3526 case BlackQueenSideCastleWild:
\r
3528 case WhiteHSideCastleFR:
\r
3529 case BlackHSideCastleFR:
\r
3531 sprintf(user_move, "o-o\n");
\r
3533 case WhiteQueenSideCastle:
\r
3534 case BlackQueenSideCastle:
\r
3535 case WhiteKingSideCastleWild:
\r
3536 case BlackKingSideCastleWild:
\r
3538 case WhiteASideCastleFR:
\r
3539 case BlackASideCastleFR:
\r
3541 sprintf(user_move, "o-o-o\n");
\r
3543 case WhitePromotionQueen:
\r
3544 case BlackPromotionQueen:
\r
3545 case WhitePromotionRook:
\r
3546 case BlackPromotionRook:
\r
3547 case WhitePromotionBishop:
\r
3548 case BlackPromotionBishop:
\r
3549 case WhitePromotionKnight:
\r
3550 case BlackPromotionKnight:
\r
3551 case WhitePromotionKing:
\r
3552 case BlackPromotionKing:
\r
3554 case WhitePromotionChancellor:
\r
3555 case BlackPromotionChancellor:
\r
3556 case WhitePromotionArchbishop:
\r
3557 case BlackPromotionArchbishop:
\r
3559 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3560 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3561 PieceToChar(PromoPiece(moveType)));
\r
3565 sprintf(user_move, "%c@%c%c\n",
\r
3566 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3567 AAA + toX, ONE + toY);
\r
3570 case WhiteCapturesEnPassant:
\r
3571 case BlackCapturesEnPassant:
\r
3572 case IllegalMove: /* could be a variant we don't quite understand */
\r
3573 sprintf(user_move, "%c%c%c%c\n",
\r
3574 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3577 SendToICS(user_move);
\r
3581 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3582 int rf, ff, rt, ft;
\r
3586 if (rf == DROP_RANK) {
\r
3587 sprintf(move, "%c@%c%c\n",
\r
3588 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3590 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3591 sprintf(move, "%c%c%c%c\n",
\r
3592 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3594 sprintf(move, "%c%c%c%c%c\n",
\r
3595 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3598 AlphaRank(move, 4);
\r
3602 ProcessICSInitScript(f)
\r
3605 char buf[MSG_SIZ];
\r
3607 while (fgets(buf, MSG_SIZ, f)) {
\r
3608 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3615 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3617 AlphaRank(char *move, int n)
\r
3619 char *p = move, c;
\r
3621 if( !appData.alphaRank ) return;
\r
3624 if(c>='0' && c<='9') *p += AAA-ONE; else
\r
3625 if(c>='a' && c<'x') *p -= AAA-ONE;
\r
3627 if(--n < 1) break;
\r
3631 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3633 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3636 ChessMove *moveType;
\r
3637 int *fromX, *fromY, *toX, *toY;
\r
3640 if (appData.debugMode) {
\r
3641 fprintf(debugFP, "move to parse: %s\n", move);
\r
3643 AlphaRank(move, 10);
\r
3644 *moveType = yylexstr(moveNum, move);
\r
3646 switch (*moveType) {
\r
3648 case WhitePromotionChancellor:
\r
3649 case BlackPromotionChancellor:
\r
3650 case WhitePromotionArchbishop:
\r
3651 case BlackPromotionArchbishop:
\r
3653 case WhitePromotionQueen:
\r
3654 case BlackPromotionQueen:
\r
3655 case WhitePromotionRook:
\r
3656 case BlackPromotionRook:
\r
3657 case WhitePromotionBishop:
\r
3658 case BlackPromotionBishop:
\r
3659 case WhitePromotionKnight:
\r
3660 case BlackPromotionKnight:
\r
3661 case WhitePromotionKing:
\r
3662 case BlackPromotionKing:
\r
3664 case WhiteCapturesEnPassant:
\r
3665 case BlackCapturesEnPassant:
\r
3666 case WhiteKingSideCastle:
\r
3667 case WhiteQueenSideCastle:
\r
3668 case BlackKingSideCastle:
\r
3669 case BlackQueenSideCastle:
\r
3670 case WhiteKingSideCastleWild:
\r
3671 case WhiteQueenSideCastleWild:
\r
3672 case BlackKingSideCastleWild:
\r
3673 case BlackQueenSideCastleWild:
\r
3674 /* Code added by Tord: */
\r
3675 case WhiteHSideCastleFR:
\r
3676 case WhiteASideCastleFR:
\r
3677 case BlackHSideCastleFR:
\r
3678 case BlackASideCastleFR:
\r
3679 /* End of code added by Tord */
\r
3680 case IllegalMove: /* bug or odd chess variant */
\r
3681 *fromX = currentMoveString[0] - AAA;
\r
3682 *fromY = currentMoveString[1] - ONE;
\r
3683 *toX = currentMoveString[2] - AAA;
\r
3684 *toY = currentMoveString[3] - ONE;
\r
3685 *promoChar = currentMoveString[4];
\r
3686 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3687 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3688 *fromX = *fromY = *toX = *toY = 0;
\r
3691 if (appData.testLegality) {
\r
3692 return (*moveType != IllegalMove);
\r
3694 return !(fromX == fromY && toX == toY);
\r
3699 *fromX = *moveType == WhiteDrop ?
\r
3700 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3701 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3702 *fromY = DROP_RANK;
\r
3703 *toX = currentMoveString[2] - AAA;
\r
3704 *toY = currentMoveString[3] - ONE;
\r
3705 *promoChar = NULLCHAR;
\r
3708 case AmbiguousMove:
\r
3709 case ImpossibleMove:
\r
3710 case (ChessMove) 0: /* end of file */
\r
3720 *fromX = *fromY = *toX = *toY = 0;
\r
3721 *promoChar = NULLCHAR;
\r
3726 /* [AS] FRC game initialization */
\r
3727 static int FindEmptySquare( Board board, int n )
\r
3732 while( board[0][i] != EmptySquare ) i++;
\r
3742 static void ShuffleFRC( Board board )
\r
3748 for( i=0; i<8; i++ ) {
\r
3749 board[0][i] = EmptySquare;
\r
3752 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3753 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
3754 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
3755 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
3756 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
3757 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3758 board[0][FindEmptySquare(board, 0)] = WhiteKing;
\r
3759 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3761 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3762 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3766 static unsigned char FRC_KnightTable[10] = {
\r
3767 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
3770 static void SetupFRC( Board board, int pos_index )
\r
3773 unsigned char knights;
\r
3775 /* Bring the position index into a safe range (just in case...) */
\r
3776 if( pos_index < 0 ) pos_index = 0;
\r
3780 /* Clear the board */
\r
3781 for( i=0; i<8; i++ ) {
\r
3782 board[0][i] = EmptySquare;
\r
3785 /* Place bishops and queen */
\r
3786 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
3789 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3792 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
3795 /* Place knigths */
\r
3796 knights = FRC_KnightTable[ pos_index ];
\r
3798 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
3799 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
3801 /* Place rooks and king */
\r
3802 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3803 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
\r
3804 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3806 /* Mirror piece placement for black */
\r
3807 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3808 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3812 BOOL SetCharTable( char *table, const char * map )
\r
3813 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
3814 /* Basically a safe strcpy that uses the last character as King */
\r
3816 BOOL result = FALSE; int NrPieces;
\r
3818 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
3819 && NrPieces >= 12 && !(NrPieces&1)) {
\r
3820 int i; /* [HGM] Accept even length from 12 to 34 */
\r
3822 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
3823 for( i=0; i<NrPieces/2-1; i++ ) {
\r
3824 table[i] = map[i];
\r
3825 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
3827 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
3828 table[(int) BlackKing] = map[NrPieces-1];
\r
3837 InitPosition(redraw)
\r
3840 ChessSquare (* pieces)[BOARD_SIZE];
\r
3841 int i, j, pawnRow, overrule,
\r
3842 oldx = gameInfo.boardWidth,
\r
3843 oldy = gameInfo.boardHeight,
\r
3844 oldh = gameInfo.holdingsWidth,
\r
3845 oldv = gameInfo.variant;
\r
3847 currentMove = forwardMostMove = backwardMostMove = 0;
\r
3849 /* [AS] Initialize pv info list [HGM] and game status */
\r
3851 for( i=0; i<MAX_MOVES; i++ ) {
\r
3852 pvInfoList[i].depth = 0;
\r
3853 epStatus[i]=EP_NONE;
\r
3854 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
3857 initialRulePlies = 0; /* 50-move counter start */
\r
3861 /* [HGM] logic here is completely changed. In stead of full positions */
\r
3862 /* the initialized data only consist of the two backranks. The switch */
\r
3863 /* selects which one we will use, which is than copied to the Board */
\r
3864 /* initialPosition, which for the rest is initialized by Pawns and */
\r
3865 /* empty squares. This initial position is then copied to boards[0], */
\r
3866 /* possibly after shuffling, so that it remains available. */
\r
3868 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
3869 gameInfo.boardWidth = 8;
\r
3870 gameInfo.boardHeight = 8;
\r
3871 gameInfo.holdingsSize = 0;
\r
3872 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
3873 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
3874 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
3876 switch (gameInfo.variant) {
\r
3878 pieces = FIDEArray;
\r
3880 case VariantShatranj:
\r
3881 pieces = ShatranjArray;
\r
3882 nrCastlingRights = 0;
\r
3884 case VariantTwoKings:
\r
3885 pieces = twoKingsArray;
\r
3886 nrCastlingRights = 8; /* add rights for second King */
\r
3887 castlingRights[0][6] = initialRights[2] = 5;
\r
3888 castlingRights[0][7] = initialRights[5] = 5;
\r
3889 castlingRank[6] = 0;
\r
3890 castlingRank[6] = BOARD_HEIGHT-1;
\r
3891 startedFromSetupPosition = TRUE;
\r
3893 case VariantCapablanca:
\r
3894 pieces = CapablancaArray;
\r
3895 gameInfo.boardWidth = 10;
\r
3896 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3898 case VariantGothic:
\r
3899 pieces = GothicArray;
\r
3900 gameInfo.boardWidth = 10;
\r
3901 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3903 case VariantXiangqi:
\r
3904 pieces = XiangqiArray;
\r
3905 gameInfo.boardWidth = 9;
\r
3906 gameInfo.boardHeight = 10;
\r
3907 nrCastlingRights = 0;
\r
3908 SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c.......");
\r
3910 case VariantShogi:
\r
3911 pieces = ShogiArray;
\r
3912 gameInfo.boardWidth = 9;
\r
3913 gameInfo.boardHeight = 9;
\r
3914 gameInfo.holdingsSize = 7;
\r
3915 nrCastlingRights = 0;
\r
3916 SetCharTable(pieceToChar, "PNBRLSG.........Kpnbrlsg.........k");
\r
3918 case VariantShowgi:
\r
3919 pieces = ShogiArray;
\r
3920 gameInfo.boardWidth = 9;
\r
3921 gameInfo.boardHeight = 9;
\r
3922 gameInfo.holdingsSize = 7;
\r
3923 nrCastlingRights = 0;
\r
3924 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3925 SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k");
\r
3927 case VariantCourier:
\r
3928 pieces = CourierArray;
\r
3929 gameInfo.boardWidth = 12;
\r
3930 nrCastlingRights = 0;
\r
3931 SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k");
\r
3932 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3934 case VariantKnightmate:
\r
3935 pieces = KnightmateArray;
\r
3936 strcpy(pieceToChar, "P.BRQ...M.K......p.brq...m.k......");
\r
3938 case VariantFairy:
\r
3939 pieces = fairyArray;
\r
3940 SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk");
\r
3941 startedFromSetupPosition = TRUE;
\r
3943 case VariantCrazyhouse:
\r
3944 case VariantBughouse:
\r
3945 pieces = FIDEArray;
\r
3946 gameInfo.holdingsSize = 5;
\r
3948 case VariantWildCastle:
\r
3949 pieces = FIDEArray;
\r
3950 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
3952 case VariantNoCastle:
\r
3953 pieces = FIDEArray;
\r
3954 nrCastlingRights = 0;
\r
3955 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3956 /* !!?unconstrained back-rank shuffle */
\r
3961 if(appData.NrFiles >= 0) {
\r
3962 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
3963 gameInfo.boardWidth = appData.NrFiles;
\r
3965 if(appData.NrRanks >= 0) {
\r
3966 gameInfo.boardHeight = appData.NrRanks;
\r
3968 if(appData.holdingsSize >= 0) {
\r
3969 i = appData.holdingsSize;
\r
3970 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
3971 gameInfo.holdingsSize = i;
\r
3973 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
3974 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
3975 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
3977 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
3978 if(pawnRow < 1) pawnRow = 1;
\r
3980 /* User pieceToChar list overrules defaults */
\r
3981 if(appData.pieceToCharTable != NULL)
\r
3982 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
3984 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
3986 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
3987 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
3988 for( i=0; i<BOARD_HEIGHT; i++ )
\r
3989 initialPosition[i][j] = s;
\r
3991 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
3992 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
3993 initialPosition[pawnRow][j] = WhitePawn;
\r
3994 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
3995 if(gameInfo.variant == VariantXiangqi) {
\r
3997 initialPosition[pawnRow][j] =
\r
3998 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
3999 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4000 initialPosition[2][j] = WhiteCannon;
\r
4001 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4005 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4007 if( (gameInfo.variant == VariantShogi
\r
4008 ||gameInfo.variant == VariantShowgi
\r
4009 ) && !overrule ) {
\r
4011 initialPosition[1][j] = WhiteBishop;
\r
4012 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4014 initialPosition[1][j] = WhiteRook;
\r
4015 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4018 if( nrCastlingRights == -1) {
\r
4019 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4020 /* This sets default castling rights from none to normal corners */
\r
4021 /* Variants with other castling rights must set them themselves above */
\r
4022 nrCastlingRights = 6;
\r
4024 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4025 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4026 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4027 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4028 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4029 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4031 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4032 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4035 if(gameInfo.variant == VariantFischeRandom) {
\r
4036 if( appData.defaultFrcPosition < 0 ) {
\r
4037 ShuffleFRC( initialPosition );
\r
4040 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4044 CopyBoard(boards[0], initialPosition);
\r
4046 if(oldx != gameInfo.boardWidth ||
\r
4047 oldy != gameInfo.boardHeight ||
\r
4048 oldh != gameInfo.holdingsWidth
\r
4050 || oldv == VariantGothic ||
\r
4051 gameInfo.variant == VariantGothic
\r
4054 InitDrawingSizes(-2 ,0);
\r
4057 DrawPosition(TRUE, boards[currentMove]);
\r
4061 SendBoard(cps, moveNum)
\r
4062 ChessProgramState *cps;
\r
4065 char message[MSG_SIZ];
\r
4067 if (cps->useSetboard) {
\r
4068 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4069 sprintf(message, "setboard %s\n", fen);
\r
4070 SendToProgram(message, cps);
\r
4076 /* Kludge to set black to move, avoiding the troublesome and now
\r
4077 * deprecated "black" command.
\r
4079 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4081 SendToProgram("edit\n", cps);
\r
4082 SendToProgram("#\n", cps);
\r
4083 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4084 bp = &boards[moveNum][i][0];
\r
4085 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4086 if ((int) *bp < (int) BlackPawn) {
\r
4087 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4088 AAA + j, ONE + i);
\r
4089 SendToProgram(message, cps);
\r
4094 SendToProgram("c\n", cps);
\r
4095 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4096 bp = &boards[moveNum][i][0];
\r
4097 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4098 if (((int) *bp != (int) EmptySquare)
\r
4099 && ((int) *bp >= (int) BlackPawn)) {
\r
4100 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4101 AAA + j, ONE + i);
\r
4102 SendToProgram(message, cps);
\r
4107 SendToProgram(".\n", cps);
\r
4112 IsPromotion(fromX, fromY, toX, toY)
\r
4113 int fromX, fromY, toX, toY;
\r
4115 /* [HGM] add Shogi promotions */
\r
4116 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4117 ChessSquare piece;
\r
4119 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4120 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4121 /* [HGM] Note to self: line above also weeds out drops */
\r
4122 piece = boards[currentMove][fromY][fromX];
\r
4123 if(gameInfo.variant == VariantShogi) {
\r
4124 promotionZoneSize = 3;
\r
4125 highestPromotingPiece = (int)WhiteKing;
\r
4126 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4127 and if in normal chess we then allow promotion to King, why not
\r
4128 allow promotion of other piece in Shogi? */
\r
4130 if((int)piece >= BlackPawn) {
\r
4131 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4133 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4135 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4136 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4138 return ( (int)piece <= highestPromotingPiece );
\r
4142 InPalace(row, column)
\r
4144 { /* [HGM] for Xiangqi */
\r
4145 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4146 column < (BOARD_WIDTH + 4)/2 &&
\r
4147 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4152 PieceForSquare (x, y)
\r
4156 if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)
\r
4159 return boards[currentMove][y][x];
\r
4163 OKToStartUserMove(x, y)
\r
4166 ChessSquare from_piece;
\r
4169 if (matchMode) return FALSE;
\r
4170 if (gameMode == EditPosition) return TRUE;
\r
4172 if (x >= 0 && y >= 0)
\r
4173 from_piece = boards[currentMove][y][x];
\r
4175 from_piece = EmptySquare;
\r
4177 if (from_piece == EmptySquare) return FALSE;
\r
4179 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4180 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4182 switch (gameMode) {
\r
4183 case PlayFromGameFile:
\r
4185 case TwoMachinesPlay:
\r
4189 case IcsObserving:
\r
4193 case MachinePlaysWhite:
\r
4194 case IcsPlayingBlack:
\r
4195 if (appData.zippyPlay) return FALSE;
\r
4196 if (white_piece) {
\r
4197 DisplayMoveError("You are playing Black");
\r
4202 case MachinePlaysBlack:
\r
4203 case IcsPlayingWhite:
\r
4204 if (appData.zippyPlay) return FALSE;
\r
4205 if (!white_piece) {
\r
4206 DisplayMoveError("You are playing White");
\r
4212 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4213 DisplayMoveError("It is White's turn");
\r
4216 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4217 DisplayMoveError("It is Black's turn");
\r
4220 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4221 /* Editing correspondence game history */
\r
4222 /* Could disallow this or prompt for confirmation */
\r
4223 cmailOldMove = -1;
\r
4225 if (currentMove < forwardMostMove) {
\r
4226 /* Discarding moves */
\r
4227 /* Could prompt for confirmation here,
\r
4228 but I don't think that's such a good idea */
\r
4229 forwardMostMove = currentMove;
\r
4233 case BeginningOfGame:
\r
4234 if (appData.icsActive) return FALSE;
\r
4235 if (!appData.noChessProgram) {
\r
4236 if (!white_piece) {
\r
4237 DisplayMoveError("You are playing White");
\r
4244 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4245 DisplayMoveError("It is White's turn");
\r
4248 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4249 DisplayMoveError("It is Black's turn");
\r
4255 case IcsExamining:
\r
4258 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4259 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4260 DisplayMoveError("Displayed position is not current");
\r
4266 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4267 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4268 int lastLoadGameUseList = FALSE;
\r
4269 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4270 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4274 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4275 int fromX, fromY, toX, toY;
\r
4278 ChessMove moveType;
\r
4279 ChessSquare pdown, pup;
\r
4281 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4282 if ((fromX == toX) && (fromY == toY)) {
\r
4283 return ImpossibleMove;
\r
4286 /* Check if the user is playing in turn. This is complicated because we
\r
4287 let the user "pick up" a piece before it is his turn. So the piece he
\r
4288 tried to pick up may have been captured by the time he puts it down!
\r
4289 Therefore we use the color the user is supposed to be playing in this
\r
4290 test, not the color of the piece that is currently on the starting
\r
4291 square---except in EditGame mode, where the user is playing both
\r
4292 sides; fortunately there the capture race can't happen. (It can
\r
4293 now happen in IcsExamining mode, but that's just too bad. The user
\r
4294 will get a somewhat confusing message in that case.)
\r
4297 switch (gameMode) {
\r
4298 case PlayFromGameFile:
\r
4300 case TwoMachinesPlay:
\r
4302 case IcsObserving:
\r
4304 /* We switched into a game mode where moves are not accepted,
\r
4305 perhaps while the mouse button was down. */
\r
4306 return ImpossibleMove;
\r
4308 case MachinePlaysWhite:
\r
4309 /* User is moving for Black */
\r
4310 if (WhiteOnMove(currentMove)) {
\r
4311 DisplayMoveError("It is White's turn");
\r
4312 return ImpossibleMove;
\r
4316 case MachinePlaysBlack:
\r
4317 /* User is moving for White */
\r
4318 if (!WhiteOnMove(currentMove)) {
\r
4319 DisplayMoveError("It is Black's turn");
\r
4320 return ImpossibleMove;
\r
4325 case IcsExamining:
\r
4326 case BeginningOfGame:
\r
4329 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4330 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4331 /* User is moving for Black */
\r
4332 if (WhiteOnMove(currentMove)) {
\r
4333 DisplayMoveError("It is White's turn");
\r
4334 return ImpossibleMove;
\r
4337 /* User is moving for White */
\r
4338 if (!WhiteOnMove(currentMove)) {
\r
4339 DisplayMoveError("It is Black's turn");
\r
4340 return ImpossibleMove;
\r
4345 case IcsPlayingBlack:
\r
4346 /* User is moving for Black */
\r
4347 if (WhiteOnMove(currentMove)) {
\r
4348 if (!appData.premove) {
\r
4349 DisplayMoveError("It is White's turn");
\r
4350 } else if (toX >= 0 && toY >= 0) {
\r
4353 premoveFromX = fromX;
\r
4354 premoveFromY = fromY;
\r
4355 premovePromoChar = promoChar;
\r
4357 if (appData.debugMode)
\r
4358 fprintf(debugFP, "Got premove: fromX %d,"
\r
4359 "fromY %d, toX %d, toY %d\n",
\r
4360 fromX, fromY, toX, toY);
\r
4362 return ImpossibleMove;
\r
4366 case IcsPlayingWhite:
\r
4367 /* User is moving for White */
\r
4368 if (!WhiteOnMove(currentMove)) {
\r
4369 if (!appData.premove) {
\r
4370 DisplayMoveError("It is Black's turn");
\r
4371 } else if (toX >= 0 && toY >= 0) {
\r
4374 premoveFromX = fromX;
\r
4375 premoveFromY = fromY;
\r
4376 premovePromoChar = promoChar;
\r
4378 if (appData.debugMode)
\r
4379 fprintf(debugFP, "Got premove: fromX %d,"
\r
4380 "fromY %d, toX %d, toY %d\n",
\r
4381 fromX, fromY, toX, toY);
\r
4383 return ImpossibleMove;
\r
4390 case EditPosition:
\r
4391 /* EditPosition, empty square, or different color piece;
\r
4392 click-click move is possible */
\r
4393 if (toX == -2 || toY == -2) {
\r
4394 boards[0][fromY][fromX] = EmptySquare;
\r
4395 DrawPosition(FALSE, boards[currentMove]);
\r
4396 } else if (toX >= 0 && toY >= 0) {
\r
4397 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4398 boards[0][fromY][fromX] = EmptySquare;
\r
4399 DrawPosition(FALSE, boards[currentMove]);
\r
4401 return ImpossibleMove;
\r
4404 /* [HGM] suppress all moves into holdings area and guard band */
\r
4405 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4406 return ImpossibleMove;
\r
4408 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4409 /* note: EditPosition already filtered out and performed! */
\r
4410 pdown = boards[currentMove][fromY][fromX];
\r
4411 pup = boards[currentMove][toY][toX];
\r
4413 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4414 WhitePawn <= pup && pup < BlackPawn) ||
\r
4415 (BlackPawn <= pdown && pdown < EmptySquare &&
\r
4416 BlackPawn <= pup && pup < EmptySquare) )
\r
4417 return ImpossibleMove;
\r
4419 /* [HGM] If move started in holdings, it means a drop */
\r
4420 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4421 if( pup != EmptySquare ) return ImpossibleMove;
\r
4422 if(appData.testLegality) {
\r
4423 /* it would be more logical if LegalityTest() also figured out
\r
4424 * which drops are legal. For now we forbid pawns on back rank.
\r
4425 * Shogi is on its own here...
\r
4427 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
4428 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
4429 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
4431 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4434 userOfferedDraw = FALSE;
\r
4436 /* [HGM] always test for legality, to get promotion info */
\r
4437 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4438 epStatus[currentMove], castlingRights[currentMove],
\r
4439 fromY, fromX, toY, toX, promoChar);
\r
4441 /* [HGM] but possibly ignore an IllegalMove result */
\r
4442 if (appData.testLegality) {
\r
4443 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4444 DisplayMoveError("Illegal move");
\r
4445 return ImpossibleMove;
\r
4450 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4451 function is made into one that returns an OK move type if FinishMove
\r
4452 should be called. This to give the calling driver routine the
\r
4453 opportunity to finish the userMove input with a promotion popup,
\r
4454 without bothering the user with this for invalid or illegal moves */
\r
4456 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4459 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4461 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4462 ChessMove moveType;
\r
4463 int fromX, fromY, toX, toY;
\r
4464 /*char*/int promoChar;
\r
4466 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4467 move type in caller when we know the move is a legal promotion */
\r
4468 if(moveType == NormalMove)
\r
4469 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4471 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4472 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4473 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4474 fromX = boards[currentMove][fromY][fromX];
\r
4475 fromY = DROP_RANK;
\r
4478 /* [HGM] <popupFix> The following if has been moved here from
\r
4479 UserMoveEnevt(). Because it seemed to belon here (why not allow
\r
4480 piece drops in training games?), and because it can only be
\r
4481 performed after it is known to what we promote. */
\r
4482 if (gameMode == Training) {
\r
4483 /* compare the move played on the board to the next move in the
\r
4484 * game. If they match, display the move and the opponent's response.
\r
4485 * If they don't match, display an error message.
\r
4489 CopyBoard(testBoard, boards[currentMove]);
\r
4490 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4492 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4493 ForwardInner(currentMove+1);
\r
4495 /* Autoplay the opponent's response.
\r
4496 * if appData.animate was TRUE when Training mode was entered,
\r
4497 * the response will be animated.
\r
4499 saveAnimate = appData.animate;
\r
4500 appData.animate = animateTraining;
\r
4501 ForwardInner(currentMove+1);
\r
4502 appData.animate = saveAnimate;
\r
4504 /* check for the end of the game */
\r
4505 if (currentMove >= forwardMostMove) {
\r
4506 gameMode = PlayFromGameFile;
\r
4508 SetTrainingModeOff();
\r
4509 DisplayInformation("End of game");
\r
4512 DisplayError("Incorrect move", 0);
\r
4517 /* Ok, now we know that the move is good, so we can kill
\r
4518 the previous line in Analysis Mode */
\r
4519 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4520 forwardMostMove = currentMove;
\r
4523 /* If we need the chess program but it's dead, restart it */
\r
4524 ResurrectChessProgram();
\r
4526 /* A user move restarts a paused game*/
\r
4530 thinkOutput[0] = NULLCHAR;
\r
4532 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4534 if (gameMode == BeginningOfGame) {
\r
4535 if (appData.noChessProgram) {
\r
4536 gameMode = EditGame;
\r
4539 char buf[MSG_SIZ];
\r
4540 gameMode = MachinePlaysBlack;
\r
4542 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4543 DisplayTitle(buf);
\r
4544 if (first.sendName) {
\r
4545 sprintf(buf, "name %s\n", gameInfo.white);
\r
4546 SendToProgram(buf, &first);
\r
4552 /* Relay move to ICS or chess engine */
\r
4553 if (appData.icsActive) {
\r
4554 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4555 gameMode == IcsExamining) {
\r
4556 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4557 ics_user_moved = 1;
\r
4560 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4561 gameMode == MachinePlaysWhite ||
\r
4562 gameMode == MachinePlaysBlack)) {
\r
4563 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4565 SendMoveToProgram(forwardMostMove-1, &first);
\r
4566 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4567 first.maybeThinking = TRUE;
\r
4569 if (currentMove == cmailOldMove + 1) {
\r
4570 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4574 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4576 switch (gameMode) {
\r
4578 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4579 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4583 case MT_CHECKMATE:
\r
4584 if (WhiteOnMove(currentMove)) {
\r
4585 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4587 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4590 case MT_STALEMATE:
\r
4591 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4596 case MachinePlaysBlack:
\r
4597 case MachinePlaysWhite:
\r
4598 /* disable certain menu options while machine is thinking */
\r
4599 SetMachineThinkingEnables();
\r
4608 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
4609 int fromX, fromY, toX, toY;
\r
4612 /* [HGM] This routine was added to allow calling of its two logical
\r
4613 parts from other modules in the old way. Before, UserMoveEvent()
\r
4614 automatically called FinishMove() if the move was OK, and returned
\r
4615 otherwise. I separated the two, in order to make it possible to
\r
4616 slip a promotion popup in between. But that it always needs two
\r
4617 calls, to the first part, (now called UserMoveTest() ), and to
\r
4618 FinishMove if the first part succeeded. Calls that do not need
\r
4619 to do anything in between, can call this routine the old way.
\r
4621 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
4623 if(moveType != ImpossibleMove)
\r
4624 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4627 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4629 char * hint = lastHint;
\r
4630 FrontEndProgramStats stats;
\r
4632 stats.which = cps == &first ? 0 : 1;
\r
4633 stats.depth = cpstats->depth;
\r
4634 stats.nodes = cpstats->nodes;
\r
4635 stats.score = cpstats->score;
\r
4636 stats.time = cpstats->time;
\r
4637 stats.pv = cpstats->movelist;
\r
4638 stats.hint = lastHint;
\r
4639 stats.an_move_index = 0;
\r
4640 stats.an_move_count = 0;
\r
4642 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4643 stats.hint = cpstats->move_name;
\r
4644 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4645 stats.an_move_count = cpstats->nr_moves;
\r
4648 SetProgramStats( &stats );
\r
4652 HandleMachineMove(message, cps)
\r
4654 ChessProgramState *cps;
\r
4656 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4657 char realname[MSG_SIZ];
\r
4658 int fromX, fromY, toX, toY;
\r
4659 ChessMove moveType;
\r
4665 * Kludge to ignore BEL characters
\r
4667 while (*message == '\007') message++;
\r
4670 * Look for book output
\r
4672 if (cps == &first && bookRequested) {
\r
4673 if (message[0] == '\t' || message[0] == ' ') {
\r
4674 /* Part of the book output is here; append it */
\r
4675 strcat(bookOutput, message);
\r
4676 strcat(bookOutput, " \n");
\r
4678 } else if (bookOutput[0] != NULLCHAR) {
\r
4679 /* All of book output has arrived; display it */
\r
4680 char *p = bookOutput;
\r
4681 while (*p != NULLCHAR) {
\r
4682 if (*p == '\t') *p = ' ';
\r
4685 DisplayInformation(bookOutput);
\r
4686 bookRequested = FALSE;
\r
4687 /* Fall through to parse the current output */
\r
4692 * Look for machine move.
\r
4694 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4695 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4697 /* This method is only useful on engines that support ping */
\r
4698 if (cps->lastPing != cps->lastPong) {
\r
4699 if (gameMode == BeginningOfGame) {
\r
4700 /* Extra move from before last new; ignore */
\r
4701 if (appData.debugMode) {
\r
4702 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4705 if (appData.debugMode) {
\r
4706 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4707 cps->which, gameMode);
\r
4710 SendToProgram("undo\n", cps);
\r
4715 switch (gameMode) {
\r
4716 case BeginningOfGame:
\r
4717 /* Extra move from before last reset; ignore */
\r
4718 if (appData.debugMode) {
\r
4719 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4726 /* Extra move after we tried to stop. The mode test is
\r
4727 not a reliable way of detecting this problem, but it's
\r
4728 the best we can do on engines that don't support ping.
\r
4730 if (appData.debugMode) {
\r
4731 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4732 cps->which, gameMode);
\r
4734 SendToProgram("undo\n", cps);
\r
4737 case MachinePlaysWhite:
\r
4738 case IcsPlayingWhite:
\r
4739 machineWhite = TRUE;
\r
4742 case MachinePlaysBlack:
\r
4743 case IcsPlayingBlack:
\r
4744 machineWhite = FALSE;
\r
4747 case TwoMachinesPlay:
\r
4748 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
4751 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
4752 if (appData.debugMode) {
\r
4754 "Ignoring move out of turn by %s, gameMode %d"
\r
4755 ", forwardMost %d\n",
\r
4756 cps->which, gameMode, forwardMostMove);
\r
4761 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
4762 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4763 /* Machine move could not be parsed; ignore it. */
\r
4764 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
4765 machineMove, cps->which);
\r
4766 DisplayError(buf1, 0);
\r
4767 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
4768 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4769 if (gameMode == TwoMachinesPlay) {
\r
4770 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4776 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
4777 /* So we have to redo legality test with true e.p. status here, */
\r
4778 /* to make sure an illegal e.p. capture does not slip through, */
\r
4779 /* to cause a forfeit on a justified illegal-move complaint */
\r
4780 /* of the opponent. */
\r
4781 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
4782 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
4784 ChessMove moveType;
\r
4785 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
4786 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
4787 fromY, fromX, toY, toX, promoChar);
\r
4788 if (appData.debugMode) {
\r
4790 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
4791 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
4792 fprintf(debugFP, "castling rights\n");
\r
4794 if(moveType == IllegalMove) {
\r
4795 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
4796 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4797 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4799 } else if(gameInfo.variant != VariantFischeRandom)
\r
4800 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
4801 when they shouldn't (like TSCP-Gothic) */
\r
4802 switch(moveType) {
\r
4803 case WhiteASideCastleFR:
\r
4804 case BlackASideCastleFR:
\r
4806 currentMoveString[2]++;
\r
4808 case WhiteHSideCastleFR:
\r
4809 case BlackHSideCastleFR:
\r
4811 currentMoveString[2]--;
\r
4815 hintRequested = FALSE;
\r
4816 lastHint[0] = NULLCHAR;
\r
4817 bookRequested = FALSE;
\r
4818 /* Program may be pondering now */
\r
4819 cps->maybeThinking = TRUE;
\r
4820 if (cps->sendTime == 2) cps->sendTime = 1;
\r
4821 if (cps->offeredDraw) cps->offeredDraw--;
\r
4824 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
4826 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4827 ics_user_moved = 1;
\r
4830 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
4831 strcpy(machineMove, currentMoveString);
\r
4832 strcat(machineMove, "\n");
\r
4833 strcpy(moveList[forwardMostMove], machineMove);
\r
4835 /* [AS] Save move info and clear stats for next move */
\r
4836 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
4837 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
4838 pvInfoList[ forwardMostMove ].time = -1;
\r
4839 ClearProgramStats();
\r
4840 thinkOutput[0] = NULLCHAR;
\r
4841 hiddenThinkOutputState = 0;
\r
4843 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
4845 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
4846 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
4849 while( count < adjudicateLossPlies ) {
\r
4850 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
4853 score = -score; /* Flip score for winning side */
\r
4856 if( score > adjudicateLossThreshold ) {
\r
4863 if( count >= adjudicateLossPlies ) {
\r
4864 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4866 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
4867 "Xboard adjudication",
\r
4874 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
4876 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
4877 int count = 0, epFile = epStatus[forwardMostMove];
\r
4879 if(appData.testLegality && appData.checkMates)
\r
4880 // don't wait for engine to announce game end if we can judge ourselves
\r
4881 switch (MateTest(boards[forwardMostMove],
\r
4882 PosFlags(forwardMostMove), epFile,
\r
4883 castlingRights[forwardMostMove]) ) {
\r
4888 case MT_STALEMATE:
\r
4889 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4890 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
4893 case MT_CHECKMATE:
\r
4894 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4895 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
4896 "Xboard adjudication: Checkmate",
\r
4901 if( appData.testLegality )
\r
4902 { /* [HGM] Some more adjudications for obstinate engines */
\r
4903 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
4905 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
4906 static int moveCount;
\r
4908 /* First absolutely insufficient mating material. Count what is on board. */
\r
4909 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
4910 { ChessSquare p = boards[forwardMostMove][i][j];
\r
4914 { /* count B,N,R and other of each side */
\r
4931 case EmptySquare:
\r
4936 PawnAdvance += m; NrPawns++;
\r
4938 NrPieces += (p != EmptySquare);
\r
4941 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )
\r
4942 { /* KBK, KNK or KK */
\r
4944 /* always flag draws, for judging claims */
\r
4945 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
4947 if(appData.materialDraws) {
\r
4948 /* but only adjudicate them if adjudication enabled */
\r
4949 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4950 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
4955 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
4956 if(NrPieces == 4 &&
\r
4957 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
4958 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
4959 || NrWN==2 || NrBN==2 /* KNNK */
\r
4960 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
4962 if(--moveCount < 0 && appData.trivialDraws)
\r
4963 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
4964 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4965 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
4968 } else moveCount = 6;
\r
4970 if (appData.debugMode) { int i;
\r
4971 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
4972 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
4973 appData.drawRepeats);
\r
4974 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
4975 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
4978 /* Check for rep-draws */
\r
4980 for(k = forwardMostMove-2;
\r
4981 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
4982 epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
4985 if (appData.debugMode) {
\r
4986 fprintf(debugFP, " loop\n");
\r
4988 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
4989 if (appData.debugMode) {
\r
4990 fprintf(debugFP, "match\n");
\r
4992 /* compare castling rights */
\r
4993 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
4994 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
4995 rights++; /* King lost rights, while rook still had them */
\r
4996 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
4997 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
4998 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
4999 rights++; /* but at least one rook lost them */
\r
5001 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5002 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5004 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5005 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5006 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5009 if (appData.debugMode) {
\r
5010 for(i=0; i<nrCastlingRights; i++)
\r
5011 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5014 if (appData.debugMode) {
\r
5015 fprintf(debugFP, " %d %d\n", rights, k);
\r
5017 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5018 && appData.drawRepeats > 1) {
\r
5019 /* adjudicate after user-specified nr of repeats */
\r
5020 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5021 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5024 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5025 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5029 /* Now we test for 50-move draws. Determine ply count */
\r
5030 count = forwardMostMove;
\r
5031 /* look for last irreversble move */
\r
5032 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5034 /* if we hit starting position, add initial plies */
\r
5035 if( count == backwardMostMove )
\r
5036 count -= initialRulePlies;
\r
5037 count = forwardMostMove - count;
\r
5039 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5040 /* this is used to judge if draw claims are legal */
\r
5041 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5042 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5043 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5051 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5052 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5054 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5059 if (gameMode == TwoMachinesPlay) {
\r
5060 if (cps->other->sendTime) {
\r
5061 SendTimeRemaining(cps->other,
\r
5062 cps->other->twoMachinesColor[0] == 'w');
\r
5064 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
5066 firstMove = FALSE;
\r
5067 if (cps->other->useColors) {
\r
5068 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5070 SendToProgram("go\n", cps->other);
\r
5072 cps->other->maybeThinking = TRUE;
\r
5075 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5077 if (!pausing && appData.ringBellAfterMoves) {
\r
5082 * Reenable menu items that were disabled while
\r
5083 * machine was thinking
\r
5085 if (gameMode != TwoMachinesPlay)
\r
5086 SetUserThinkingEnables();
\r
5091 /* Set special modes for chess engines. Later something general
\r
5092 * could be added here; for now there is just one kludge feature,
\r
5093 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5094 * when "xboard" is given as an interactive command.
\r
5096 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5097 cps->useSigint = FALSE;
\r
5098 cps->useSigterm = FALSE;
\r
5101 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5102 * want this, I was asked to put it in, and obliged.
\r
5104 if (!strncmp(message, "setboard ", 9)) {
\r
5105 Board initial_position; int i;
\r
5107 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
5109 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5110 DisplayError("Bad FEN received from engine", 0);
\r
5113 Reset(FALSE, FALSE);
\r
5114 CopyBoard(boards[0], initial_position);
\r
5115 initialRulePlies = FENrulePlies;
\r
5116 epStatus[0] = FENepStatus;
\r
5117 for( i=0; i<nrCastlingRights; i++ )
\r
5118 castlingRights[0][i] = FENcastlingRights[i];
\r
5119 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5120 else gameMode = MachinePlaysBlack;
\r
5121 DrawPosition(FALSE, boards[currentMove]);
\r
5127 * Look for communication commands
\r
5129 if (!strncmp(message, "telluser ", 9)) {
\r
5130 DisplayNote(message + 9);
\r
5133 if (!strncmp(message, "tellusererror ", 14)) {
\r
5134 DisplayError(message + 14, 0);
\r
5137 if (!strncmp(message, "tellopponent ", 13)) {
\r
5138 if (appData.icsActive) {
\r
5140 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5144 DisplayNote(message + 13);
\r
5148 if (!strncmp(message, "tellothers ", 11)) {
\r
5149 if (appData.icsActive) {
\r
5151 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5157 if (!strncmp(message, "tellall ", 8)) {
\r
5158 if (appData.icsActive) {
\r
5160 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5164 DisplayNote(message + 8);
\r
5168 if (strncmp(message, "warning", 7) == 0) {
\r
5169 /* Undocumented feature, use tellusererror in new code */
\r
5170 DisplayError(message, 0);
\r
5173 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5174 strcpy(realname, cps->tidy);
\r
5175 strcat(realname, " query");
\r
5176 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5179 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5180 * sent until we are logged on. Crafty kibitzes have been known to
\r
5181 * interfere with the login process.
\r
5184 if (!strncmp(message, "tellics ", 8)) {
\r
5185 SendToICS(message + 8);
\r
5189 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5190 SendToICS(ics_prefix);
\r
5191 SendToICS(message + 15);
\r
5195 /* The following are for backward compatibility only */
\r
5196 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5197 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5198 SendToICS(ics_prefix);
\r
5199 SendToICS(message);
\r
5204 if (strncmp(message, "feature ", 8) == 0) {
\r
5205 ParseFeatures(message+8, cps);
\r
5207 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5211 * If the move is illegal, cancel it and redraw the board.
\r
5212 * Also deal with other error cases. Matching is rather loose
\r
5213 * here to accommodate engines written before the spec.
\r
5215 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5216 strncmp(message, "Error", 5) == 0) {
\r
5217 if (StrStr(message, "name") ||
\r
5218 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5219 StrStr(message, "result") || StrStr(message, "board") ||
\r
5220 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5221 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5222 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5223 StrStr(message, "accepted")) {
\r
5226 if (StrStr(message, "protover")) {
\r
5227 /* Program is responding to input, so it's apparently done
\r
5228 initializing, and this error message indicates it is
\r
5229 protocol version 1. So we don't need to wait any longer
\r
5230 for it to initialize and send feature commands. */
\r
5231 FeatureDone(cps, 1);
\r
5232 cps->protocolVersion = 1;
\r
5235 cps->maybeThinking = FALSE;
\r
5237 if (StrStr(message, "draw")) {
\r
5238 /* Program doesn't have "draw" command */
\r
5239 cps->sendDrawOffers = 0;
\r
5242 if (cps->sendTime != 1 &&
\r
5243 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5244 /* Program apparently doesn't have "time" or "otim" command */
\r
5245 cps->sendTime = 0;
\r
5248 if (StrStr(message, "analyze")) {
\r
5249 cps->analysisSupport = FALSE;
\r
5250 cps->analyzing = FALSE;
\r
5251 Reset(FALSE, TRUE);
\r
5252 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5253 DisplayError(buf2, 0);
\r
5256 if (StrStr(message, "(no matching move)st")) {
\r
5257 /* Special kludge for GNU Chess 4 only */
\r
5258 cps->stKludge = TRUE;
\r
5259 SendTimeControl(cps, movesPerSession, timeControl,
\r
5260 timeIncrement, appData.searchDepth,
\r
5264 if (StrStr(message, "(no matching move)sd")) {
\r
5265 /* Special kludge for GNU Chess 4 only */
\r
5266 cps->sdKludge = TRUE;
\r
5267 SendTimeControl(cps, movesPerSession, timeControl,
\r
5268 timeIncrement, appData.searchDepth,
\r
5272 if (!StrStr(message, "llegal")) {
\r
5275 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5276 gameMode == IcsIdle) return;
\r
5277 if (forwardMostMove <= backwardMostMove) return;
\r
5279 /* Following removed: it caused a bug where a real illegal move
\r
5280 message in analyze mored would be ignored. */
\r
5281 if (cps == &first && programStats.ok_to_send == 0) {
\r
5282 /* Bogus message from Crafty responding to "." This filtering
\r
5283 can miss some of the bad messages, but fortunately the bug
\r
5284 is fixed in current Crafty versions, so it doesn't matter. */
\r
5288 if (pausing) PauseEvent();
\r
5289 if (gameMode == PlayFromGameFile) {
\r
5290 /* Stop reading this game file */
\r
5291 gameMode = EditGame;
\r
5294 currentMove = --forwardMostMove;
\r
5295 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5297 DisplayBothClocks();
\r
5298 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5299 parseList[currentMove], cps->which);
\r
5300 DisplayMoveError(buf1);
\r
5301 DrawPosition(FALSE, boards[currentMove]);
\r
5303 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5304 /* only passes fully legal moves */
\r
5305 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5306 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5307 "False illegal-move claim", GE_XBOARD );
\r
5311 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5312 /* Program has a broken "time" command that
\r
5313 outputs a string not ending in newline.
\r
5315 cps->sendTime = 0;
\r
5319 * If chess program startup fails, exit with an error message.
\r
5320 * Attempts to recover here are futile.
\r
5322 if ((StrStr(message, "unknown host") != NULL)
\r
5323 || (StrStr(message, "No remote directory") != NULL)
\r
5324 || (StrStr(message, "not found") != NULL)
\r
5325 || (StrStr(message, "No such file") != NULL)
\r
5326 || (StrStr(message, "can't alloc") != NULL)
\r
5327 || (StrStr(message, "Permission denied") != NULL)) {
\r
5329 cps->maybeThinking = FALSE;
\r
5330 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5331 cps->which, cps->program, cps->host, message);
\r
5332 RemoveInputSource(cps->isr);
\r
5333 DisplayFatalError(buf1, 0, 1);
\r
5338 * Look for hint output
\r
5340 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5341 if (cps == &first && hintRequested) {
\r
5342 hintRequested = FALSE;
\r
5343 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5344 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5345 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5346 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5347 fromY, fromX, toY, toX, promoChar, buf1);
\r
5348 sprintf(buf2, "Hint: %s", buf1);
\r
5349 DisplayInformation(buf2);
\r
5351 /* Hint move could not be parsed!? */
\r
5353 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5354 buf1, cps->which);
\r
5355 DisplayError(buf2, 0);
\r
5358 strcpy(lastHint, buf1);
\r
5364 * Ignore other messages if game is not in progress
\r
5366 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5367 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5370 * look for win, lose, draw, or draw offer
\r
5372 if (strncmp(message, "1-0", 3) == 0) {
\r
5373 char *p, *q, *r = "";
\r
5374 p = strchr(message, '{');
\r
5376 q = strchr(p, '}');
\r
5382 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5384 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5385 char *p, *q, *r = "";
\r
5386 p = strchr(message, '{');
\r
5388 q = strchr(p, '}');
\r
5394 /* Kludge for Arasan 4.1 bug */
\r
5395 if (strcmp(r, "Black resigns") == 0) {
\r
5396 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5399 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5401 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5402 char *p, *q, *r = "";
\r
5403 p = strchr(message, '{');
\r
5405 q = strchr(p, '}');
\r
5412 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
5415 } else if (strncmp(message, "White resign", 12) == 0) {
\r
5416 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5418 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
5419 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5421 } else if (strncmp(message, "White", 5) == 0 &&
\r
5422 message[5] != '(' &&
\r
5423 StrStr(message, "Black") == NULL) {
\r
5424 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5426 } else if (strncmp(message, "Black", 5) == 0 &&
\r
5427 message[5] != '(') {
\r
5428 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5430 } else if (strcmp(message, "resign") == 0 ||
\r
5431 strcmp(message, "computer resigns") == 0) {
\r
5432 switch (gameMode) {
\r
5433 case MachinePlaysBlack:
\r
5434 case IcsPlayingBlack:
\r
5435 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5437 case MachinePlaysWhite:
\r
5438 case IcsPlayingWhite:
\r
5439 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5441 case TwoMachinesPlay:
\r
5442 if (cps->twoMachinesColor[0] == 'w')
\r
5443 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5445 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5448 /* can't happen */
\r
5452 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5453 switch (gameMode) {
\r
5454 case MachinePlaysBlack:
\r
5455 case IcsPlayingBlack:
\r
5456 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5458 case MachinePlaysWhite:
\r
5459 case IcsPlayingWhite:
\r
5460 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5462 case TwoMachinesPlay:
\r
5463 if (cps->twoMachinesColor[0] == 'w')
\r
5464 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5466 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5469 /* can't happen */
\r
5473 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5474 switch (gameMode) {
\r
5475 case MachinePlaysBlack:
\r
5476 case IcsPlayingBlack:
\r
5477 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5479 case MachinePlaysWhite:
\r
5480 case IcsPlayingWhite:
\r
5481 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5483 case TwoMachinesPlay:
\r
5484 if (cps->twoMachinesColor[0] == 'w')
\r
5485 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5487 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5490 /* can't happen */
\r
5494 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5495 if (WhiteOnMove(forwardMostMove)) {
\r
5496 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5498 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5501 } else if (strstr(message, "Draw") != NULL ||
\r
5502 strstr(message, "game is a draw") != NULL) {
\r
5503 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5505 } else if (strstr(message, "offer") != NULL &&
\r
5506 strstr(message, "draw") != NULL) {
\r
5508 if (appData.zippyPlay && first.initDone) {
\r
5509 /* Relay offer to ICS */
\r
5510 SendToICS(ics_prefix);
\r
5511 SendToICS("draw\n");
\r
5514 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5515 if (gameMode == TwoMachinesPlay) {
\r
5516 if (cps->other->offeredDraw) {
\r
5517 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5519 if (cps->other->sendDrawOffers) {
\r
5520 SendToProgram("draw\n", cps->other);
\r
5523 } else if (gameMode == MachinePlaysWhite ||
\r
5524 gameMode == MachinePlaysBlack) {
\r
5525 if (userOfferedDraw) {
\r
5526 DisplayInformation("Machine accepts your draw offer");
\r
5527 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5529 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5536 * Look for thinking output
\r
5538 if ( appData.showThinking) {
\r
5539 int plylev, mvleft, mvtot, curscore, time;
\r
5540 char mvname[MOVE_LEN];
\r
5541 unsigned long nodes;
\r
5543 int ignore = FALSE;
\r
5544 int prefixHint = FALSE;
\r
5545 mvname[0] = NULLCHAR;
\r
5547 switch (gameMode) {
\r
5548 case MachinePlaysBlack:
\r
5549 case IcsPlayingBlack:
\r
5550 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5552 case MachinePlaysWhite:
\r
5553 case IcsPlayingWhite:
\r
5554 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5559 case TwoMachinesPlay:
\r
5560 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5570 buf1[0] = NULLCHAR;
\r
5571 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5572 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5574 if (plyext != ' ' && plyext != '\t') {
\r
5578 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5579 if( cps->scoreIsAbsolute &&
\r
5580 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5582 curscore = -curscore;
\r
5586 programStats.depth = plylev;
\r
5587 programStats.nodes = nodes;
\r
5588 programStats.time = time;
\r
5589 programStats.score = curscore;
\r
5590 programStats.got_only_move = 0;
\r
5592 /* Buffer overflow protection */
\r
5593 if (buf1[0] != NULLCHAR) {
\r
5594 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5595 && appData.debugMode) {
\r
5597 "PV is too long; using the first %d bytes.\n",
\r
5598 sizeof(programStats.movelist) - 1);
\r
5601 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5603 sprintf(programStats.movelist, " no PV\n");
\r
5606 if (programStats.seen_stat) {
\r
5607 programStats.ok_to_send = 1;
\r
5610 if (strchr(programStats.movelist, '(') != NULL) {
\r
5611 programStats.line_is_book = 1;
\r
5612 programStats.nr_moves = 0;
\r
5613 programStats.moves_left = 0;
\r
5615 programStats.line_is_book = 0;
\r
5618 SendProgramStatsToFrontend( cps, &programStats );
\r
5621 [AS] Protect the thinkOutput buffer from overflow... this
\r
5622 is only useful if buf1 hasn't overflowed first!
\r
5624 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5626 (gameMode == TwoMachinesPlay ?
\r
5627 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5628 ((double) curscore) / 100.0,
\r
5629 prefixHint ? lastHint : "",
\r
5630 prefixHint ? " " : "" );
\r
5632 if( buf1[0] != NULLCHAR ) {
\r
5633 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5635 if( strlen(buf1) > max_len ) {
\r
5636 if( appData.debugMode) {
\r
5637 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5639 buf1[max_len+1] = '\0';
\r
5642 strcat( thinkOutput, buf1 );
\r
5645 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5646 DisplayMove(currentMove - 1);
\r
5647 DisplayAnalysis();
\r
5651 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
5652 /* crafty (9.25+) says "(only move) <move>"
\r
5653 * if there is only 1 legal move
\r
5655 sscanf(p, "(only move) %s", buf1);
\r
5656 sprintf(thinkOutput, "%s (only move)", buf1);
\r
5657 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
5658 programStats.depth = 1;
\r
5659 programStats.nr_moves = 1;
\r
5660 programStats.moves_left = 1;
\r
5661 programStats.nodes = 1;
\r
5662 programStats.time = 1;
\r
5663 programStats.got_only_move = 1;
\r
5665 /* Not really, but we also use this member to
\r
5666 mean "line isn't going to change" (Crafty
\r
5667 isn't searching, so stats won't change) */
\r
5668 programStats.line_is_book = 1;
\r
5670 SendProgramStatsToFrontend( cps, &programStats );
\r
5672 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5673 DisplayMove(currentMove - 1);
\r
5674 DisplayAnalysis();
\r
5677 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
5678 &time, &nodes, &plylev, &mvleft,
\r
5679 &mvtot, mvname) >= 5) {
\r
5680 /* The stat01: line is from Crafty (9.29+) in response
\r
5681 to the "." command */
\r
5682 programStats.seen_stat = 1;
\r
5683 cps->maybeThinking = TRUE;
\r
5685 if (programStats.got_only_move || !appData.periodicUpdates)
\r
5688 programStats.depth = plylev;
\r
5689 programStats.time = time;
\r
5690 programStats.nodes = nodes;
\r
5691 programStats.moves_left = mvleft;
\r
5692 programStats.nr_moves = mvtot;
\r
5693 strcpy(programStats.move_name, mvname);
\r
5694 programStats.ok_to_send = 1;
\r
5695 programStats.movelist[0] = '\0';
\r
5697 SendProgramStatsToFrontend( cps, &programStats );
\r
5699 DisplayAnalysis();
\r
5702 } else if (strncmp(message,"++",2) == 0) {
\r
5703 /* Crafty 9.29+ outputs this */
\r
5704 programStats.got_fail = 2;
\r
5707 } else if (strncmp(message,"--",2) == 0) {
\r
5708 /* Crafty 9.29+ outputs this */
\r
5709 programStats.got_fail = 1;
\r
5712 } else if (thinkOutput[0] != NULLCHAR &&
\r
5713 strncmp(message, " ", 4) == 0) {
\r
5714 unsigned message_len;
\r
5717 while (*p && *p == ' ') p++;
\r
5719 message_len = strlen( p );
\r
5721 /* [AS] Avoid buffer overflow */
\r
5722 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
5723 strcat(thinkOutput, " ");
\r
5724 strcat(thinkOutput, p);
\r
5727 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
5728 strcat(programStats.movelist, " ");
\r
5729 strcat(programStats.movelist, p);
\r
5732 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5733 DisplayMove(currentMove - 1);
\r
5734 DisplayAnalysis();
\r
5740 buf1[0] = NULLCHAR;
\r
5742 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5743 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
5745 ChessProgramStats cpstats;
\r
5747 if (plyext != ' ' && plyext != '\t') {
\r
5751 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5752 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
5753 curscore = -curscore;
\r
5756 cpstats.depth = plylev;
\r
5757 cpstats.nodes = nodes;
\r
5758 cpstats.time = time;
\r
5759 cpstats.score = curscore;
\r
5760 cpstats.got_only_move = 0;
\r
5761 cpstats.movelist[0] = '\0';
\r
5763 if (buf1[0] != NULLCHAR) {
\r
5764 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
5767 cpstats.ok_to_send = 0;
\r
5768 cpstats.line_is_book = 0;
\r
5769 cpstats.nr_moves = 0;
\r
5770 cpstats.moves_left = 0;
\r
5772 SendProgramStatsToFrontend( cps, &cpstats );
\r
5779 /* Parse a game score from the character string "game", and
\r
5780 record it as the history of the current game. The game
\r
5781 score is NOT assumed to start from the standard position.
\r
5782 The display is not updated in any way.
\r
5785 ParseGameHistory(game)
\r
5788 ChessMove moveType;
\r
5789 int fromX, fromY, toX, toY, boardIndex;
\r
5792 char buf[MSG_SIZ];
\r
5794 if (appData.debugMode)
\r
5795 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
5797 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
5798 gameInfo.site = StrSave(appData.icsHost);
\r
5799 gameInfo.date = PGNDate();
\r
5800 gameInfo.round = StrSave("-");
\r
5802 /* Parse out names of players */
\r
5803 while (*game == ' ') game++;
\r
5805 while (*game != ' ') *p++ = *game++;
\r
5807 gameInfo.white = StrSave(buf);
\r
5808 while (*game == ' ') game++;
\r
5810 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
5812 gameInfo.black = StrSave(buf);
\r
5815 boardIndex = blackPlaysFirst ? 1 : 0;
\r
5818 yyboardindex = boardIndex;
\r
5819 moveType = (ChessMove) yylex();
\r
5820 switch (moveType) {
\r
5822 case WhitePromotionChancellor:
\r
5823 case BlackPromotionChancellor:
\r
5824 case WhitePromotionArchbishop:
\r
5825 case BlackPromotionArchbishop:
\r
5827 case WhitePromotionQueen:
\r
5828 case BlackPromotionQueen:
\r
5829 case WhitePromotionRook:
\r
5830 case BlackPromotionRook:
\r
5831 case WhitePromotionBishop:
\r
5832 case BlackPromotionBishop:
\r
5833 case WhitePromotionKnight:
\r
5834 case BlackPromotionKnight:
\r
5835 case WhitePromotionKing:
\r
5836 case BlackPromotionKing:
\r
5838 case WhiteCapturesEnPassant:
\r
5839 case BlackCapturesEnPassant:
\r
5840 case WhiteKingSideCastle:
\r
5841 case WhiteQueenSideCastle:
\r
5842 case BlackKingSideCastle:
\r
5843 case BlackQueenSideCastle:
\r
5844 case WhiteKingSideCastleWild:
\r
5845 case WhiteQueenSideCastleWild:
\r
5846 case BlackKingSideCastleWild:
\r
5847 case BlackQueenSideCastleWild:
\r
5849 case WhiteHSideCastleFR:
\r
5850 case WhiteASideCastleFR:
\r
5851 case BlackHSideCastleFR:
\r
5852 case BlackASideCastleFR:
\r
5854 case IllegalMove: /* maybe suicide chess, etc. */
\r
5855 fromX = currentMoveString[0] - AAA;
\r
5856 fromY = currentMoveString[1] - ONE;
\r
5857 toX = currentMoveString[2] - AAA;
\r
5858 toY = currentMoveString[3] - ONE;
\r
5859 promoChar = currentMoveString[4];
\r
5863 fromX = moveType == WhiteDrop ?
\r
5864 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
5865 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
5866 fromY = DROP_RANK;
\r
5867 toX = currentMoveString[2] - AAA;
\r
5868 toY = currentMoveString[3] - ONE;
\r
5869 promoChar = NULLCHAR;
\r
5871 case AmbiguousMove:
\r
5873 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
5874 DisplayError(buf, 0);
\r
5876 case ImpossibleMove:
\r
5878 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
5879 DisplayError(buf, 0);
\r
5881 case (ChessMove) 0: /* end of file */
\r
5882 if (boardIndex < backwardMostMove) {
\r
5883 /* Oops, gap. How did that happen? */
\r
5884 DisplayError("Gap in move list", 0);
\r
5887 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5888 if (boardIndex > forwardMostMove) {
\r
5889 forwardMostMove = boardIndex;
\r
5893 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
5894 strcat(parseList[boardIndex-1], " ");
\r
5895 strcat(parseList[boardIndex-1], yy_text);
\r
5907 case GameUnfinished:
\r
5908 if (gameMode == IcsExamining) {
\r
5909 if (boardIndex < backwardMostMove) {
\r
5910 /* Oops, gap. How did that happen? */
\r
5913 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5916 gameInfo.result = moveType;
\r
5917 p = strchr(yy_text, '{');
\r
5918 if (p == NULL) p = strchr(yy_text, '(');
\r
5921 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
5923 q = strchr(p, *p == '{' ? '}' : ')');
\r
5924 if (q != NULL) *q = NULLCHAR;
\r
5927 gameInfo.resultDetails = StrSave(p);
\r
5930 if (boardIndex >= forwardMostMove &&
\r
5931 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
5932 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5935 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
5936 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
5937 parseList[boardIndex]);
\r
5938 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
5939 /* currentMoveString is set as a side-effect of yylex */
\r
5940 strcpy(moveList[boardIndex], currentMoveString);
\r
5941 strcat(moveList[boardIndex], "\n");
\r
5943 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
5944 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
5945 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
5947 case MT_STALEMATE:
\r
5951 if(gameInfo.variant != VariantShogi)
\r
5952 strcat(parseList[boardIndex - 1], "+");
\r
5954 case MT_CHECKMATE:
\r
5955 strcat(parseList[boardIndex - 1], "#");
\r
5962 /* Apply a move to the given board */
\r
5964 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
5965 int fromX, fromY, toX, toY;
\r
5969 ChessSquare captured = board[toY][toX], piece; int p;
\r
5971 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
5972 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
5973 && promoChar != 0) promoChar = 'F';
\r
5975 if (fromX == toX && fromY == toY) return;
\r
5977 if (fromY == DROP_RANK) {
\r
5978 /* must be first */
\r
5979 board[toY][toX] = (ChessSquare) fromX;
\r
5981 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
5983 /* Code added by Tord: */
\r
5984 /* FRC castling assumed when king captures friendly rook. */
\r
5985 if (board[fromY][fromX] == WhiteKing &&
\r
5986 board[toY][toX] == WhiteRook) {
\r
5987 board[fromY][fromX] = EmptySquare;
\r
5988 board[toY][toX] = EmptySquare;
\r
5990 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
5992 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
5994 } else if (board[fromY][fromX] == BlackKing &&
\r
5995 board[toY][toX] == BlackRook) {
\r
5996 board[fromY][fromX] = EmptySquare;
\r
5997 board[toY][toX] = EmptySquare;
\r
5999 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
6001 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
6003 /* End of code added by Tord */
\r
6005 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
6006 && board[fromY][fromX] == WhiteKing
\r
6007 && toY == fromY && toX > fromX+1) {
\r
6008 board[fromY][fromX] = EmptySquare;
\r
6009 board[toY][toX] = WhiteKing;
\r
6010 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6011 board[toY][toX-1] = WhiteRook;
\r
6012 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
6013 && board[fromY][fromX] == WhiteKing
\r
6014 && toY == fromY && toX < fromX-1) {
\r
6015 board[fromY][fromX] = EmptySquare;
\r
6016 board[toY][toX] = WhiteKing;
\r
6017 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6018 board[toY][toX+1] = WhiteRook;
\r
6019 } else if (fromY == 0 && fromX == 3
\r
6020 && board[fromY][fromX] == WhiteKing
\r
6021 && toY == 0 && toX == 5) {
\r
6022 board[fromY][fromX] = EmptySquare;
\r
6023 board[toY][toX] = WhiteKing;
\r
6024 board[fromY][7] = EmptySquare;
\r
6025 board[toY][4] = WhiteRook;
\r
6026 } else if (fromY == 0 && fromX == 3
\r
6027 && board[fromY][fromX] == WhiteKing
\r
6028 && toY == 0 && toX == 1) {
\r
6029 board[fromY][fromX] = EmptySquare;
\r
6030 board[toY][toX] = WhiteKing;
\r
6031 board[fromY][0] = EmptySquare;
\r
6032 board[toY][2] = WhiteRook;
\r
6033 } else if (board[fromY][fromX] == WhitePawn
\r
6034 && toY == BOARD_HEIGHT-1
\r
6035 && gameInfo.variant != VariantXiangqi
\r
6037 /* white pawn promotion */
\r
6038 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6039 if (board[toY][toX] == EmptySquare) {
\r
6040 board[toY][toX] = WhiteQueen;
\r
6042 if(gameInfo.variant==VariantBughouse ||
\r
6043 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6044 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6045 board[fromY][fromX] = EmptySquare;
\r
6046 } else if ((fromY == BOARD_HEIGHT-4)
\r
6048 && (board[fromY][fromX] == WhitePawn)
\r
6049 && (board[toY][toX] == EmptySquare)) {
\r
6050 board[fromY][fromX] = EmptySquare;
\r
6051 board[toY][toX] = WhitePawn;
\r
6052 captured = board[toY - 1][toX];
\r
6053 board[toY - 1][toX] = EmptySquare;
\r
6054 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6055 && board[fromY][fromX] == BlackKing
\r
6056 && toY == fromY && toX > fromX+1) {
\r
6057 board[fromY][fromX] = EmptySquare;
\r
6058 board[toY][toX] = BlackKing;
\r
6059 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6060 board[toY][toX-1] = BlackRook;
\r
6061 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6062 && board[fromY][fromX] == BlackKing
\r
6063 && toY == fromY && toX < fromX-1) {
\r
6064 board[fromY][fromX] = EmptySquare;
\r
6065 board[toY][toX] = BlackKing;
\r
6066 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6067 board[toY][toX+1] = BlackRook;
\r
6068 } else if (fromY == 7 && fromX == 3
\r
6069 && board[fromY][fromX] == BlackKing
\r
6070 && toY == 7 && toX == 5) {
\r
6071 board[fromY][fromX] = EmptySquare;
\r
6072 board[toY][toX] = BlackKing;
\r
6073 board[fromY][7] = EmptySquare;
\r
6074 board[toY][4] = BlackRook;
\r
6075 } else if (fromY == 7 && fromX == 3
\r
6076 && board[fromY][fromX] == BlackKing
\r
6077 && toY == 7 && toX == 1) {
\r
6078 board[fromY][fromX] = EmptySquare;
\r
6079 board[toY][toX] = BlackKing;
\r
6080 board[fromY][0] = EmptySquare;
\r
6081 board[toY][2] = BlackRook;
\r
6082 } else if (board[fromY][fromX] == BlackPawn
\r
6084 && gameInfo.variant != VariantXiangqi
\r
6086 /* black pawn promotion */
\r
6087 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6088 if (board[0][toX] == EmptySquare) {
\r
6089 board[0][toX] = BlackQueen;
\r
6091 if(gameInfo.variant==VariantBughouse ||
\r
6092 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6093 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6094 board[fromY][fromX] = EmptySquare;
\r
6095 } else if ((fromY == 3)
\r
6097 && (board[fromY][fromX] == BlackPawn)
\r
6098 && (board[toY][toX] == EmptySquare)) {
\r
6099 board[fromY][fromX] = EmptySquare;
\r
6100 board[toY][toX] = BlackPawn;
\r
6101 captured = board[toY + 1][toX];
\r
6102 board[toY + 1][toX] = EmptySquare;
\r
6104 board[toY][toX] = board[fromY][fromX];
\r
6105 board[fromY][fromX] = EmptySquare;
\r
6108 /* [HGM] now we promote for Shogi, if needed */
\r
6109 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
6110 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6113 if (gameInfo.holdingsWidth != 0) {
\r
6115 /* !!A lot more code needs to be written to support holdings */
\r
6116 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
6117 /* penultimate board files, so they are automaticlly stored */
\r
6118 /* in the game history. */
\r
6119 if (fromY == DROP_RANK) {
\r
6120 /* Delete from holdings, by decreasing count */
\r
6121 /* and erasing image if necessary */
\r
6123 if(p < (int) BlackPawn) { /* white drop */
\r
6124 p -= (int)WhitePawn;
\r
6125 if(p >= gameInfo.holdingsSize) p = 0;
\r
6126 if(--board[p][BOARD_WIDTH-2] == 0)
\r
6127 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
6128 } else { /* black drop */
\r
6129 p -= (int)BlackPawn;
\r
6130 if(p >= gameInfo.holdingsSize) p = 0;
\r
6131 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
6132 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
6135 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
6136 && gameInfo.variant != VariantBughouse ) {
\r
6137 /* Add to holdings, if holdings exist */
\r
6138 p = (int) captured;
\r
6139 if (p >= (int) BlackPawn) {
\r
6140 p -= (int)BlackPawn;
\r
6141 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6142 /* in Shogi restore piece to its original first */
\r
6143 captured = (ChessSquare) (DEMOTED captured);
\r
6146 p = PieceToNumber((ChessSquare)p);
\r
6147 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
6148 board[p][BOARD_WIDTH-2]++;
\r
6149 board[p][BOARD_WIDTH-1] =
\r
6150 BLACK_TO_WHITE captured;
\r
6152 p -= (int)WhitePawn;
\r
6153 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6154 captured = (ChessSquare) (DEMOTED captured);
\r
6157 p = PieceToNumber((ChessSquare)p);
\r
6158 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
6159 board[BOARD_HEIGHT-1-p][1]++;
\r
6160 board[BOARD_HEIGHT-1-p][0] =
\r
6161 WHITE_TO_BLACK captured;
\r
6165 } else if (gameInfo.variant == VariantAtomic) {
\r
6166 if (captured != EmptySquare) {
\r
6168 for (y = toY-1; y <= toY+1; y++) {
\r
6169 for (x = toX-1; x <= toX+1; x++) {
\r
6170 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
6171 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
6172 board[y][x] = EmptySquare;
\r
6176 board[toY][toX] = EmptySquare;
\r
6179 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
6180 /* [HGM] Shogi promotions */
\r
6181 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6186 /* Updates forwardMostMove */
\r
6188 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6189 int fromX, fromY, toX, toY;
\r
6192 forwardMostMove++;
\r
6194 if (forwardMostMove >= MAX_MOVES) {
\r
6195 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6200 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6201 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6202 if (commentList[forwardMostMove] != NULL) {
\r
6203 free(commentList[forwardMostMove]);
\r
6204 commentList[forwardMostMove] = NULL;
\r
6206 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6207 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6210 epStatus[forwardMostMove] = EP_NONE;
\r
6212 if( boards[forwardMostMove][toY][toX] != EmptySquare )
\r
6213 epStatus[forwardMostMove] = EP_CAPTURE;
\r
6215 if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {
\r
6216 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6217 if( toY-fromY==2 &&
\r
6218 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||
\r
6219 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )
\r
6220 epStatus[forwardMostMove] = toX;
\r
6222 if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {
\r
6223 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6224 if( toY-fromY== -2 &&
\r
6225 (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||
\r
6226 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )
\r
6227 epStatus[forwardMostMove] = toX;
\r
6230 for(i=0; i<nrCastlingRights; i++) {
\r
6231 castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];
\r
6232 if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||
\r
6233 castlingRights[forwardMostMove][i] == toX && castlingRank[i] == toY
\r
6234 ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece
\r
6239 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6240 gameInfo.result = GameUnfinished;
\r
6241 if (gameInfo.resultDetails != NULL) {
\r
6242 free(gameInfo.resultDetails);
\r
6243 gameInfo.resultDetails = NULL;
\r
6245 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6246 moveList[forwardMostMove - 1]);
\r
6247 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6248 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6249 fromY, fromX, toY, toX, promoChar,
\r
6250 parseList[forwardMostMove - 1]);
\r
6251 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6252 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6253 castlingRights[forwardMostMove]) ) {
\r
6255 case MT_STALEMATE:
\r
6259 if(gameInfo.variant != VariantShogi)
\r
6260 strcat(parseList[forwardMostMove - 1], "+");
\r
6262 case MT_CHECKMATE:
\r
6263 strcat(parseList[forwardMostMove - 1], "#");
\r
6266 if (appData.debugMode) {
\r
6267 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
6272 /* Updates currentMove if not pausing */
\r
6274 ShowMove(fromX, fromY, toX, toY)
\r
6276 int instant = (gameMode == PlayFromGameFile) ?
\r
6277 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6278 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6280 if (forwardMostMove == currentMove + 1) {
\r
6281 AnimateMove(boards[forwardMostMove - 1],
\r
6282 fromX, fromY, toX, toY);
\r
6284 if (appData.highlightLastMove) {
\r
6285 SetHighlights(fromX, fromY, toX, toY);
\r
6288 currentMove = forwardMostMove;
\r
6291 if (instant) return;
\r
6293 DisplayMove(currentMove - 1);
\r
6294 DrawPosition(FALSE, boards[currentMove]);
\r
6295 DisplayBothClocks();
\r
6296 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6301 InitChessProgram(cps)
\r
6302 ChessProgramState *cps;
\r
6304 char buf[MSG_SIZ], *b; int overruled;
\r
6305 if (appData.noChessProgram) return;
\r
6306 hintRequested = FALSE;
\r
6307 bookRequested = FALSE;
\r
6308 SendToProgram(cps->initString, cps);
\r
6309 if (gameInfo.variant != VariantNormal &&
\r
6310 gameInfo.variant != VariantLoadable
\r
6311 /* [HGM] also send variant if board size non-standard */
\r
6312 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8
\r
6314 char *v = VariantName(gameInfo.variant);
\r
6315 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
6316 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
6317 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
6318 DisplayFatalError(buf, 0, 1);
\r
6322 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
6323 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6324 if( gameInfo.variant == VariantXiangqi )
\r
6325 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
6326 if( gameInfo.variant == VariantShogi )
\r
6327 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
6328 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
6329 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
6330 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )
\r
6331 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6332 if( gameInfo.variant == VariantCourier )
\r
6333 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6336 if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {
\r
6337 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
6338 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
6339 DisplayFatalError(buf, 0, 1);
\r
6342 /* [HGM] here we really should compare with the maximum supported board size */
\r
6343 sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,
\r
6344 gameInfo.boardHeight, gameInfo.holdingsSize );
\r
6345 while(*b++ != '_');
\r
6347 sprintf(b, "variant %s\n", VariantName(gameInfo.variant));
\r
6348 SendToProgram(buf, cps);
\r
6350 if (cps->sendICS) {
\r
6351 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
6352 SendToProgram(buf, cps);
\r
6354 cps->maybeThinking = FALSE;
\r
6355 cps->offeredDraw = 0;
\r
6356 if (!appData.icsActive) {
\r
6357 SendTimeControl(cps, movesPerSession, timeControl,
\r
6358 timeIncrement, appData.searchDepth,
\r
6361 if (appData.showThinking) {
\r
6362 SendToProgram("post\n", cps);
\r
6364 SendToProgram("hard\n", cps);
\r
6365 if (!appData.ponderNextMove) {
\r
6366 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
6367 it without being sure what state we are in first. "hard"
\r
6368 is not a toggle, so that one is OK.
\r
6370 SendToProgram("easy\n", cps);
\r
6372 if (cps->usePing) {
\r
6373 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
6374 SendToProgram(buf, cps);
\r
6376 cps->initDone = TRUE;
\r
6381 StartChessProgram(cps)
\r
6382 ChessProgramState *cps;
\r
6384 char buf[MSG_SIZ];
\r
6387 if (appData.noChessProgram) return;
\r
6388 cps->initDone = FALSE;
\r
6390 if (strcmp(cps->host, "localhost") == 0) {
\r
6391 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
6392 } else if (*appData.remoteShell == NULLCHAR) {
\r
6393 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
6395 if (*appData.remoteUser == NULLCHAR) {
\r
6396 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
6399 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
6400 cps->host, appData.remoteUser, cps->program);
\r
6402 err = StartChildProcess(buf, "", &cps->pr);
\r
6406 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
6407 DisplayFatalError(buf, err, 1);
\r
6413 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
6414 if (cps->protocolVersion > 1) {
\r
6415 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
6416 SendToProgram(buf, cps);
\r
6418 SendToProgram("xboard\n", cps);
\r
6424 TwoMachinesEventIfReady P((void))
\r
6426 if (first.lastPing != first.lastPong) {
\r
6427 DisplayMessage("", "Waiting for first chess program");
\r
6428 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6431 if (second.lastPing != second.lastPong) {
\r
6432 DisplayMessage("", "Waiting for second chess program");
\r
6433 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6437 TwoMachinesEvent();
\r
6441 NextMatchGame P((void))
\r
6443 Reset(FALSE, TRUE);
\r
6444 if (*appData.loadGameFile != NULLCHAR) {
\r
6445 LoadGameFromFile(appData.loadGameFile,
\r
6446 appData.loadGameIndex,
\r
6447 appData.loadGameFile, FALSE);
\r
6448 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
6449 LoadPositionFromFile(appData.loadPositionFile,
\r
6450 appData.loadPositionIndex,
\r
6451 appData.loadPositionFile);
\r
6453 TwoMachinesEventIfReady();
\r
6456 void UserAdjudicationEvent( int result )
\r
6458 ChessMove gameResult = GameIsDrawn;
\r
6460 if( result > 0 ) {
\r
6461 gameResult = WhiteWins;
\r
6463 else if( result < 0 ) {
\r
6464 gameResult = BlackWins;
\r
6467 if( gameMode == TwoMachinesPlay ) {
\r
6468 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
6474 GameEnds(result, resultDetails, whosays)
\r
6476 char *resultDetails;
\r
6479 GameMode nextGameMode;
\r
6481 char buf[MSG_SIZ];
\r
6483 if (appData.debugMode) {
\r
6484 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
6485 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6488 if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
6489 /* If we are playing on ICS, the server decides when the
\r
6490 game is over, but the engine can offer to draw, claim
\r
6491 a draw, or resign.
\r
6494 if (appData.zippyPlay && first.initDone) {
\r
6495 if (result == GameIsDrawn) {
\r
6496 /* In case draw still needs to be claimed */
\r
6497 SendToICS(ics_prefix);
\r
6498 SendToICS("draw\n");
\r
6499 } else if (StrCaseStr(resultDetails, "resign")) {
\r
6500 SendToICS(ics_prefix);
\r
6501 SendToICS("resign\n");
\r
6508 /* If we're loading the game from a file, stop */
\r
6509 if (whosays == GE_FILE) {
\r
6510 (void) StopLoadGameTimer();
\r
6511 gameFileFP = NULL;
\r
6514 /* Cancel draw offers */
\r
6515 first.offeredDraw = second.offeredDraw = 0;
\r
6517 /* If this is an ICS game, only ICS can really say it's done;
\r
6518 if not, anyone can. */
\r
6519 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6520 gameMode == IcsPlayingBlack ||
\r
6521 gameMode == IcsObserving ||
\r
6522 gameMode == IcsExamining);
\r
6524 if (!isIcsGame || whosays == GE_ICS) {
\r
6525 /* OK -- not an ICS game, or ICS said it was done */
\r
6527 if (appData.debugMode) {
\r
6528 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6529 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6531 if (!isIcsGame && !appData.noChessProgram)
\r
6532 SetUserThinkingEnables();
\r
6534 /* [HGM] if a machine claims the game end we verify this claim */
\r
6535 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6536 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6539 if (appData.debugMode) {
\r
6540 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6541 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6543 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6544 first.twoMachinesColor[0] :
\r
6545 second.twoMachinesColor[0] ;
\r
6546 if( result == WhiteWins && claimer == 'w' ||
\r
6547 result == BlackWins && claimer == 'b' ) {
\r
6548 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6549 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6550 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6551 resultDetails = buf;
\r
6553 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {
\r
6554 /* Draw that was not flagged by Xboard is false */
\r
6555 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6556 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6557 resultDetails = buf;
\r
6559 /* (Claiming a loss is accepted no questions asked!) */
\r
6562 if (appData.debugMode) {
\r
6563 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6564 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6566 if (resultDetails != NULL) {
\r
6567 gameInfo.result = result;
\r
6568 gameInfo.resultDetails = StrSave(resultDetails);
\r
6570 /* Tell program how game ended in case it is learning */
\r
6571 if (gameMode == MachinePlaysWhite ||
\r
6572 gameMode == MachinePlaysBlack ||
\r
6573 gameMode == TwoMachinesPlay ||
\r
6574 gameMode == IcsPlayingWhite ||
\r
6575 gameMode == IcsPlayingBlack ||
\r
6576 gameMode == BeginningOfGame) {
\r
6577 char buf[MSG_SIZ];
\r
6578 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
6580 if (first.pr != NoProc) {
\r
6581 SendToProgram(buf, &first);
\r
6583 if (second.pr != NoProc &&
\r
6584 gameMode == TwoMachinesPlay) {
\r
6585 SendToProgram(buf, &second);
\r
6589 /* display last move only if game was not loaded from file */
\r
6590 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
6591 DisplayMove(currentMove - 1);
\r
6593 if (forwardMostMove != 0) {
\r
6594 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
6595 if (*appData.saveGameFile != NULLCHAR) {
\r
6596 SaveGameToFile(appData.saveGameFile, TRUE);
\r
6597 } else if (appData.autoSaveGames) {
\r
6600 if (*appData.savePositionFile != NULLCHAR) {
\r
6601 SavePositionToFile(appData.savePositionFile);
\r
6607 if (appData.icsActive) {
\r
6608 if (appData.quietPlay &&
\r
6609 (gameMode == IcsPlayingWhite ||
\r
6610 gameMode == IcsPlayingBlack)) {
\r
6611 SendToICS(ics_prefix);
\r
6612 SendToICS("set shout 1\n");
\r
6614 nextGameMode = IcsIdle;
\r
6615 ics_user_moved = FALSE;
\r
6616 /* clean up premove. It's ugly when the game has ended and the
\r
6617 * premove highlights are still on the board.
\r
6620 gotPremove = FALSE;
\r
6621 ClearPremoveHighlights();
\r
6622 DrawPosition(FALSE, boards[currentMove]);
\r
6624 if (whosays == GE_ICS) {
\r
6627 if (gameMode == IcsPlayingWhite)
\r
6628 PlayIcsWinSound();
\r
6629 else if(gameMode == IcsPlayingBlack)
\r
6630 PlayIcsLossSound();
\r
6633 if (gameMode == IcsPlayingBlack)
\r
6634 PlayIcsWinSound();
\r
6635 else if(gameMode == IcsPlayingWhite)
\r
6636 PlayIcsLossSound();
\r
6639 PlayIcsDrawSound();
\r
6642 PlayIcsUnfinishedSound();
\r
6645 } else if (gameMode == EditGame ||
\r
6646 gameMode == PlayFromGameFile ||
\r
6647 gameMode == AnalyzeMode ||
\r
6648 gameMode == AnalyzeFile) {
\r
6649 nextGameMode = gameMode;
\r
6651 nextGameMode = EndOfGame;
\r
6656 nextGameMode = gameMode;
\r
6659 if (appData.noChessProgram) {
\r
6660 gameMode = nextGameMode;
\r
6665 if (first.reuse) {
\r
6666 /* Put first chess program into idle state */
\r
6667 if (first.pr != NoProc &&
\r
6668 (gameMode == MachinePlaysWhite ||
\r
6669 gameMode == MachinePlaysBlack ||
\r
6670 gameMode == TwoMachinesPlay ||
\r
6671 gameMode == IcsPlayingWhite ||
\r
6672 gameMode == IcsPlayingBlack ||
\r
6673 gameMode == BeginningOfGame)) {
\r
6674 SendToProgram("force\n", &first);
\r
6675 if (first.usePing) {
\r
6676 char buf[MSG_SIZ];
\r
6677 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
6678 SendToProgram(buf, &first);
\r
6681 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6682 /* Kill off first chess program */
\r
6683 if (first.isr != NULL)
\r
6684 RemoveInputSource(first.isr);
\r
6687 if (first.pr != NoProc) {
\r
6688 ExitAnalyzeMode();
\r
6689 DoSleep( appData.delayBeforeQuit );
\r
6690 SendToProgram("quit\n", &first);
\r
6691 DoSleep( appData.delayAfterQuit );
\r
6692 DestroyChildProcess(first.pr, first.useSigterm);
\r
6694 first.pr = NoProc;
\r
6696 if (second.reuse) {
\r
6697 /* Put second chess program into idle state */
\r
6698 if (second.pr != NoProc &&
\r
6699 gameMode == TwoMachinesPlay) {
\r
6700 SendToProgram("force\n", &second);
\r
6701 if (second.usePing) {
\r
6702 char buf[MSG_SIZ];
\r
6703 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
6704 SendToProgram(buf, &second);
\r
6707 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6708 /* Kill off second chess program */
\r
6709 if (second.isr != NULL)
\r
6710 RemoveInputSource(second.isr);
\r
6711 second.isr = NULL;
\r
6713 if (second.pr != NoProc) {
\r
6714 DoSleep( appData.delayBeforeQuit );
\r
6715 SendToProgram("quit\n", &second);
\r
6716 DoSleep( appData.delayAfterQuit );
\r
6717 DestroyChildProcess(second.pr, second.useSigterm);
\r
6719 second.pr = NoProc;
\r
6722 if (matchMode && gameMode == TwoMachinesPlay) {
\r
6725 if (first.twoMachinesColor[0] == 'w') {
\r
6726 first.matchWins++;
\r
6728 second.matchWins++;
\r
6732 if (first.twoMachinesColor[0] == 'b') {
\r
6733 first.matchWins++;
\r
6735 second.matchWins++;
\r
6741 if (matchGame < appData.matchGames) {
\r
6743 tmp = first.twoMachinesColor;
\r
6744 first.twoMachinesColor = second.twoMachinesColor;
\r
6745 second.twoMachinesColor = tmp;
\r
6746 gameMode = nextGameMode;
\r
6748 if(appData.matchPause>10000 || appData.matchPause<10)
\r
6749 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
6750 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
6753 char buf[MSG_SIZ];
\r
6754 gameMode = nextGameMode;
\r
6755 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
6756 first.tidy, second.tidy,
\r
6757 first.matchWins, second.matchWins,
\r
6758 appData.matchGames - (first.matchWins + second.matchWins));
\r
6759 DisplayFatalError(buf, 0, 0);
\r
6762 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6763 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
6764 ExitAnalyzeMode();
\r
6765 gameMode = nextGameMode;
\r
6769 /* Assumes program was just initialized (initString sent).
\r
6770 Leaves program in force mode. */
\r
6772 FeedMovesToProgram(cps, upto)
\r
6773 ChessProgramState *cps;
\r
6778 if (appData.debugMode)
\r
6779 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
6780 startedFromSetupPosition ? "position and " : "",
\r
6781 backwardMostMove, upto, cps->which);
\r
6782 SendToProgram("force\n", cps);
\r
6783 if (startedFromSetupPosition) {
\r
6784 SendBoard(cps, backwardMostMove);
\r
6786 for (i = backwardMostMove; i < upto; i++) {
\r
6787 SendMoveToProgram(i, cps);
\r
6793 ResurrectChessProgram()
\r
6795 /* The chess program may have exited.
\r
6796 If so, restart it and feed it all the moves made so far. */
\r
6798 if (appData.noChessProgram || first.pr != NoProc) return;
\r
6800 StartChessProgram(&first);
\r
6801 InitChessProgram(&first);
\r
6802 FeedMovesToProgram(&first, currentMove);
\r
6804 if (!first.sendTime) {
\r
6805 /* can't tell gnuchess what its clock should read,
\r
6806 so we bow to its notion. */
\r
6808 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
6809 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
6812 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6813 first.analysisSupport) {
\r
6814 SendToProgram("analyze\n", &first);
\r
6815 first.analyzing = TRUE;
\r
6820 * Button procedures
\r
6823 Reset(redraw, init)
\r
6828 if (appData.debugMode) {
\r
6829 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
6830 redraw, init, gameMode);
\r
6833 pausing = pauseExamInvalid = FALSE;
\r
6834 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
6836 whiteFlag = blackFlag = FALSE;
\r
6837 userOfferedDraw = FALSE;
\r
6838 hintRequested = bookRequested = FALSE;
\r
6839 first.maybeThinking = FALSE;
\r
6840 second.maybeThinking = FALSE;
\r
6841 thinkOutput[0] = NULLCHAR;
\r
6842 lastHint[0] = NULLCHAR;
\r
6843 ClearGameInfo(&gameInfo);
\r
6844 gameInfo.variant = StringToVariant(appData.variant);
\r
6845 ics_user_moved = ics_clock_paused = FALSE;
\r
6846 ics_getting_history = H_FALSE;
\r
6848 white_holding[0] = black_holding[0] = NULLCHAR;
\r
6849 ClearProgramStats();
\r
6852 ClearHighlights();
\r
6853 flipView = appData.flipView;
\r
6854 ClearPremoveHighlights();
\r
6855 gotPremove = FALSE;
\r
6856 alarmSounded = FALSE;
\r
6858 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
6859 ExitAnalyzeMode();
\r
6860 gameMode = BeginningOfGame;
\r
6862 InitPosition(redraw);
\r
6863 for (i = 0; i < MAX_MOVES; i++) {
\r
6864 if (commentList[i] != NULL) {
\r
6865 free(commentList[i]);
\r
6866 commentList[i] = NULL;
\r
6870 timeRemaining[0][0] = whiteTimeRemaining;
\r
6871 timeRemaining[1][0] = blackTimeRemaining;
\r
6872 if (first.pr == NULL) {
\r
6873 StartChessProgram(&first);
\r
6875 if (init) InitChessProgram(&first);
\r
6877 DisplayMessage("", "");
\r
6878 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
6882 AutoPlayGameLoop()
\r
6885 if (!AutoPlayOneMove())
\r
6887 if (matchMode || appData.timeDelay == 0)
\r
6889 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
6891 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
6900 int fromX, fromY, toX, toY;
\r
6902 if (appData.debugMode) {
\r
6903 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
6906 if (gameMode != PlayFromGameFile)
\r
6909 if (currentMove >= forwardMostMove) {
\r
6910 gameMode = EditGame;
\r
6913 /* [AS] Clear current move marker at the end of a game */
\r
6914 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
6919 toX = moveList[currentMove][2] - AAA;
\r
6920 toY = moveList[currentMove][3] - ONE;
\r
6922 if (moveList[currentMove][1] == '@') {
\r
6923 if (appData.highlightLastMove) {
\r
6924 SetHighlights(-1, -1, toX, toY);
\r
6927 fromX = moveList[currentMove][0] - AAA;
\r
6928 fromY = moveList[currentMove][1] - ONE;
\r
6930 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
6932 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
6934 if (appData.highlightLastMove) {
\r
6935 SetHighlights(fromX, fromY, toX, toY);
\r
6938 DisplayMove(currentMove);
\r
6939 SendMoveToProgram(currentMove++, &first);
\r
6940 DisplayBothClocks();
\r
6941 DrawPosition(FALSE, boards[currentMove]);
\r
6942 if (commentList[currentMove] != NULL) {
\r
6943 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6950 LoadGameOneMove(readAhead)
\r
6951 ChessMove readAhead;
\r
6953 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
6954 char promoChar = NULLCHAR;
\r
6955 ChessMove moveType;
\r
6956 char move[MSG_SIZ];
\r
6959 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
6960 gameMode != AnalyzeMode && gameMode != Training) {
\r
6961 gameFileFP = NULL;
\r
6965 yyboardindex = forwardMostMove;
\r
6966 if (readAhead != (ChessMove)0) {
\r
6967 moveType = readAhead;
\r
6969 if (gameFileFP == NULL)
\r
6971 moveType = (ChessMove) yylex();
\r
6975 switch (moveType) {
\r
6977 if (appData.debugMode)
\r
6978 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
6980 if (*p == '{' || *p == '[' || *p == '(') {
\r
6981 p[strlen(p) - 1] = NULLCHAR;
\r
6985 /* append the comment but don't display it */
\r
6986 while (*p == '\n') p++;
\r
6987 AppendComment(currentMove, p);
\r
6990 case WhiteCapturesEnPassant:
\r
6991 case BlackCapturesEnPassant:
\r
6992 case WhitePromotionChancellor:
\r
6993 case BlackPromotionChancellor:
\r
6994 case WhitePromotionArchbishop:
\r
6995 case BlackPromotionArchbishop:
\r
6996 case WhitePromotionQueen:
\r
6997 case BlackPromotionQueen:
\r
6998 case WhitePromotionRook:
\r
6999 case BlackPromotionRook:
\r
7000 case WhitePromotionBishop:
\r
7001 case BlackPromotionBishop:
\r
7002 case WhitePromotionKnight:
\r
7003 case BlackPromotionKnight:
\r
7004 case WhitePromotionKing:
\r
7005 case BlackPromotionKing:
\r
7007 case WhiteKingSideCastle:
\r
7008 case WhiteQueenSideCastle:
\r
7009 case BlackKingSideCastle:
\r
7010 case BlackQueenSideCastle:
\r
7011 case WhiteKingSideCastleWild:
\r
7012 case WhiteQueenSideCastleWild:
\r
7013 case BlackKingSideCastleWild:
\r
7014 case BlackQueenSideCastleWild:
\r
7016 case WhiteHSideCastleFR:
\r
7017 case WhiteASideCastleFR:
\r
7018 case BlackHSideCastleFR:
\r
7019 case BlackASideCastleFR:
\r
7021 if (appData.debugMode)
\r
7022 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7023 fromX = currentMoveString[0] - AAA;
\r
7024 fromY = currentMoveString[1] - ONE;
\r
7025 toX = currentMoveString[2] - AAA;
\r
7026 toY = currentMoveString[3] - ONE;
\r
7027 promoChar = currentMoveString[4];
\r
7032 if (appData.debugMode)
\r
7033 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7034 fromX = moveType == WhiteDrop ?
\r
7035 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
7036 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
7037 fromY = DROP_RANK;
\r
7038 toX = currentMoveString[2] - AAA;
\r
7039 toY = currentMoveString[3] - ONE;
\r
7045 case GameUnfinished:
\r
7046 if (appData.debugMode)
\r
7047 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
7048 p = strchr(yy_text, '{');
\r
7049 if (p == NULL) p = strchr(yy_text, '(');
\r
7052 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7054 q = strchr(p, *p == '{' ? '}' : ')');
\r
7055 if (q != NULL) *q = NULLCHAR;
\r
7058 GameEnds(moveType, p, GE_FILE);
\r
7060 if (cmailMsgLoaded) {
\r
7061 ClearHighlights();
\r
7062 flipView = WhiteOnMove(currentMove);
\r
7063 if (moveType == GameUnfinished) flipView = !flipView;
\r
7064 if (appData.debugMode)
\r
7065 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
7069 case (ChessMove) 0: /* end of file */
\r
7070 if (appData.debugMode)
\r
7071 fprintf(debugFP, "Parser hit end of file\n");
\r
7072 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7073 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7077 case MT_CHECKMATE:
\r
7078 if (WhiteOnMove(currentMove)) {
\r
7079 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7081 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7084 case MT_STALEMATE:
\r
7085 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7091 case MoveNumberOne:
\r
7092 if (lastLoadGameStart == GNUChessGame) {
\r
7093 /* GNUChessGames have numbers, but they aren't move numbers */
\r
7094 if (appData.debugMode)
\r
7095 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7096 yy_text, (int) moveType);
\r
7097 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7099 /* else fall thru */
\r
7102 case GNUChessGame:
\r
7104 /* Reached start of next game in file */
\r
7105 if (appData.debugMode)
\r
7106 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
7107 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7108 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7112 case MT_CHECKMATE:
\r
7113 if (WhiteOnMove(currentMove)) {
\r
7114 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7116 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7119 case MT_STALEMATE:
\r
7120 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7126 case PositionDiagram: /* should not happen; ignore */
\r
7127 case ElapsedTime: /* ignore */
\r
7128 case NAG: /* ignore */
\r
7129 if (appData.debugMode)
\r
7130 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7131 yy_text, (int) moveType);
\r
7132 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7135 if (appData.testLegality) {
\r
7136 if (appData.debugMode)
\r
7137 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
7138 sprintf(move, "Illegal move: %d.%s%s",
\r
7139 (forwardMostMove / 2) + 1,
\r
7140 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7141 DisplayError(move, 0);
\r
7144 if (appData.debugMode)
\r
7145 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
7146 yy_text, currentMoveString);
\r
7147 fromX = currentMoveString[0] - AAA;
\r
7148 fromY = currentMoveString[1] - ONE;
\r
7149 toX = currentMoveString[2] - AAA;
\r
7150 toY = currentMoveString[3] - ONE;
\r
7151 promoChar = currentMoveString[4];
\r
7155 case AmbiguousMove:
\r
7156 if (appData.debugMode)
\r
7157 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
7158 sprintf(move, "Ambiguous move: %d.%s%s",
\r
7159 (forwardMostMove / 2) + 1,
\r
7160 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7161 DisplayError(move, 0);
\r
7166 case ImpossibleMove:
\r
7167 if (appData.debugMode)
\r
7168 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
7169 sprintf(move, "Illegal move: %d.%s%s",
\r
7170 (forwardMostMove / 2) + 1,
\r
7171 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7172 DisplayError(move, 0);
\r
7178 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
7179 DrawPosition(FALSE, boards[currentMove]);
\r
7180 DisplayBothClocks();
\r
7181 if (!appData.matchMode && commentList[currentMove] != NULL)
\r
7182 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7184 (void) StopLoadGameTimer();
\r
7185 gameFileFP = NULL;
\r
7186 cmailOldMove = forwardMostMove;
\r
7189 /* currentMoveString is set as a side-effect of yylex */
\r
7190 strcat(currentMoveString, "\n");
\r
7191 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7193 thinkOutput[0] = NULLCHAR;
\r
7194 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7195 currentMove = forwardMostMove;
\r
7200 /* Load the nth game from the given file */
\r
7202 LoadGameFromFile(filename, n, title, useList)
\r
7206 /*Boolean*/ int useList;
\r
7209 char buf[MSG_SIZ];
\r
7211 if (strcmp(filename, "-") == 0) {
\r
7215 f = fopen(filename, "rb");
\r
7217 sprintf(buf, "Can't open \"%s\"", filename);
\r
7218 DisplayError(buf, errno);
\r
7222 if (fseek(f, 0, 0) == -1) {
\r
7223 /* f is not seekable; probably a pipe */
\r
7226 if (useList && n == 0) {
\r
7227 int error = GameListBuild(f);
\r
7229 DisplayError("Cannot build game list", error);
\r
7230 } else if (!ListEmpty(&gameList) &&
\r
7231 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7232 GameListPopUp(f, title);
\r
7235 GameListDestroy();
\r
7238 if (n == 0) n = 1;
\r
7239 return LoadGame(f, n, title, FALSE);
\r
7244 MakeRegisteredMove()
\r
7246 int fromX, fromY, toX, toY;
\r
7248 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7249 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
7252 if (appData.debugMode)
\r
7253 fprintf(debugFP, "Restoring %s for game %d\n",
\r
7254 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
7256 thinkOutput[0] = NULLCHAR;
\r
7257 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
7258 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
7259 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
7260 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
7261 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
7262 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
7263 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7264 ShowMove(fromX, fromY, toX, toY);
\r
7266 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7267 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7272 case MT_CHECKMATE:
\r
7273 if (WhiteOnMove(currentMove)) {
\r
7274 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
7276 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
7280 case MT_STALEMATE:
\r
7281 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
7287 case CMAIL_RESIGN:
\r
7288 if (WhiteOnMove(currentMove)) {
\r
7289 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
7291 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
7295 case CMAIL_ACCEPT:
\r
7296 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
7307 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
7309 CmailLoadGame(f, gameNumber, title, useList)
\r
7317 if (gameNumber > nCmailGames) {
\r
7318 DisplayError("No more games in this message", 0);
\r
7321 if (f == lastLoadGameFP) {
\r
7322 int offset = gameNumber - lastLoadGameNumber;
\r
7323 if (offset == 0) {
\r
7324 cmailMsg[0] = NULLCHAR;
\r
7325 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7326 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
7327 nCmailMovesRegistered--;
\r
7329 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
7330 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
7331 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
7334 if (! RegisterMove()) return FALSE;
\r
7338 retVal = LoadGame(f, gameNumber, title, useList);
\r
7340 /* Make move registered during previous look at this game, if any */
\r
7341 MakeRegisteredMove();
\r
7343 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
7344 commentList[currentMove]
\r
7345 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
7346 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7352 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
7354 ReloadGame(offset)
\r
7357 int gameNumber = lastLoadGameNumber + offset;
\r
7358 if (lastLoadGameFP == NULL) {
\r
7359 DisplayError("No game has been loaded yet", 0);
\r
7362 if (gameNumber <= 0) {
\r
7363 DisplayError("Can't back up any further", 0);
\r
7366 if (cmailMsgLoaded) {
\r
7367 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
7368 lastLoadGameTitle, lastLoadGameUseList);
\r
7370 return LoadGame(lastLoadGameFP, gameNumber,
\r
7371 lastLoadGameTitle, lastLoadGameUseList);
\r
7377 /* Load the nth game from open file f */
\r
7379 LoadGame(f, gameNumber, title, useList)
\r
7386 char buf[MSG_SIZ];
\r
7387 int gn = gameNumber;
\r
7388 ListGame *lg = NULL;
\r
7389 int numPGNTags = 0;
\r
7391 GameMode oldGameMode;
\r
7393 if (appData.debugMode)
\r
7394 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
7396 if (gameMode == Training )
\r
7397 SetTrainingModeOff();
\r
7399 oldGameMode = gameMode;
\r
7400 if (gameMode != BeginningOfGame) {
\r
7401 Reset(FALSE, TRUE);
\r
7405 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
7406 fclose(lastLoadGameFP);
\r
7410 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
7413 fseek(f, lg->offset, 0);
\r
7414 GameListHighlight(gameNumber);
\r
7418 DisplayError("Game number out of range", 0);
\r
7422 GameListDestroy();
\r
7423 if (fseek(f, 0, 0) == -1) {
\r
7424 if (f == lastLoadGameFP ?
\r
7425 gameNumber == lastLoadGameNumber + 1 :
\r
7426 gameNumber == 1) {
\r
7429 DisplayError("Can't seek on game file", 0);
\r
7434 lastLoadGameFP = f;
\r
7435 lastLoadGameNumber = gameNumber;
\r
7436 strcpy(lastLoadGameTitle, title);
\r
7437 lastLoadGameUseList = useList;
\r
7442 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
7443 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
7444 lg->gameInfo.black);
\r
7445 DisplayTitle(buf);
\r
7446 } else if (*title != NULLCHAR) {
\r
7447 if (gameNumber > 1) {
\r
7448 sprintf(buf, "%s %d", title, gameNumber);
\r
7449 DisplayTitle(buf);
\r
7451 DisplayTitle(title);
\r
7455 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
7456 gameMode = PlayFromGameFile;
\r
7460 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7461 CopyBoard(boards[0], initialPosition);
\r
7465 * Skip the first gn-1 games in the file.
\r
7466 * Also skip over anything that precedes an identifiable
\r
7467 * start of game marker, to avoid being confused by
\r
7468 * garbage at the start of the file. Currently
\r
7469 * recognized start of game markers are the move number "1",
\r
7470 * the pattern "gnuchess .* game", the pattern
\r
7471 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
7472 * A game that starts with one of the latter two patterns
\r
7473 * will also have a move number 1, possibly
\r
7474 * following a position diagram.
\r
7475 * 5-4-02: Let's try being more lenient and allowing a game to
\r
7476 * start with an unnumbered move. Does that break anything?
\r
7478 cm = lastLoadGameStart = (ChessMove) 0;
\r
7480 yyboardindex = forwardMostMove;
\r
7481 cm = (ChessMove) yylex();
\r
7483 case (ChessMove) 0:
\r
7484 if (cmailMsgLoaded) {
\r
7485 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
7487 Reset(TRUE, TRUE);
\r
7488 DisplayError("Game not found in file", 0);
\r
7492 case GNUChessGame:
\r
7495 lastLoadGameStart = cm;
\r
7498 case MoveNumberOne:
\r
7499 switch (lastLoadGameStart) {
\r
7500 case GNUChessGame:
\r
7504 case MoveNumberOne:
\r
7505 case (ChessMove) 0:
\r
7506 gn--; /* count this game */
\r
7507 lastLoadGameStart = cm;
\r
7516 switch (lastLoadGameStart) {
\r
7517 case GNUChessGame:
\r
7519 case MoveNumberOne:
\r
7520 case (ChessMove) 0:
\r
7521 gn--; /* count this game */
\r
7522 lastLoadGameStart = cm;
\r
7525 lastLoadGameStart = cm; /* game counted already */
\r
7533 yyboardindex = forwardMostMove;
\r
7534 cm = (ChessMove) yylex();
\r
7535 } while (cm == PGNTag || cm == Comment);
\r
7542 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
7543 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
7544 != CMAIL_OLD_RESULT) {
\r
7545 nCmailResults ++ ;
\r
7546 cmailResult[ CMAIL_MAX_GAMES
\r
7547 - gn - 1] = CMAIL_OLD_RESULT;
\r
7553 /* Only a NormalMove can be at the start of a game
\r
7554 * without a position diagram. */
\r
7555 if (lastLoadGameStart == (ChessMove) 0) {
\r
7557 lastLoadGameStart = MoveNumberOne;
\r
7566 if (appData.debugMode)
\r
7567 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
7569 if (cm == XBoardGame) {
\r
7570 /* Skip any header junk before position diagram and/or move 1 */
\r
7572 yyboardindex = forwardMostMove;
\r
7573 cm = (ChessMove) yylex();
\r
7575 if (cm == (ChessMove) 0 ||
\r
7576 cm == GNUChessGame || cm == XBoardGame) {
\r
7577 /* Empty game; pretend end-of-file and handle later */
\r
7578 cm = (ChessMove) 0;
\r
7582 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
7583 cm == PGNTag || cm == Comment)
\r
7586 } else if (cm == GNUChessGame) {
\r
7587 if (gameInfo.event != NULL) {
\r
7588 free(gameInfo.event);
\r
7590 gameInfo.event = StrSave(yy_text);
\r
7593 startedFromSetupPosition = FALSE;
\r
7594 while (cm == PGNTag) {
\r
7595 if (appData.debugMode)
\r
7596 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
7597 err = ParsePGNTag(yy_text, &gameInfo);
\r
7598 if (!err) numPGNTags++;
\r
7600 if (gameInfo.fen != NULL) {
\r
7601 Board initial_position;
\r
7602 startedFromSetupPosition = TRUE;
\r
7603 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
7604 Reset(TRUE, TRUE);
\r
7605 DisplayError("Bad FEN position in file", 0);
\r
7608 CopyBoard(boards[0], initial_position);
\r
7609 /* [HGM] copy FEN attributes as well */
\r
7611 initialRulePlies = FENrulePlies;
\r
7612 epStatus[0] = FENepStatus;
\r
7613 for( i=0; i< nrCastlingRights; i++ )
\r
7614 castlingRights[0][i] = FENcastlingRights[i];
\r
7616 if (blackPlaysFirst) {
\r
7617 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7618 CopyBoard(boards[1], initial_position);
\r
7619 strcpy(moveList[0], "");
\r
7620 strcpy(parseList[0], "");
\r
7621 timeRemaining[0][1] = whiteTimeRemaining;
\r
7622 timeRemaining[1][1] = blackTimeRemaining;
\r
7623 if (commentList[0] != NULL) {
\r
7624 commentList[1] = commentList[0];
\r
7625 commentList[0] = NULL;
\r
7628 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7630 yyboardindex = forwardMostMove;
\r
7631 free(gameInfo.fen);
\r
7632 gameInfo.fen = NULL;
\r
7635 yyboardindex = forwardMostMove;
\r
7636 cm = (ChessMove) yylex();
\r
7638 /* Handle comments interspersed among the tags */
\r
7639 while (cm == Comment) {
\r
7641 if (appData.debugMode)
\r
7642 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7644 if (*p == '{' || *p == '[' || *p == '(') {
\r
7645 p[strlen(p) - 1] = NULLCHAR;
\r
7648 while (*p == '\n') p++;
\r
7649 AppendComment(currentMove, p);
\r
7650 yyboardindex = forwardMostMove;
\r
7651 cm = (ChessMove) yylex();
\r
7655 /* don't rely on existence of Event tag since if game was
\r
7656 * pasted from clipboard the Event tag may not exist
\r
7658 if (numPGNTags > 0){
\r
7660 if (gameInfo.variant == VariantNormal) {
\r
7661 gameInfo.variant = StringToVariant(gameInfo.event);
\r
7664 if( appData.autoDisplayTags ) {
\r
7665 tags = PGNTags(&gameInfo);
\r
7666 TagsPopUp(tags, CmailMsg());
\r
7671 /* Make something up, but don't display it now */
\r
7676 if (cm == PositionDiagram) {
\r
7679 Board initial_position;
\r
7681 if (appData.debugMode)
\r
7682 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
7684 if (!startedFromSetupPosition) {
\r
7686 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
7687 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
7697 initial_position[i][j++] = CharToPiece(*p);
\r
7700 while (*p == ' ' || *p == '\t' ||
\r
7701 *p == '\n' || *p == '\r') p++;
\r
7703 if (strncmp(p, "black", strlen("black"))==0)
\r
7704 blackPlaysFirst = TRUE;
\r
7706 blackPlaysFirst = FALSE;
\r
7707 startedFromSetupPosition = TRUE;
\r
7709 CopyBoard(boards[0], initial_position);
\r
7710 if (blackPlaysFirst) {
\r
7711 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7712 CopyBoard(boards[1], initial_position);
\r
7713 strcpy(moveList[0], "");
\r
7714 strcpy(parseList[0], "");
\r
7715 timeRemaining[0][1] = whiteTimeRemaining;
\r
7716 timeRemaining[1][1] = blackTimeRemaining;
\r
7717 if (commentList[0] != NULL) {
\r
7718 commentList[1] = commentList[0];
\r
7719 commentList[0] = NULL;
\r
7722 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7725 yyboardindex = forwardMostMove;
\r
7726 cm = (ChessMove) yylex();
\r
7729 if (first.pr == NoProc) {
\r
7730 StartChessProgram(&first);
\r
7732 InitChessProgram(&first);
\r
7733 SendToProgram("force\n", &first);
\r
7734 if (startedFromSetupPosition) {
\r
7735 SendBoard(&first, forwardMostMove);
\r
7736 DisplayBothClocks();
\r
7739 while (cm == Comment) {
\r
7741 if (appData.debugMode)
\r
7742 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7744 if (*p == '{' || *p == '[' || *p == '(') {
\r
7745 p[strlen(p) - 1] = NULLCHAR;
\r
7748 while (*p == '\n') p++;
\r
7749 AppendComment(currentMove, p);
\r
7750 yyboardindex = forwardMostMove;
\r
7751 cm = (ChessMove) yylex();
\r
7754 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
7755 cm == WhiteWins || cm == BlackWins ||
\r
7756 cm == GameIsDrawn || cm == GameUnfinished) {
\r
7757 DisplayMessage("", "No moves in game");
\r
7758 if (cmailMsgLoaded) {
\r
7759 if (appData.debugMode)
\r
7760 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
7761 ClearHighlights();
\r
7764 DrawPosition(FALSE, boards[currentMove]);
\r
7765 DisplayBothClocks();
\r
7766 gameMode = EditGame;
\r
7768 gameFileFP = NULL;
\r
7773 if (commentList[currentMove] != NULL) {
\r
7774 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
7775 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7778 if (!matchMode && appData.timeDelay != 0)
\r
7779 DrawPosition(FALSE, boards[currentMove]);
\r
7781 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
7782 programStats.ok_to_send = 1;
\r
7785 /* if the first token after the PGN tags is a move
\r
7786 * and not move number 1, retrieve it from the parser
\r
7788 if (cm != MoveNumberOne)
\r
7789 LoadGameOneMove(cm);
\r
7791 /* load the remaining moves from the file */
\r
7792 while (LoadGameOneMove((ChessMove)0)) {
\r
7793 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7794 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7797 /* rewind to the start of the game */
\r
7798 currentMove = backwardMostMove;
\r
7800 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7802 if (oldGameMode == AnalyzeFile ||
\r
7803 oldGameMode == AnalyzeMode) {
\r
7804 AnalyzeFileEvent();
\r
7807 if (matchMode || appData.timeDelay == 0) {
\r
7809 gameMode = EditGame;
\r
7811 } else if (appData.timeDelay > 0) {
\r
7812 AutoPlayGameLoop();
\r
7815 if (appData.debugMode)
\r
7816 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
7820 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
7822 ReloadPosition(offset)
\r
7825 int positionNumber = lastLoadPositionNumber + offset;
\r
7826 if (lastLoadPositionFP == NULL) {
\r
7827 DisplayError("No position has been loaded yet", 0);
\r
7830 if (positionNumber <= 0) {
\r
7831 DisplayError("Can't back up any further", 0);
\r
7834 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
7835 lastLoadPositionTitle);
\r
7838 /* Load the nth position from the given file */
\r
7840 LoadPositionFromFile(filename, n, title)
\r
7846 char buf[MSG_SIZ];
\r
7848 if (strcmp(filename, "-") == 0) {
\r
7849 return LoadPosition(stdin, n, "stdin");
\r
7851 f = fopen(filename, "rb");
\r
7853 sprintf(buf, "Can't open \"%s\"", filename);
\r
7854 DisplayError(buf, errno);
\r
7857 return LoadPosition(f, n, title);
\r
7862 /* Load the nth position from the given open file, and close it */
\r
7864 LoadPosition(f, positionNumber, title)
\r
7866 int positionNumber;
\r
7869 char *p, line[MSG_SIZ];
\r
7870 Board initial_position;
\r
7871 int i, j, fenMode, pn;
\r
7873 if (gameMode == Training )
\r
7874 SetTrainingModeOff();
\r
7876 if (gameMode != BeginningOfGame) {
\r
7877 Reset(FALSE, TRUE);
\r
7879 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
7880 fclose(lastLoadPositionFP);
\r
7882 if (positionNumber == 0) positionNumber = 1;
\r
7883 lastLoadPositionFP = f;
\r
7884 lastLoadPositionNumber = positionNumber;
\r
7885 strcpy(lastLoadPositionTitle, title);
\r
7886 if (first.pr == NoProc) {
\r
7887 StartChessProgram(&first);
\r
7888 InitChessProgram(&first);
\r
7890 pn = positionNumber;
\r
7891 if (positionNumber < 0) {
\r
7892 /* Negative position number means to seek to that byte offset */
\r
7893 if (fseek(f, -positionNumber, 0) == -1) {
\r
7894 DisplayError("Can't seek on position file", 0);
\r
7899 if (fseek(f, 0, 0) == -1) {
\r
7900 if (f == lastLoadPositionFP ?
\r
7901 positionNumber == lastLoadPositionNumber + 1 :
\r
7902 positionNumber == 1) {
\r
7905 DisplayError("Can't seek on position file", 0);
\r
7910 /* See if this file is FEN or old-style xboard */
\r
7911 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7912 DisplayError("Position not found in file", 0);
\r
7915 switch (line[0]) {
\r
7916 case '#': case 'x':
\r
7920 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
7921 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
7922 case '1': case '2': case '3': case '4': case '5': case '6':
\r
7923 case '7': case '8': case '9':
\r
7925 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
7926 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
7927 case 'C': case 'W': case 'c': case 'w':
\r
7934 if (fenMode || line[0] == '#') pn--;
\r
7936 /* skip postions before number pn */
\r
7937 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7938 Reset(TRUE, TRUE);
\r
7939 DisplayError("Position not found in file", 0);
\r
7942 if (fenMode || line[0] == '#') pn--;
\r
7947 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
7948 DisplayError("Bad FEN position in file", 0);
\r
7952 (void) fgets(line, MSG_SIZ, f);
\r
7953 (void) fgets(line, MSG_SIZ, f);
\r
7955 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
7956 (void) fgets(line, MSG_SIZ, f);
\r
7957 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
7960 initial_position[i][j++] = CharToPiece(*p);
\r
7964 blackPlaysFirst = FALSE;
\r
7966 (void) fgets(line, MSG_SIZ, f);
\r
7967 if (strncmp(line, "black", strlen("black"))==0)
\r
7968 blackPlaysFirst = TRUE;
\r
7971 startedFromSetupPosition = TRUE;
\r
7973 SendToProgram("force\n", &first);
\r
7974 CopyBoard(boards[0], initial_position);
\r
7975 /* [HGM] copy FEN attributes as well */
\r
7977 initialRulePlies = FENrulePlies;
\r
7978 epStatus[0] = FENepStatus;
\r
7979 for( i=0; i< nrCastlingRights; i++ )
\r
7980 castlingRights[0][i] = FENcastlingRights[i];
\r
7982 if (blackPlaysFirst) {
\r
7983 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7984 strcpy(moveList[0], "");
\r
7985 strcpy(parseList[0], "");
\r
7986 CopyBoard(boards[1], initial_position);
\r
7987 DisplayMessage("", "Black to play");
\r
7989 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7990 DisplayMessage("", "White to play");
\r
7992 SendBoard(&first, forwardMostMove);
\r
7994 if (positionNumber > 1) {
\r
7995 sprintf(line, "%s %d", title, positionNumber);
\r
7996 DisplayTitle(line);
\r
7998 DisplayTitle(title);
\r
8000 gameMode = EditGame;
\r
8003 timeRemaining[0][1] = whiteTimeRemaining;
\r
8004 timeRemaining[1][1] = blackTimeRemaining;
\r
8005 DrawPosition(FALSE, boards[currentMove]);
\r
8012 CopyPlayerNameIntoFileName(dest, src)
\r
8013 char **dest, *src;
\r
8015 while (*src != NULLCHAR && *src != ',') {
\r
8016 if (*src == ' ') {
\r
8020 *(*dest)++ = *src++;
\r
8025 char *DefaultFileName(ext)
\r
8028 static char def[MSG_SIZ];
\r
8031 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
8033 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
8035 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
8039 def[0] = NULLCHAR;
\r
8044 /* Save the current game to the given file */
\r
8046 SaveGameToFile(filename, append)
\r
8051 char buf[MSG_SIZ];
\r
8053 if (strcmp(filename, "-") == 0) {
\r
8054 return SaveGame(stdout, 0, NULL);
\r
8056 f = fopen(filename, append ? "a" : "w");
\r
8058 sprintf(buf, "Can't open \"%s\"", filename);
\r
8059 DisplayError(buf, errno);
\r
8062 return SaveGame(f, 0, NULL);
\r
8071 static char buf[MSG_SIZ];
\r
8074 p = strchr(str, ' ');
\r
8075 if (p == NULL) return str;
\r
8076 strncpy(buf, str, p - str);
\r
8077 buf[p - str] = NULLCHAR;
\r
8081 #define PGN_MAX_LINE 75
\r
8083 #define PGN_SIDE_WHITE 0
\r
8084 #define PGN_SIDE_BLACK 1
\r
8087 static int FindFirstMoveOutOfBook( int side )
\r
8091 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
8092 int index = backwardMostMove;
\r
8093 int has_book_hit = 0;
\r
8095 if( (index % 2) != side ) {
\r
8099 while( index < forwardMostMove ) {
\r
8100 /* Check to see if engine is in book */
\r
8101 int depth = pvInfoList[index].depth;
\r
8102 int score = pvInfoList[index].score;
\r
8105 if( depth <= 2 ) {
\r
8108 else if( score == 0 && depth == 63 ) {
\r
8109 in_book = 1; /* Zappa */
\r
8111 else if( score == 2 && depth == 99 ) {
\r
8112 in_book = 1; /* Abrok */
\r
8115 has_book_hit += in_book;
\r
8131 void GetOutOfBookInfo( char * buf )
\r
8135 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8137 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
8138 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
8142 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
8143 for( i=0; i<2; i++ ) {
\r
8147 if( i > 0 && oob[0] >= 0 ) {
\r
8148 strcat( buf, " " );
\r
8151 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
8152 sprintf( buf+strlen(buf), "%s%.2f",
\r
8153 pvInfoList[idx].score >= 0 ? "+" : "",
\r
8154 pvInfoList[idx].score / 100.0 );
\r
8160 /* Save game in PGN style and close the file */
\r
8165 int i, offset, linelen, newblock;
\r
8169 int movelen, numlen, blank;
\r
8170 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
8172 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8174 tm = time((time_t *) NULL);
\r
8176 PrintPGNTags(f, &gameInfo);
\r
8178 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8179 char *fen = PositionToFEN(backwardMostMove, 1);
\r
8180 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
8181 fprintf(f, "\n{--------------\n");
\r
8182 PrintPosition(f, backwardMostMove);
\r
8183 fprintf(f, "--------------}\n");
\r
8187 /* [AS] Out of book annotation */
\r
8188 if( appData.saveOutOfBookInfo ) {
\r
8191 GetOutOfBookInfo( buf );
\r
8193 if( buf[0] != '\0' ) {
\r
8194 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8201 i = backwardMostMove;
\r
8205 while (i < forwardMostMove) {
\r
8206 /* Print comments preceding this move */
\r
8207 if (commentList[i] != NULL) {
\r
8208 if (linelen > 0) fprintf(f, "\n");
\r
8209 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8214 /* Format move number */
\r
8215 if ((i % 2) == 0) {
\r
8216 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8219 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
8221 numtext[0] = NULLCHAR;
\r
8224 numlen = strlen(numtext);
\r
8227 /* Print move number */
\r
8228 blank = linelen > 0 && numlen > 0;
\r
8229 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
8238 fprintf(f, numtext);
\r
8239 linelen += numlen;
\r
8242 movetext = SavePart(parseList[i]);
\r
8244 /* [AS] Add PV info if present */
\r
8245 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
8246 /* [HGM] add time */
\r
8247 char buf[MSG_SIZ]; int seconds = 0;
\r
8249 if(i >= backwardMostMove) {
\r
8250 /* take the time that changed */
\r
8251 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
8253 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
8256 if (appData.debugMode) {
\r
8257 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
8258 timeRemaining[0][i+1], timeRemaining[0][i],
\r
8259 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
8263 if( seconds < 0 ) buf[0] = 0; else
\r
8264 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
8265 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
8267 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
8269 pvInfoList[i].score >= 0 ? "+" : "",
\r
8270 pvInfoList[i].score / 100.0,
\r
8271 pvInfoList[i].depth,
\r
8273 movetext = move_buffer;
\r
8276 movelen = strlen(movetext);
\r
8279 blank = linelen > 0 && movelen > 0;
\r
8280 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
8289 fprintf(f, movetext);
\r
8290 linelen += movelen;
\r
8295 /* Start a new line */
\r
8296 if (linelen > 0) fprintf(f, "\n");
\r
8298 /* Print comments after last move */
\r
8299 if (commentList[i] != NULL) {
\r
8300 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8303 /* Print result */
\r
8304 if (gameInfo.resultDetails != NULL &&
\r
8305 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8306 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
8307 PGNResult(gameInfo.result));
\r
8309 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8316 /* Save game in old style and close the file */
\r
8318 SaveGameOldStyle(f)
\r
8324 tm = time((time_t *) NULL);
\r
8326 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
8327 PrintOpponents(f);
\r
8329 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8330 fprintf(f, "\n[--------------\n");
\r
8331 PrintPosition(f, backwardMostMove);
\r
8332 fprintf(f, "--------------]\n");
\r
8337 i = backwardMostMove;
\r
8338 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8340 while (i < forwardMostMove) {
\r
8341 if (commentList[i] != NULL) {
\r
8342 fprintf(f, "[%s]\n", commentList[i]);
\r
8345 if ((i % 2) == 1) {
\r
8346 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
8349 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
8351 if (commentList[i] != NULL) {
\r
8355 if (i >= forwardMostMove) {
\r
8359 fprintf(f, "%s\n", parseList[i]);
\r
8364 if (commentList[i] != NULL) {
\r
8365 fprintf(f, "[%s]\n", commentList[i]);
\r
8368 /* This isn't really the old style, but it's close enough */
\r
8369 if (gameInfo.resultDetails != NULL &&
\r
8370 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8371 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
8372 gameInfo.resultDetails);
\r
8374 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8381 /* Save the current game to open file f and close the file */
\r
8383 SaveGame(f, dummy, dummy2)
\r
8388 if (gameMode == EditPosition) EditPositionDone();
\r
8389 if (appData.oldSaveStyle)
\r
8390 return SaveGameOldStyle(f);
\r
8392 return SaveGamePGN(f);
\r
8395 /* Save the current position to the given file */
\r
8397 SavePositionToFile(filename)
\r
8401 char buf[MSG_SIZ];
\r
8403 if (strcmp(filename, "-") == 0) {
\r
8404 return SavePosition(stdout, 0, NULL);
\r
8406 f = fopen(filename, "a");
\r
8408 sprintf(buf, "Can't open \"%s\"", filename);
\r
8409 DisplayError(buf, errno);
\r
8412 SavePosition(f, 0, NULL);
\r
8418 /* Save the current position to the given open file and close the file */
\r
8420 SavePosition(f, dummy, dummy2)
\r
8428 if (appData.oldSaveStyle) {
\r
8429 tm = time((time_t *) NULL);
\r
8431 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
8432 PrintOpponents(f);
\r
8433 fprintf(f, "[--------------\n");
\r
8434 PrintPosition(f, currentMove);
\r
8435 fprintf(f, "--------------]\n");
\r
8437 fen = PositionToFEN(currentMove, 1);
\r
8438 fprintf(f, "%s\n", fen);
\r
8446 ReloadCmailMsgEvent(unregister)
\r
8450 static char *inFilename = NULL;
\r
8451 static char *outFilename;
\r
8453 struct stat inbuf, outbuf;
\r
8456 /* Any registered moves are unregistered if unregister is set, */
\r
8457 /* i.e. invoked by the signal handler */
\r
8459 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8460 cmailMoveRegistered[i] = FALSE;
\r
8461 if (cmailCommentList[i] != NULL) {
\r
8462 free(cmailCommentList[i]);
\r
8463 cmailCommentList[i] = NULL;
\r
8466 nCmailMovesRegistered = 0;
\r
8469 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8470 cmailResult[i] = CMAIL_NOT_RESULT;
\r
8472 nCmailResults = 0;
\r
8474 if (inFilename == NULL) {
\r
8475 /* Because the filenames are static they only get malloced once */
\r
8476 /* and they never get freed */
\r
8477 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
8478 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
8480 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
8481 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
8484 status = stat(outFilename, &outbuf);
\r
8486 cmailMailedMove = FALSE;
\r
8488 status = stat(inFilename, &inbuf);
\r
8489 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
8492 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
8493 counts the games, notes how each one terminated, etc.
\r
8495 It would be nice to remove this kludge and instead gather all
\r
8496 the information while building the game list. (And to keep it
\r
8497 in the game list nodes instead of having a bunch of fixed-size
\r
8498 parallel arrays.) Note this will require getting each game's
\r
8499 termination from the PGN tags, as the game list builder does
\r
8500 not process the game moves. --mann
\r
8502 cmailMsgLoaded = TRUE;
\r
8503 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
8505 /* Load first game in the file or popup game menu */
\r
8506 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
8508 #endif /* !WIN32 */
\r
8516 char string[MSG_SIZ];
\r
8518 if ( cmailMailedMove
\r
8519 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
8520 return TRUE; /* Allow free viewing */
\r
8523 /* Unregister move to ensure that we don't leave RegisterMove */
\r
8524 /* with the move registered when the conditions for registering no */
\r
8526 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8527 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8528 nCmailMovesRegistered --;
\r
8530 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
8532 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
8533 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
8537 if (cmailOldMove == -1) {
\r
8538 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
8542 if (currentMove > cmailOldMove + 1) {
\r
8543 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
8547 if (currentMove < cmailOldMove) {
\r
8548 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
8552 if (forwardMostMove > currentMove) {
\r
8553 /* Silently truncate extra moves */
\r
8557 if ( (currentMove == cmailOldMove + 1)
\r
8558 || ( (currentMove == cmailOldMove)
\r
8559 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
8560 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
8561 if (gameInfo.result != GameUnfinished) {
\r
8562 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
8565 if (commentList[currentMove] != NULL) {
\r
8566 cmailCommentList[lastLoadGameNumber - 1]
\r
8567 = StrSave(commentList[currentMove]);
\r
8569 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
8571 if (appData.debugMode)
\r
8572 fprintf(debugFP, "Saving %s for game %d\n",
\r
8573 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8576 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
8578 f = fopen(string, "w");
\r
8579 if (appData.oldSaveStyle) {
\r
8580 SaveGameOldStyle(f); /* also closes the file */
\r
8582 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
8583 f = fopen(string, "w");
\r
8584 SavePosition(f, 0, NULL); /* also closes the file */
\r
8586 fprintf(f, "{--------------\n");
\r
8587 PrintPosition(f, currentMove);
\r
8588 fprintf(f, "--------------}\n\n");
\r
8590 SaveGame(f, 0, NULL); /* also closes the file*/
\r
8593 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
8594 nCmailMovesRegistered ++;
\r
8595 } else if (nCmailGames == 1) {
\r
8596 DisplayError("You have not made a move yet", 0);
\r
8607 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
8608 FILE *commandOutput;
\r
8609 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
8610 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
8616 if (! cmailMsgLoaded) {
\r
8617 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
8621 if (nCmailGames == nCmailResults) {
\r
8622 DisplayError("No unfinished games", 0);
\r
8626 #if CMAIL_PROHIBIT_REMAIL
\r
8627 if (cmailMailedMove) {
\r
8628 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
8629 DisplayError(msg, 0);
\r
8634 if (! (cmailMailedMove || RegisterMove())) return;
\r
8636 if ( cmailMailedMove
\r
8637 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
8638 sprintf(string, partCommandString,
\r
8639 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
8640 commandOutput = popen(string, "rb");
\r
8642 if (commandOutput == NULL) {
\r
8643 DisplayError("Failed to invoke cmail", 0);
\r
8645 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
8646 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
8648 if (nBuffers > 1) {
\r
8649 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
8650 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
8651 nBytes = MSG_SIZ - 1;
\r
8653 (void) memcpy(msg, buffer, nBytes);
\r
8655 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
8657 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
8658 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
8661 for (i = 0; i < nCmailGames; i ++) {
\r
8662 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
8667 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
8669 sprintf(buffer, "%s/%s.%s.archive",
\r
8671 appData.cmailGameName,
\r
8673 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
8674 cmailMsgLoaded = FALSE;
\r
8678 DisplayInformation(msg);
\r
8679 pclose(commandOutput);
\r
8682 if ((*cmailMsg) != '\0') {
\r
8683 DisplayInformation(cmailMsg);
\r
8688 #endif /* !WIN32 */
\r
8697 int prependComma = 0;
\r
8699 char string[MSG_SIZ]; /* Space for game-list */
\r
8702 if (!cmailMsgLoaded) return "";
\r
8704 if (cmailMailedMove) {
\r
8705 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
8707 /* Create a list of games left */
\r
8708 sprintf(string, "[");
\r
8709 for (i = 0; i < nCmailGames; i ++) {
\r
8710 if (! ( cmailMoveRegistered[i]
\r
8711 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
8712 if (prependComma) {
\r
8713 sprintf(number, ",%d", i + 1);
\r
8715 sprintf(number, "%d", i + 1);
\r
8719 strcat(string, number);
\r
8722 strcat(string, "]");
\r
8724 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
8725 switch (nCmailGames) {
\r
8728 "Still need to make move for game\n");
\r
8733 "Still need to make moves for both games\n");
\r
8738 "Still need to make moves for all %d games\n",
\r
8743 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
8746 "Still need to make a move for game %s\n",
\r
8751 if (nCmailResults == nCmailGames) {
\r
8752 sprintf(cmailMsg, "No unfinished games\n");
\r
8754 sprintf(cmailMsg, "Ready to send mail\n");
\r
8760 "Still need to make moves for games %s\n",
\r
8766 #endif /* WIN32 */
\r
8772 if (gameMode == Training)
\r
8773 SetTrainingModeOff();
\r
8775 Reset(TRUE, TRUE);
\r
8776 cmailMsgLoaded = FALSE;
\r
8777 if (appData.icsActive) {
\r
8778 SendToICS(ics_prefix);
\r
8779 SendToICS("refresh\n");
\r
8783 static int exiting = 0;
\r
8790 if (exiting > 2) {
\r
8791 /* Give up on clean exit */
\r
8794 if (exiting > 1) {
\r
8795 /* Keep trying for clean exit */
\r
8799 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
8801 if (telnetISR != NULL) {
\r
8802 RemoveInputSource(telnetISR);
\r
8804 if (icsPR != NoProc) {
\r
8805 DestroyChildProcess(icsPR, TRUE);
\r
8807 /* Save game if resource set and not already saved by GameEnds() */
\r
8808 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
\r
8809 if (*appData.saveGameFile != NULLCHAR) {
\r
8810 SaveGameToFile(appData.saveGameFile, TRUE);
\r
8811 } else if (appData.autoSaveGames) {
\r
8814 if (*appData.savePositionFile != NULLCHAR) {
\r
8815 SavePositionToFile(appData.savePositionFile);
\r
8818 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8820 /* Kill off chess programs */
\r
8821 if (first.pr != NoProc) {
\r
8822 ExitAnalyzeMode();
\r
8824 DoSleep( appData.delayBeforeQuit );
\r
8825 SendToProgram("quit\n", &first);
\r
8826 DoSleep( appData.delayAfterQuit );
\r
8827 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
8829 if (second.pr != NoProc) {
\r
8830 DoSleep( appData.delayBeforeQuit );
\r
8831 SendToProgram("quit\n", &second);
\r
8832 DoSleep( appData.delayAfterQuit );
\r
8833 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
8835 if (first.isr != NULL) {
\r
8836 RemoveInputSource(first.isr);
\r
8838 if (second.isr != NULL) {
\r
8839 RemoveInputSource(second.isr);
\r
8842 ShutDownFrontEnd();
\r
8849 if (appData.debugMode)
\r
8850 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
8854 if (gameMode == MachinePlaysWhite ||
\r
8855 gameMode == MachinePlaysBlack) {
\r
8858 DisplayBothClocks();
\r
8860 if (gameMode == PlayFromGameFile) {
\r
8861 if (appData.timeDelay >= 0)
\r
8862 AutoPlayGameLoop();
\r
8863 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
8864 Reset(FALSE, TRUE);
\r
8865 SendToICS(ics_prefix);
\r
8866 SendToICS("refresh\n");
\r
8867 } else if (currentMove < forwardMostMove) {
\r
8868 ForwardInner(forwardMostMove);
\r
8870 pauseExamInvalid = FALSE;
\r
8872 switch (gameMode) {
\r
8875 case IcsExamining:
\r
8876 pauseExamForwardMostMove = forwardMostMove;
\r
8877 pauseExamInvalid = FALSE;
\r
8878 /* fall through */
\r
8879 case IcsObserving:
\r
8880 case IcsPlayingWhite:
\r
8881 case IcsPlayingBlack:
\r
8885 case PlayFromGameFile:
\r
8886 (void) StopLoadGameTimer();
\r
8890 case BeginningOfGame:
\r
8891 if (appData.icsActive) return;
\r
8892 /* else fall through */
\r
8893 case MachinePlaysWhite:
\r
8894 case MachinePlaysBlack:
\r
8895 case TwoMachinesPlay:
\r
8896 if (forwardMostMove == 0)
\r
8897 return; /* don't pause if no one has moved */
\r
8898 if ((gameMode == MachinePlaysWhite &&
\r
8899 !WhiteOnMove(forwardMostMove)) ||
\r
8900 (gameMode == MachinePlaysBlack &&
\r
8901 WhiteOnMove(forwardMostMove))) {
\r
8912 EditCommentEvent()
\r
8914 char title[MSG_SIZ];
\r
8916 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
8917 strcpy(title, "Edit comment");
\r
8919 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
8920 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
8921 parseList[currentMove - 1]);
\r
8924 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
8931 char *tags = PGNTags(&gameInfo);
\r
8932 EditTagsPopUp(tags);
\r
8937 AnalyzeModeEvent()
\r
8939 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
8942 if (gameMode != AnalyzeFile) {
\r
8944 if (gameMode != EditGame) return;
\r
8945 ResurrectChessProgram();
\r
8946 SendToProgram("analyze\n", &first);
\r
8947 first.analyzing = TRUE;
\r
8948 /*first.maybeThinking = TRUE;*/
\r
8949 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8950 AnalysisPopUp("Analysis",
\r
8951 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8953 gameMode = AnalyzeMode;
\r
8958 StartAnalysisClock();
\r
8959 GetTimeMark(&lastNodeCountTime);
\r
8960 lastNodeCount = 0;
\r
8964 AnalyzeFileEvent()
\r
8966 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
8969 if (gameMode != AnalyzeMode) {
\r
8971 if (gameMode != EditGame) return;
\r
8972 ResurrectChessProgram();
\r
8973 SendToProgram("analyze\n", &first);
\r
8974 first.analyzing = TRUE;
\r
8975 /*first.maybeThinking = TRUE;*/
\r
8976 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8977 AnalysisPopUp("Analysis",
\r
8978 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8980 gameMode = AnalyzeFile;
\r
8985 StartAnalysisClock();
\r
8986 GetTimeMark(&lastNodeCountTime);
\r
8987 lastNodeCount = 0;
\r
8991 MachineWhiteEvent()
\r
8993 char buf[MSG_SIZ];
\r
8995 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
8999 if (gameMode == PlayFromGameFile ||
\r
9000 gameMode == TwoMachinesPlay ||
\r
9001 gameMode == Training ||
\r
9002 gameMode == AnalyzeMode ||
\r
9003 gameMode == EndOfGame)
\r
9006 if (gameMode == EditPosition)
\r
9007 EditPositionDone();
\r
9009 if (!WhiteOnMove(currentMove)) {
\r
9010 DisplayError("It is not White's turn", 0);
\r
9014 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9015 ExitAnalyzeMode();
\r
9017 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9018 gameMode == AnalyzeFile)
\r
9021 ResurrectChessProgram(); /* in case it isn't running */
\r
9022 gameMode = MachinePlaysWhite;
\r
9026 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9027 DisplayTitle(buf);
\r
9028 if (first.sendName) {
\r
9029 sprintf(buf, "name %s\n", gameInfo.black);
\r
9030 SendToProgram(buf, &first);
\r
9032 if (first.sendTime) {
\r
9033 if (first.useColors) {
\r
9034 SendToProgram("black\n", &first); /*gnu kludge*/
\r
9036 SendTimeRemaining(&first, TRUE);
\r
9038 if (first.useColors) {
\r
9039 SendToProgram("white\ngo\n", &first);
\r
9041 SendToProgram("go\n", &first);
\r
9043 SetMachineThinkingEnables();
\r
9044 first.maybeThinking = TRUE;
\r
9047 if (appData.autoFlipView && !flipView) {
\r
9048 flipView = !flipView;
\r
9049 DrawPosition(FALSE, NULL);
\r
9054 MachineBlackEvent()
\r
9056 char buf[MSG_SIZ];
\r
9058 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
9062 if (gameMode == PlayFromGameFile ||
\r
9063 gameMode == TwoMachinesPlay ||
\r
9064 gameMode == Training ||
\r
9065 gameMode == AnalyzeMode ||
\r
9066 gameMode == EndOfGame)
\r
9069 if (gameMode == EditPosition)
\r
9070 EditPositionDone();
\r
9072 if (WhiteOnMove(currentMove)) {
\r
9073 DisplayError("It is not Black's turn", 0);
\r
9077 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9078 ExitAnalyzeMode();
\r
9080 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9081 gameMode == AnalyzeFile)
\r
9084 ResurrectChessProgram(); /* in case it isn't running */
\r
9085 gameMode = MachinePlaysBlack;
\r
9089 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9090 DisplayTitle(buf);
\r
9091 if (first.sendName) {
\r
9092 sprintf(buf, "name %s\n", gameInfo.white);
\r
9093 SendToProgram(buf, &first);
\r
9095 if (first.sendTime) {
\r
9096 if (first.useColors) {
\r
9097 SendToProgram("white\n", &first); /*gnu kludge*/
\r
9099 SendTimeRemaining(&first, FALSE);
\r
9101 if (first.useColors) {
\r
9102 SendToProgram("black\ngo\n", &first);
\r
9104 SendToProgram("go\n", &first);
\r
9106 SetMachineThinkingEnables();
\r
9107 first.maybeThinking = TRUE;
\r
9110 if (appData.autoFlipView && flipView) {
\r
9111 flipView = !flipView;
\r
9112 DrawPosition(FALSE, NULL);
\r
9118 DisplayTwoMachinesTitle()
\r
9120 char buf[MSG_SIZ];
\r
9121 if (appData.matchGames > 0) {
\r
9122 if (first.twoMachinesColor[0] == 'w') {
\r
9123 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9124 gameInfo.white, gameInfo.black,
\r
9125 first.matchWins, second.matchWins,
\r
9126 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9128 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9129 gameInfo.white, gameInfo.black,
\r
9130 second.matchWins, first.matchWins,
\r
9131 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9134 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9136 DisplayTitle(buf);
\r
9140 TwoMachinesEvent P((void))
\r
9143 char buf[MSG_SIZ];
\r
9144 ChessProgramState *onmove;
\r
9146 if (appData.noChessProgram) return;
\r
9148 switch (gameMode) {
\r
9149 case TwoMachinesPlay:
\r
9151 case MachinePlaysWhite:
\r
9152 case MachinePlaysBlack:
\r
9153 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9154 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9157 /* fall through */
\r
9158 case BeginningOfGame:
\r
9159 case PlayFromGameFile:
\r
9162 if (gameMode != EditGame) return;
\r
9164 case EditPosition:
\r
9165 EditPositionDone();
\r
9169 ExitAnalyzeMode();
\r
9176 forwardMostMove = currentMove;
\r
9177 ResurrectChessProgram(); /* in case first program isn't running */
\r
9179 if (second.pr == NULL) {
\r
9180 StartChessProgram(&second);
\r
9181 if (second.protocolVersion == 1) {
\r
9182 TwoMachinesEventIfReady();
\r
9184 /* kludge: allow timeout for initial "feature" command */
\r
9186 DisplayMessage("", "Starting second chess program");
\r
9187 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9191 DisplayMessage("", "");
\r
9192 InitChessProgram(&second);
\r
9193 SendToProgram("force\n", &second);
\r
9194 if (startedFromSetupPosition) {
\r
9195 SendBoard(&second, backwardMostMove);
\r
9197 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9198 SendMoveToProgram(i, &second);
\r
9201 gameMode = TwoMachinesPlay;
\r
9205 DisplayTwoMachinesTitle();
\r
9207 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
9213 SendToProgram(first.computerString, &first);
\r
9214 if (first.sendName) {
\r
9215 sprintf(buf, "name %s\n", second.tidy);
\r
9216 SendToProgram(buf, &first);
\r
9218 SendToProgram(second.computerString, &second);
\r
9219 if (second.sendName) {
\r
9220 sprintf(buf, "name %s\n", first.tidy);
\r
9221 SendToProgram(buf, &second);
\r
9224 if (!first.sendTime || !second.sendTime) {
\r
9226 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9227 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9229 if (onmove->sendTime) {
\r
9230 if (onmove->useColors) {
\r
9231 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
9233 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
9235 if (onmove->useColors) {
\r
9236 SendToProgram(onmove->twoMachinesColor, onmove);
\r
9238 SendToProgram("go\n", onmove);
\r
9239 onmove->maybeThinking = TRUE;
\r
9240 SetMachineThinkingEnables();
\r
9248 if (gameMode == Training) {
\r
9249 SetTrainingModeOff();
\r
9250 gameMode = PlayFromGameFile;
\r
9251 DisplayMessage("", "Training mode off");
\r
9253 gameMode = Training;
\r
9254 animateTraining = appData.animate;
\r
9256 /* make sure we are not already at the end of the game */
\r
9257 if (currentMove < forwardMostMove) {
\r
9258 SetTrainingModeOn();
\r
9259 DisplayMessage("", "Training mode on");
\r
9261 gameMode = PlayFromGameFile;
\r
9262 DisplayError("Already at end of game", 0);
\r
9271 if (!appData.icsActive) return;
\r
9272 switch (gameMode) {
\r
9273 case IcsPlayingWhite:
\r
9274 case IcsPlayingBlack:
\r
9275 case IcsObserving:
\r
9277 case BeginningOfGame:
\r
9278 case IcsExamining:
\r
9284 case EditPosition:
\r
9285 EditPositionDone();
\r
9290 ExitAnalyzeMode();
\r
9298 gameMode = IcsIdle;
\r
9309 switch (gameMode) {
\r
9311 SetTrainingModeOff();
\r
9313 case MachinePlaysWhite:
\r
9314 case MachinePlaysBlack:
\r
9315 case BeginningOfGame:
\r
9316 SendToProgram("force\n", &first);
\r
9317 SetUserThinkingEnables();
\r
9319 case PlayFromGameFile:
\r
9320 (void) StopLoadGameTimer();
\r
9321 if (gameFileFP != NULL) {
\r
9322 gameFileFP = NULL;
\r
9325 case EditPosition:
\r
9326 EditPositionDone();
\r
9330 ExitAnalyzeMode();
\r
9331 SendToProgram("force\n", &first);
\r
9333 case TwoMachinesPlay:
\r
9334 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9335 ResurrectChessProgram();
\r
9336 SetUserThinkingEnables();
\r
9339 ResurrectChessProgram();
\r
9341 case IcsPlayingBlack:
\r
9342 case IcsPlayingWhite:
\r
9343 DisplayError("Warning: You are still playing a game", 0);
\r
9345 case IcsObserving:
\r
9346 DisplayError("Warning: You are still observing a game", 0);
\r
9348 case IcsExamining:
\r
9349 DisplayError("Warning: You are still examining a game", 0);
\r
9360 first.offeredDraw = second.offeredDraw = 0;
\r
9362 if (gameMode == PlayFromGameFile) {
\r
9363 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9364 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9368 if (gameMode == MachinePlaysWhite ||
\r
9369 gameMode == MachinePlaysBlack ||
\r
9370 gameMode == TwoMachinesPlay ||
\r
9371 gameMode == EndOfGame) {
\r
9372 i = forwardMostMove;
\r
9373 while (i > currentMove) {
\r
9374 SendToProgram("undo\n", &first);
\r
9377 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9378 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9379 DisplayBothClocks();
\r
9380 if (whiteFlag || blackFlag) {
\r
9381 whiteFlag = blackFlag = 0;
\r
9386 gameMode = EditGame;
\r
9393 EditPositionEvent()
\r
9395 if (gameMode == EditPosition) {
\r
9401 if (gameMode != EditGame) return;
\r
9403 gameMode = EditPosition;
\r
9406 if (currentMove > 0)
\r
9407 CopyBoard(boards[0], boards[currentMove]);
\r
9409 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
9411 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9412 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9419 if (first.analysisSupport && first.analyzing) {
\r
9420 SendToProgram("exit\n", &first);
\r
9421 first.analyzing = FALSE;
\r
9423 AnalysisPopDown();
\r
9424 thinkOutput[0] = NULLCHAR;
\r
9428 EditPositionDone()
\r
9430 startedFromSetupPosition = TRUE;
\r
9431 InitChessProgram(&first);
\r
9432 SendToProgram("force\n", &first);
\r
9433 if (blackPlaysFirst) {
\r
9434 strcpy(moveList[0], "");
\r
9435 strcpy(parseList[0], "");
\r
9436 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9437 CopyBoard(boards[1], boards[0]);
\r
9439 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9441 SendBoard(&first, forwardMostMove);
\r
9443 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9444 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9445 gameMode = EditGame;
\r
9447 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9448 ClearHighlights(); /* [AS] */
\r
9451 /* Pause for `ms' milliseconds */
\r
9452 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9462 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
9465 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9467 SendMultiLineToICS(buf)
\r
9470 char temp[MSG_SIZ+1], *p;
\r
9473 len = strlen(buf);
\r
9474 if (len > MSG_SIZ)
\r
9477 strncpy(temp, buf, len);
\r
9482 if (*p == '\n' || *p == '\r')
\r
9487 strcat(temp, "\n");
\r
9489 SendToPlayer(temp, strlen(temp));
\r
9493 SetWhiteToPlayEvent()
\r
9495 if (gameMode == EditPosition) {
\r
9496 blackPlaysFirst = FALSE;
\r
9497 DisplayBothClocks(); /* works because currentMove is 0 */
\r
9498 } else if (gameMode == IcsExamining) {
\r
9499 SendToICS(ics_prefix);
\r
9500 SendToICS("tomove white\n");
\r
9505 SetBlackToPlayEvent()
\r
9507 if (gameMode == EditPosition) {
\r
9508 blackPlaysFirst = TRUE;
\r
9509 currentMove = 1; /* kludge */
\r
9510 DisplayBothClocks();
\r
9512 } else if (gameMode == IcsExamining) {
\r
9513 SendToICS(ics_prefix);
\r
9514 SendToICS("tomove black\n");
\r
9519 EditPositionMenuEvent(selection, x, y)
\r
9520 ChessSquare selection;
\r
9523 char buf[MSG_SIZ];
\r
9524 ChessSquare piece = boards[0][y][x];
\r
9526 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
9528 switch (selection) {
\r
9530 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
9531 SendToICS(ics_prefix);
\r
9532 SendToICS("bsetup clear\n");
\r
9533 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
9534 SendToICS(ics_prefix);
\r
9535 SendToICS("clearboard\n");
\r
9537 for (x = 0; x < BOARD_WIDTH; x++) {
\r
9538 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
9539 if (gameMode == IcsExamining) {
\r
9540 if (boards[currentMove][y][x] != EmptySquare) {
\r
9541 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
9542 AAA + x, ONE + y);
\r
9546 boards[0][y][x] = EmptySquare;
\r
9551 if (gameMode == EditPosition) {
\r
9552 DrawPosition(FALSE, boards[0]);
\r
9557 SetWhiteToPlayEvent();
\r
9561 SetBlackToPlayEvent();
\r
9565 if (gameMode == IcsExamining) {
\r
9566 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
9569 boards[0][y][x] = EmptySquare;
\r
9570 DrawPosition(FALSE, boards[0]);
\r
9574 case PromotePiece:
\r
9575 if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||
\r
9576 piece >= (int)BlackPawn && piece < (int)BlackWazir ) {
\r
9577 selection = (ChessSquare) (PROMOTED piece);
\r
9578 } else if(piece == EmptySquare) selection = WhiteWazir;
\r
9579 else selection = (ChessSquare)((int)piece - 1);
\r
9580 goto defaultlabel;
\r
9583 if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||
\r
9584 piece >= (int)BlackUnicorn && piece < (int)BlackKing ) {
\r
9585 selection = (ChessSquare) (DEMOTED piece);
\r
9586 } else if( piece == WhiteKing || piece == BlackKing )
\r
9587 selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);
\r
9588 else if(piece == EmptySquare) selection = BlackWazir;
\r
9589 else selection = (ChessSquare)((int)piece + 1);
\r
9590 goto defaultlabel;
\r
9594 if(gameInfo.variant == VariantShatranj ||
\r
9595 gameInfo.variant == VariantXiangqi ||
\r
9596 gameInfo.variant == VariantCourier )
\r
9597 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
9598 goto defaultlabel;
\r
9602 if(gameInfo.variant == VariantXiangqi)
\r
9603 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
9604 if(gameInfo.variant == VariantKnightmate)
\r
9605 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
9608 if (gameMode == IcsExamining) {
\r
9609 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
9610 PieceToChar(selection), AAA + x, ONE + y);
\r
9613 boards[0][y][x] = selection;
\r
9614 DrawPosition(FALSE, boards[0]);
\r
9622 DropMenuEvent(selection, x, y)
\r
9623 ChessSquare selection;
\r
9626 ChessMove moveType;
\r
9628 switch (gameMode) {
\r
9629 case IcsPlayingWhite:
\r
9630 case MachinePlaysBlack:
\r
9631 if (!WhiteOnMove(currentMove)) {
\r
9632 DisplayMoveError("It is Black's turn");
\r
9635 moveType = WhiteDrop;
\r
9637 case IcsPlayingBlack:
\r
9638 case MachinePlaysWhite:
\r
9639 if (WhiteOnMove(currentMove)) {
\r
9640 DisplayMoveError("It is White's turn");
\r
9643 moveType = BlackDrop;
\r
9646 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
9652 if (moveType == BlackDrop && selection < BlackPawn) {
\r
9653 selection = (ChessSquare) ((int) selection
\r
9654 + (int) BlackPawn - (int) WhitePawn);
\r
9656 if (boards[currentMove][y][x] != EmptySquare) {
\r
9657 DisplayMoveError("That square is occupied");
\r
9661 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
9667 /* Accept a pending offer of any kind from opponent */
\r
9669 if (appData.icsActive) {
\r
9670 SendToICS(ics_prefix);
\r
9671 SendToICS("accept\n");
\r
9672 } else if (cmailMsgLoaded) {
\r
9673 if (currentMove == cmailOldMove &&
\r
9674 commentList[cmailOldMove] != NULL &&
\r
9675 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9676 "Black offers a draw" : "White offers a draw")) {
\r
9678 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9679 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9681 DisplayError("There is no pending offer on this move", 0);
\r
9682 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9685 /* Not used for offers from chess program */
\r
9692 /* Decline a pending offer of any kind from opponent */
\r
9694 if (appData.icsActive) {
\r
9695 SendToICS(ics_prefix);
\r
9696 SendToICS("decline\n");
\r
9697 } else if (cmailMsgLoaded) {
\r
9698 if (currentMove == cmailOldMove &&
\r
9699 commentList[cmailOldMove] != NULL &&
\r
9700 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9701 "Black offers a draw" : "White offers a draw")) {
\r
9703 AppendComment(cmailOldMove, "Draw declined");
\r
9704 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
9707 DisplayError("There is no pending offer on this move", 0);
\r
9710 /* Not used for offers from chess program */
\r
9717 /* Issue ICS rematch command */
\r
9718 if (appData.icsActive) {
\r
9719 SendToICS(ics_prefix);
\r
9720 SendToICS("rematch\n");
\r
9727 /* Call your opponent's flag (claim a win on time) */
\r
9728 if (appData.icsActive) {
\r
9729 SendToICS(ics_prefix);
\r
9730 SendToICS("flag\n");
\r
9732 switch (gameMode) {
\r
9735 case MachinePlaysWhite:
\r
9738 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9741 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
9743 DisplayError("Your opponent is not out of time", 0);
\r
9746 case MachinePlaysBlack:
\r
9749 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9752 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
9754 DisplayError("Your opponent is not out of time", 0);
\r
9764 /* Offer draw or accept pending draw offer from opponent */
\r
9766 if (appData.icsActive) {
\r
9767 /* Note: tournament rules require draw offers to be
\r
9768 made after you make your move but before you punch
\r
9769 your clock. Currently ICS doesn't let you do that;
\r
9770 instead, you immediately punch your clock after making
\r
9771 a move, but you can offer a draw at any time. */
\r
9773 SendToICS(ics_prefix);
\r
9774 SendToICS("draw\n");
\r
9775 } else if (cmailMsgLoaded) {
\r
9776 if (currentMove == cmailOldMove &&
\r
9777 commentList[cmailOldMove] != NULL &&
\r
9778 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9779 "Black offers a draw" : "White offers a draw")) {
\r
9780 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9781 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9782 } else if (currentMove == cmailOldMove + 1) {
\r
9783 char *offer = WhiteOnMove(cmailOldMove) ?
\r
9784 "White offers a draw" : "Black offers a draw";
\r
9785 AppendComment(currentMove, offer);
\r
9786 DisplayComment(currentMove - 1, offer);
\r
9787 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
9789 DisplayError("You must make your move before offering a draw", 0);
\r
9790 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9792 } else if (first.offeredDraw) {
\r
9793 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
9795 if (first.sendDrawOffers) {
\r
9796 SendToProgram("draw\n", &first);
\r
9797 userOfferedDraw = TRUE;
\r
9805 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
9807 if (appData.icsActive) {
\r
9808 SendToICS(ics_prefix);
\r
9809 SendToICS("adjourn\n");
\r
9811 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
9819 /* Offer Abort or accept pending Abort offer from opponent */
\r
9821 if (appData.icsActive) {
\r
9822 SendToICS(ics_prefix);
\r
9823 SendToICS("abort\n");
\r
9825 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
9832 /* Resign. You can do this even if it's not your turn. */
\r
9834 if (appData.icsActive) {
\r
9835 SendToICS(ics_prefix);
\r
9836 SendToICS("resign\n");
\r
9838 switch (gameMode) {
\r
9839 case MachinePlaysWhite:
\r
9840 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9842 case MachinePlaysBlack:
\r
9843 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9846 if (cmailMsgLoaded) {
\r
9848 if (WhiteOnMove(cmailOldMove)) {
\r
9849 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9851 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9853 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
9864 StopObservingEvent()
\r
9866 /* Stop observing current games */
\r
9867 SendToICS(ics_prefix);
\r
9868 SendToICS("unobserve\n");
\r
9872 StopExaminingEvent()
\r
9874 /* Stop observing current game */
\r
9875 SendToICS(ics_prefix);
\r
9876 SendToICS("unexamine\n");
\r
9880 ForwardInner(target)
\r
9885 if (appData.debugMode)
\r
9886 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
9887 target, currentMove, forwardMostMove);
\r
9889 if (gameMode == EditPosition)
\r
9892 if (gameMode == PlayFromGameFile && !pausing)
\r
9895 if (gameMode == IcsExamining && pausing)
\r
9896 limit = pauseExamForwardMostMove;
\r
9898 limit = forwardMostMove;
\r
9900 if (target > limit) target = limit;
\r
9902 if (target > 0 && moveList[target - 1][0]) {
\r
9903 int fromX, fromY, toX, toY;
\r
9904 toX = moveList[target - 1][2] - AAA;
\r
9905 toY = moveList[target - 1][3] - ONE;
\r
9906 if (moveList[target - 1][1] == '@') {
\r
9907 if (appData.highlightLastMove) {
\r
9908 SetHighlights(-1, -1, toX, toY);
\r
9911 fromX = moveList[target - 1][0] - AAA;
\r
9912 fromY = moveList[target - 1][1] - ONE;
\r
9913 if (target == currentMove + 1) {
\r
9914 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
9916 if (appData.highlightLastMove) {
\r
9917 SetHighlights(fromX, fromY, toX, toY);
\r
9921 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9922 gameMode == Training || gameMode == PlayFromGameFile ||
\r
9923 gameMode == AnalyzeFile) {
\r
9924 while (currentMove < target) {
\r
9925 SendMoveToProgram(currentMove++, &first);
\r
9928 currentMove = target;
\r
9931 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9932 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9933 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9935 DisplayBothClocks();
\r
9936 DisplayMove(currentMove - 1);
\r
9937 DrawPosition(FALSE, boards[currentMove]);
\r
9938 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9939 if (commentList[currentMove] && !matchMode && gameMode != Training) {
\r
9940 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9948 if (gameMode == IcsExamining && !pausing) {
\r
9949 SendToICS(ics_prefix);
\r
9950 SendToICS("forward\n");
\r
9952 ForwardInner(currentMove + 1);
\r
9959 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9960 /* to optimze, we temporarily turn off analysis mode while we feed
\r
9961 * the remaining moves to the engine. Otherwise we get analysis output
\r
9962 * after each move.
\r
9964 if (first.analysisSupport) {
\r
9965 SendToProgram("exit\nforce\n", &first);
\r
9966 first.analyzing = FALSE;
\r
9970 if (gameMode == IcsExamining && !pausing) {
\r
9971 SendToICS(ics_prefix);
\r
9972 SendToICS("forward 999999\n");
\r
9974 ForwardInner(forwardMostMove);
\r
9977 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9978 /* we have fed all the moves, so reactivate analysis mode */
\r
9979 SendToProgram("analyze\n", &first);
\r
9980 first.analyzing = TRUE;
\r
9981 /*first.maybeThinking = TRUE;*/
\r
9982 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9987 BackwardInner(target)
\r
9990 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
9992 if (appData.debugMode)
\r
9993 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
9994 target, currentMove, forwardMostMove);
\r
9996 if (gameMode == EditPosition) return;
\r
9997 if (currentMove <= backwardMostMove) {
\r
9998 ClearHighlights();
\r
9999 DrawPosition(full_redraw, boards[currentMove]);
\r
10002 if (gameMode == PlayFromGameFile && !pausing)
\r
10005 if (moveList[target][0]) {
\r
10006 int fromX, fromY, toX, toY;
\r
10007 toX = moveList[target][2] - AAA;
\r
10008 toY = moveList[target][3] - ONE;
\r
10009 if (moveList[target][1] == '@') {
\r
10010 if (appData.highlightLastMove) {
\r
10011 SetHighlights(-1, -1, toX, toY);
\r
10014 fromX = moveList[target][0] - AAA;
\r
10015 fromY = moveList[target][1] - ONE;
\r
10016 if (target == currentMove - 1) {
\r
10017 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
10019 if (appData.highlightLastMove) {
\r
10020 SetHighlights(fromX, fromY, toX, toY);
\r
10024 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
10025 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
10026 while (currentMove > target) {
\r
10027 SendToProgram("undo\n", &first);
\r
10031 currentMove = target;
\r
10034 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10035 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10036 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10038 DisplayBothClocks();
\r
10039 DisplayMove(currentMove - 1);
\r
10040 DrawPosition(full_redraw, boards[currentMove]);
\r
10041 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10042 if (commentList[currentMove] != NULL) {
\r
10043 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10050 if (gameMode == IcsExamining && !pausing) {
\r
10051 SendToICS(ics_prefix);
\r
10052 SendToICS("backward\n");
\r
10054 BackwardInner(currentMove - 1);
\r
10061 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10062 /* to optimze, we temporarily turn off analysis mode while we undo
\r
10063 * all the moves. Otherwise we get analysis output after each undo.
\r
10065 if (first.analysisSupport) {
\r
10066 SendToProgram("exit\nforce\n", &first);
\r
10067 first.analyzing = FALSE;
\r
10071 if (gameMode == IcsExamining && !pausing) {
\r
10072 SendToICS(ics_prefix);
\r
10073 SendToICS("backward 999999\n");
\r
10075 BackwardInner(backwardMostMove);
\r
10078 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10079 /* we have fed all the moves, so reactivate analysis mode */
\r
10080 SendToProgram("analyze\n", &first);
\r
10081 first.analyzing = TRUE;
\r
10082 /*first.maybeThinking = TRUE;*/
\r
10083 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10088 ToNrEvent(int to)
\r
10090 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
10091 if (to >= forwardMostMove) to = forwardMostMove;
\r
10092 if (to <= backwardMostMove) to = backwardMostMove;
\r
10093 if (to < currentMove) {
\r
10094 BackwardInner(to);
\r
10096 ForwardInner(to);
\r
10103 if (gameMode != IcsExamining) {
\r
10104 DisplayError("You are not examining a game", 0);
\r
10108 DisplayError("You can't revert while pausing", 0);
\r
10111 SendToICS(ics_prefix);
\r
10112 SendToICS("revert\n");
\r
10116 RetractMoveEvent()
\r
10118 switch (gameMode) {
\r
10119 case MachinePlaysWhite:
\r
10120 case MachinePlaysBlack:
\r
10121 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10122 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10125 if (forwardMostMove < 2) return;
\r
10126 currentMove = forwardMostMove = forwardMostMove - 2;
\r
10127 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10128 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10129 DisplayBothClocks();
\r
10130 DisplayMove(currentMove - 1);
\r
10131 ClearHighlights();/*!! could figure this out*/
\r
10132 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
10133 SendToProgram("remove\n", &first);
\r
10134 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
10137 case BeginningOfGame:
\r
10141 case IcsPlayingWhite:
\r
10142 case IcsPlayingBlack:
\r
10143 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
10144 SendToICS(ics_prefix);
\r
10145 SendToICS("takeback 2\n");
\r
10147 SendToICS(ics_prefix);
\r
10148 SendToICS("takeback 1\n");
\r
10157 ChessProgramState *cps;
\r
10159 switch (gameMode) {
\r
10160 case MachinePlaysWhite:
\r
10161 if (!WhiteOnMove(forwardMostMove)) {
\r
10162 DisplayError("It is your turn", 0);
\r
10167 case MachinePlaysBlack:
\r
10168 if (WhiteOnMove(forwardMostMove)) {
\r
10169 DisplayError("It is your turn", 0);
\r
10174 case TwoMachinesPlay:
\r
10175 if (WhiteOnMove(forwardMostMove) ==
\r
10176 (first.twoMachinesColor[0] == 'w')) {
\r
10182 case BeginningOfGame:
\r
10186 SendToProgram("?\n", cps);
\r
10190 TruncateGameEvent()
\r
10193 if (gameMode != EditGame) return;
\r
10200 if (forwardMostMove > currentMove) {
\r
10201 if (gameInfo.resultDetails != NULL) {
\r
10202 free(gameInfo.resultDetails);
\r
10203 gameInfo.resultDetails = NULL;
\r
10204 gameInfo.result = GameUnfinished;
\r
10206 forwardMostMove = currentMove;
\r
10207 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
10215 if (appData.noChessProgram) return;
\r
10216 switch (gameMode) {
\r
10217 case MachinePlaysWhite:
\r
10218 if (WhiteOnMove(forwardMostMove)) {
\r
10219 DisplayError("Wait until your turn", 0);
\r
10223 case BeginningOfGame:
\r
10224 case MachinePlaysBlack:
\r
10225 if (!WhiteOnMove(forwardMostMove)) {
\r
10226 DisplayError("Wait until your turn", 0);
\r
10231 DisplayError("No hint available", 0);
\r
10234 SendToProgram("hint\n", &first);
\r
10235 hintRequested = TRUE;
\r
10241 if (appData.noChessProgram) return;
\r
10242 switch (gameMode) {
\r
10243 case MachinePlaysWhite:
\r
10244 if (WhiteOnMove(forwardMostMove)) {
\r
10245 DisplayError("Wait until your turn", 0);
\r
10249 case BeginningOfGame:
\r
10250 case MachinePlaysBlack:
\r
10251 if (!WhiteOnMove(forwardMostMove)) {
\r
10252 DisplayError("Wait until your turn", 0);
\r
10256 case EditPosition:
\r
10257 EditPositionDone();
\r
10259 case TwoMachinesPlay:
\r
10264 SendToProgram("bk\n", &first);
\r
10265 bookOutput[0] = NULLCHAR;
\r
10266 bookRequested = TRUE;
\r
10272 char *tags = PGNTags(&gameInfo);
\r
10273 TagsPopUp(tags, CmailMsg());
\r
10277 /* end button procedures */
\r
10280 PrintPosition(fp, move)
\r
10286 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
10287 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
10288 char c = PieceToChar(boards[move][i][j]);
\r
10289 fputc(c == 'x' ? '.' : c, fp);
\r
10290 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
10293 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
10294 fprintf(fp, "white to play\n");
\r
10296 fprintf(fp, "black to play\n");
\r
10300 PrintOpponents(fp)
\r
10303 if (gameInfo.white != NULL) {
\r
10304 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
10306 fprintf(fp, "\n");
\r
10310 /* Find last component of program's own name, using some heuristics */
\r
10312 TidyProgramName(prog, host, buf)
\r
10313 char *prog, *host, buf[MSG_SIZ];
\r
10316 int local = (strcmp(host, "localhost") == 0);
\r
10317 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
10319 while (*p == ' ') p++;
\r
10322 if (*prog == '"' || *prog == '\'') {
\r
10323 q = strchr(prog + 1, *prog);
\r
10325 q = strchr(prog, ' ');
\r
10327 if (q == NULL) q = prog + strlen(prog);
\r
10329 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
10331 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
10332 memcpy(buf, p, q - p);
\r
10333 buf[q - p] = NULLCHAR;
\r
10335 strcat(buf, "@");
\r
10336 strcat(buf, host);
\r
10341 TimeControlTagValue()
\r
10343 char buf[MSG_SIZ];
\r
10344 if (!appData.clockMode) {
\r
10345 strcpy(buf, "-");
\r
10346 } else if (movesPerSession > 0) {
\r
10347 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
10348 } else if (timeIncrement == 0) {
\r
10349 sprintf(buf, "%ld", timeControl/1000);
\r
10351 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
10353 return StrSave(buf);
\r
10359 /* This routine is used only for certain modes */
\r
10360 VariantClass v = gameInfo.variant;
\r
10361 ClearGameInfo(&gameInfo);
\r
10362 gameInfo.variant = v;
\r
10364 switch (gameMode) {
\r
10365 case MachinePlaysWhite:
\r
10366 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10367 gameInfo.site = StrSave(HostName());
\r
10368 gameInfo.date = PGNDate();
\r
10369 gameInfo.round = StrSave("-");
\r
10370 gameInfo.white = StrSave(first.tidy);
\r
10371 gameInfo.black = StrSave(UserName());
\r
10372 gameInfo.timeControl = TimeControlTagValue();
\r
10375 case MachinePlaysBlack:
\r
10376 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10377 gameInfo.site = StrSave(HostName());
\r
10378 gameInfo.date = PGNDate();
\r
10379 gameInfo.round = StrSave("-");
\r
10380 gameInfo.white = StrSave(UserName());
\r
10381 gameInfo.black = StrSave(first.tidy);
\r
10382 gameInfo.timeControl = TimeControlTagValue();
\r
10385 case TwoMachinesPlay:
\r
10386 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10387 gameInfo.site = StrSave(HostName());
\r
10388 gameInfo.date = PGNDate();
\r
10389 if (matchGame > 0) {
\r
10390 char buf[MSG_SIZ];
\r
10391 sprintf(buf, "%d", matchGame);
\r
10392 gameInfo.round = StrSave(buf);
\r
10394 gameInfo.round = StrSave("-");
\r
10396 if (first.twoMachinesColor[0] == 'w') {
\r
10397 gameInfo.white = StrSave(first.tidy);
\r
10398 gameInfo.black = StrSave(second.tidy);
\r
10400 gameInfo.white = StrSave(second.tidy);
\r
10401 gameInfo.black = StrSave(first.tidy);
\r
10403 gameInfo.timeControl = TimeControlTagValue();
\r
10407 gameInfo.event = StrSave("Edited game");
\r
10408 gameInfo.site = StrSave(HostName());
\r
10409 gameInfo.date = PGNDate();
\r
10410 gameInfo.round = StrSave("-");
\r
10411 gameInfo.white = StrSave("-");
\r
10412 gameInfo.black = StrSave("-");
\r
10415 case EditPosition:
\r
10416 gameInfo.event = StrSave("Edited position");
\r
10417 gameInfo.site = StrSave(HostName());
\r
10418 gameInfo.date = PGNDate();
\r
10419 gameInfo.round = StrSave("-");
\r
10420 gameInfo.white = StrSave("-");
\r
10421 gameInfo.black = StrSave("-");
\r
10424 case IcsPlayingWhite:
\r
10425 case IcsPlayingBlack:
\r
10426 case IcsObserving:
\r
10427 case IcsExamining:
\r
10430 case PlayFromGameFile:
\r
10431 gameInfo.event = StrSave("Game from non-PGN file");
\r
10432 gameInfo.site = StrSave(HostName());
\r
10433 gameInfo.date = PGNDate();
\r
10434 gameInfo.round = StrSave("-");
\r
10435 gameInfo.white = StrSave("?");
\r
10436 gameInfo.black = StrSave("?");
\r
10445 ReplaceComment(index, text)
\r
10451 while (*text == '\n') text++;
\r
10452 len = strlen(text);
\r
10453 while (len > 0 && text[len - 1] == '\n') len--;
\r
10455 if (commentList[index] != NULL)
\r
10456 free(commentList[index]);
\r
10459 commentList[index] = NULL;
\r
10462 commentList[index] = (char *) malloc(len + 2);
\r
10463 strncpy(commentList[index], text, len);
\r
10464 commentList[index][len] = '\n';
\r
10465 commentList[index][len + 1] = NULLCHAR;
\r
10478 if (ch == '\r') continue;
\r
10480 } while (ch != '\0');
\r
10484 AppendComment(index, text)
\r
10491 GetInfoFromComment( index, text );
\r
10494 while (*text == '\n') text++;
\r
10495 len = strlen(text);
\r
10496 while (len > 0 && text[len - 1] == '\n') len--;
\r
10498 if (len == 0) return;
\r
10500 if (commentList[index] != NULL) {
\r
10501 old = commentList[index];
\r
10502 oldlen = strlen(old);
\r
10503 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
10504 strcpy(commentList[index], old);
\r
10506 strncpy(&commentList[index][oldlen], text, len);
\r
10507 commentList[index][oldlen + len] = '\n';
\r
10508 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
10510 commentList[index] = (char *) malloc(len + 2);
\r
10511 strncpy(commentList[index], text, len);
\r
10512 commentList[index][len] = '\n';
\r
10513 commentList[index][len + 1] = NULLCHAR;
\r
10517 static char * FindStr( char * text, char * sub_text )
\r
10519 char * result = strstr( text, sub_text );
\r
10521 if( result != NULL ) {
\r
10522 result += strlen( sub_text );
\r
10528 /* [AS] Try to extract PV info from PGN comment */
\r
10529 void GetInfoFromComment( int index, char * text )
\r
10531 if( text != NULL && index > 0 ) {
\r
10535 char * s_eval = FindStr( text, "[%eval " );
\r
10536 char * s_emt = FindStr( text, "[%emt " );
\r
10538 if( s_eval != NULL || s_emt != NULL ) {
\r
10542 if( s_eval != NULL ) {
\r
10543 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
10547 if( delim != ']' ) {
\r
10552 if( s_emt != NULL ) {
\r
10556 /* We expect something like: [+|-]nnn.nn/dd */
\r
10557 char * sep = strchr( text, '/' );
\r
10558 int score_lo = 0;
\r
10560 if( sep == NULL || sep < (text+4) ) {
\r
10564 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
10568 if( score_lo < 0 || score_lo >= 100 ) {
\r
10572 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
10575 if( depth <= 0 ) {
\r
10583 pvInfoList[index-1].depth = depth;
\r
10584 pvInfoList[index-1].score = score;
\r
10585 pvInfoList[index-1].time = time;
\r
10590 SendToProgram(message, cps)
\r
10592 ChessProgramState *cps;
\r
10594 int count, outCount, error;
\r
10595 char buf[MSG_SIZ];
\r
10597 if (cps->pr == NULL) return;
\r
10600 if (appData.debugMode) {
\r
10602 GetTimeMark(&now);
\r
10603 fprintf(debugFP, "%ld >%-6s: %s",
\r
10604 SubtractTimeMarks(&now, &programStartTime),
\r
10605 cps->which, message);
\r
10608 count = strlen(message);
\r
10609 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
10610 if (outCount < count && !exiting) {
\r
10611 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
10612 DisplayFatalError(buf, error, 1);
\r
10617 ReceiveFromProgram(isr, closure, message, count, error)
\r
10618 InputSourceRef isr;
\r
10619 VOIDSTAR closure;
\r
10625 char buf[MSG_SIZ];
\r
10626 ChessProgramState *cps = (ChessProgramState *)closure;
\r
10628 if (isr != cps->isr) return; /* Killed intentionally */
\r
10629 if (count <= 0) {
\r
10630 if (count == 0) {
\r
10632 "Error: %s chess program (%s) exited unexpectedly",
\r
10633 cps->which, cps->program);
\r
10634 RemoveInputSource(cps->isr);
\r
10635 DisplayFatalError(buf, 0, 1);
\r
10638 "Error reading from %s chess program (%s)",
\r
10639 cps->which, cps->program);
\r
10640 RemoveInputSource(cps->isr);
\r
10642 /* [AS] Program is misbehaving badly... kill it */
\r
10643 if( count == -2 ) {
\r
10644 DestroyChildProcess( cps->pr, 9 );
\r
10645 cps->pr = NoProc;
\r
10648 DisplayFatalError(buf, error, 1);
\r
10650 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10654 if ((end_str = strchr(message, '\r')) != NULL)
\r
10655 *end_str = NULLCHAR;
\r
10656 if ((end_str = strchr(message, '\n')) != NULL)
\r
10657 *end_str = NULLCHAR;
\r
10659 if (appData.debugMode) {
\r
10661 GetTimeMark(&now);
\r
10662 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
10663 SubtractTimeMarks(&now, &programStartTime),
\r
10664 cps->which, message);
\r
10666 HandleMachineMove(message, cps);
\r
10671 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
10672 ChessProgramState *cps;
\r
10673 int mps, inc, sd, st;
\r
10676 char buf[MSG_SIZ];
\r
10677 int seconds = (tc / 1000) % 60;
\r
10679 if( timeControl_2 > 0 ) {
\r
10680 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
10681 tc = timeControl_2;
\r
10686 /* Set exact time per move, normally using st command */
\r
10687 if (cps->stKludge) {
\r
10688 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
10689 seconds = st % 60;
\r
10690 if (seconds == 0) {
\r
10691 sprintf(buf, "level 1 %d\n", st/60);
\r
10693 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
10696 sprintf(buf, "st %d\n", st);
\r
10699 /* Set conventional or incremental time control, using level command */
\r
10700 if (seconds == 0) {
\r
10701 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
10702 Fixed in later versions, but still avoid :seconds
\r
10703 when seconds is 0. */
\r
10704 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
10706 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
10707 seconds, inc/1000);
\r
10710 SendToProgram(buf, cps);
\r
10712 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
10713 /* Orthogonally, limit search to given depth */
\r
10715 if (cps->sdKludge) {
\r
10716 sprintf(buf, "depth\n%d\n", sd);
\r
10718 sprintf(buf, "sd %d\n", sd);
\r
10720 SendToProgram(buf, cps);
\r
10725 SendTimeRemaining(cps, machineWhite)
\r
10726 ChessProgramState *cps;
\r
10727 int /*boolean*/ machineWhite;
\r
10729 char message[MSG_SIZ];
\r
10730 long time, otime;
\r
10732 /* Note: this routine must be called when the clocks are stopped
\r
10733 or when they have *just* been set or switched; otherwise
\r
10734 it will be off by the time since the current tick started.
\r
10736 if (machineWhite) {
\r
10737 time = whiteTimeRemaining / 10;
\r
10738 otime = blackTimeRemaining / 10;
\r
10740 time = blackTimeRemaining / 10;
\r
10741 otime = whiteTimeRemaining / 10;
\r
10743 if (time <= 0) time = 1;
\r
10744 if (otime <= 0) otime = 1;
\r
10746 sprintf(message, "time %ld\n", time);
\r
10747 SendToProgram(message, cps);
\r
10749 sprintf(message, "otim %ld\n", otime);
\r
10750 SendToProgram(message, cps);
\r
10754 BoolFeature(p, name, loc, cps)
\r
10758 ChessProgramState *cps;
\r
10760 char buf[MSG_SIZ];
\r
10761 int len = strlen(name);
\r
10763 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10765 sscanf(*p, "%d", &val);
\r
10766 *loc = (val != 0);
\r
10767 while (**p && **p != ' ') (*p)++;
\r
10768 sprintf(buf, "accepted %s\n", name);
\r
10769 SendToProgram(buf, cps);
\r
10776 IntFeature(p, name, loc, cps)
\r
10780 ChessProgramState *cps;
\r
10782 char buf[MSG_SIZ];
\r
10783 int len = strlen(name);
\r
10784 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10786 sscanf(*p, "%d", loc);
\r
10787 while (**p && **p != ' ') (*p)++;
\r
10788 sprintf(buf, "accepted %s\n", name);
\r
10789 SendToProgram(buf, cps);
\r
10796 StringFeature(p, name, loc, cps)
\r
10800 ChessProgramState *cps;
\r
10802 char buf[MSG_SIZ];
\r
10803 int len = strlen(name);
\r
10804 if (strncmp((*p), name, len) == 0
\r
10805 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
10807 sscanf(*p, "%[^\"]", loc);
\r
10808 while (**p && **p != '\"') (*p)++;
\r
10809 if (**p == '\"') (*p)++;
\r
10810 sprintf(buf, "accepted %s\n", name);
\r
10811 SendToProgram(buf, cps);
\r
10818 FeatureDone(cps, val)
\r
10819 ChessProgramState* cps;
\r
10822 DelayedEventCallback cb = GetDelayedEvent();
\r
10823 if ((cb == InitBackEnd3 && cps == &first) ||
\r
10824 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
10825 CancelDelayedEvent();
\r
10826 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
10828 cps->initDone = val;
\r
10831 /* Parse feature command from engine */
\r
10833 ParseFeatures(args, cps)
\r
10835 ChessProgramState *cps;
\r
10840 char buf[MSG_SIZ];
\r
10843 while (*p == ' ') p++;
\r
10844 if (*p == NULLCHAR) return;
\r
10846 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
10847 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
10848 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
10849 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
10850 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
10851 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
10852 /* Engine can disable reuse, but can't enable it if user said no */
\r
10853 if (!val) cps->reuse = FALSE;
\r
10856 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
10857 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
10858 if (gameMode == TwoMachinesPlay) {
\r
10859 DisplayTwoMachinesTitle();
\r
10861 DisplayTitle("");
\r
10865 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
10866 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
10867 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
10868 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
10869 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
10870 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
10871 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
10872 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
10873 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
10874 if (IntFeature(&p, "done", &val, cps)) {
\r
10875 FeatureDone(cps, val);
\r
10878 /* Added by Tord: */
\r
10879 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
10880 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
10881 /* End of additions by Tord */
\r
10883 /* unknown feature: complain and skip */
\r
10885 while (*q && *q != '=') q++;
\r
10886 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
10887 SendToProgram(buf, cps);
\r
10891 if (*p == '\"') {
\r
10893 while (*p && *p != '\"') p++;
\r
10894 if (*p == '\"') p++;
\r
10896 while (*p && *p != ' ') p++;
\r
10904 PeriodicUpdatesEvent(newState)
\r
10907 if (newState == appData.periodicUpdates)
\r
10910 appData.periodicUpdates=newState;
\r
10912 /* Display type changes, so update it now */
\r
10913 DisplayAnalysis();
\r
10915 /* Get the ball rolling again... */
\r
10917 AnalysisPeriodicEvent(1);
\r
10918 StartAnalysisClock();
\r
10923 PonderNextMoveEvent(newState)
\r
10926 if (newState == appData.ponderNextMove) return;
\r
10927 if (gameMode == EditPosition) EditPositionDone();
\r
10929 SendToProgram("hard\n", &first);
\r
10930 if (gameMode == TwoMachinesPlay) {
\r
10931 SendToProgram("hard\n", &second);
\r
10934 SendToProgram("easy\n", &first);
\r
10935 thinkOutput[0] = NULLCHAR;
\r
10936 if (gameMode == TwoMachinesPlay) {
\r
10937 SendToProgram("easy\n", &second);
\r
10940 appData.ponderNextMove = newState;
\r
10944 ShowThinkingEvent(newState)
\r
10947 if (newState == appData.showThinking) return;
\r
10948 if (gameMode == EditPosition) EditPositionDone();
\r
10950 SendToProgram("post\n", &first);
\r
10951 if (gameMode == TwoMachinesPlay) {
\r
10952 SendToProgram("post\n", &second);
\r
10955 SendToProgram("nopost\n", &first);
\r
10956 thinkOutput[0] = NULLCHAR;
\r
10957 if (gameMode == TwoMachinesPlay) {
\r
10958 SendToProgram("nopost\n", &second);
\r
10961 appData.showThinking = newState;
\r
10965 AskQuestionEvent(title, question, replyPrefix, which)
\r
10966 char *title; char *question; char *replyPrefix; char *which;
\r
10968 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
10969 if (pr == NoProc) return;
\r
10970 AskQuestion(title, question, replyPrefix, pr);
\r
10974 DisplayMove(moveNumber)
\r
10977 char message[MSG_SIZ];
\r
10978 char res[MSG_SIZ];
\r
10979 char cpThinkOutput[MSG_SIZ];
\r
10981 if (moveNumber == forwardMostMove - 1 ||
\r
10982 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10984 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
10986 if (strchr(cpThinkOutput, '\n')) {
\r
10987 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
10990 *cpThinkOutput = NULLCHAR;
\r
10993 /* [AS] Hide thinking from human user */
\r
10994 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
10995 *cpThinkOutput = NULLCHAR;
\r
10996 if( thinkOutput[0] != NULLCHAR ) {
\r
10999 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
11000 cpThinkOutput[i] = '.';
\r
11002 cpThinkOutput[i] = NULLCHAR;
\r
11003 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
11007 if (moveNumber == forwardMostMove - 1 &&
\r
11008 gameInfo.resultDetails != NULL) {
\r
11009 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
11010 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
11012 sprintf(res, " {%s} %s",
\r
11013 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
11016 res[0] = NULLCHAR;
\r
11019 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11020 DisplayMessage(res, cpThinkOutput);
\r
11022 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
11023 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11024 parseList[moveNumber], res);
\r
11025 DisplayMessage(message, cpThinkOutput);
\r
11030 DisplayAnalysisText(text)
\r
11033 char buf[MSG_SIZ];
\r
11035 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11036 sprintf(buf, "Analysis (%s)", first.tidy);
\r
11037 AnalysisPopUp(buf, text);
\r
11042 only_one_move(str)
\r
11045 while (*str && isspace(*str)) ++str;
\r
11046 while (*str && !isspace(*str)) ++str;
\r
11047 if (!*str) return 1;
\r
11048 while (*str && isspace(*str)) ++str;
\r
11049 if (!*str) return 1;
\r
11054 DisplayAnalysis()
\r
11056 char buf[MSG_SIZ];
\r
11057 char lst[MSG_SIZ / 2];
\r
11059 static char *xtra[] = { "", " (--)", " (++)" };
\r
11062 if (programStats.time == 0) {
\r
11063 programStats.time = 1;
\r
11066 if (programStats.got_only_move) {
\r
11067 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
11069 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
11071 nps = (((double)programStats.nodes) /
\r
11072 (((double)programStats.time)/100.0));
\r
11074 cs = programStats.time % 100;
\r
11075 s = programStats.time / 100;
\r
11076 h = (s / (60*60));
\r
11081 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
11082 if (programStats.move_name[0] != NULLCHAR) {
\r
11083 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11084 programStats.depth,
\r
11085 programStats.nr_moves-programStats.moves_left,
\r
11086 programStats.nr_moves, programStats.move_name,
\r
11087 ((float)programStats.score)/100.0, lst,
\r
11088 only_one_move(lst)?
\r
11089 xtra[programStats.got_fail] : "",
\r
11090 programStats.nodes, (int)nps, h, m, s, cs);
\r
11092 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11093 programStats.depth,
\r
11094 programStats.nr_moves-programStats.moves_left,
\r
11095 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
11097 only_one_move(lst)?
\r
11098 xtra[programStats.got_fail] : "",
\r
11099 programStats.nodes, (int)nps, h, m, s, cs);
\r
11102 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11103 programStats.depth,
\r
11104 ((float)programStats.score)/100.0,
\r
11106 only_one_move(lst)?
\r
11107 xtra[programStats.got_fail] : "",
\r
11108 programStats.nodes, (int)nps, h, m, s, cs);
\r
11111 DisplayAnalysisText(buf);
\r
11115 DisplayComment(moveNumber, text)
\r
11119 char title[MSG_SIZ];
\r
11121 if( appData.autoDisplayComment ) {
\r
11122 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11123 strcpy(title, "Comment");
\r
11125 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
11126 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11127 parseList[moveNumber]);
\r
11130 CommentPopUp(title, text);
\r
11134 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
11135 * might be busy thinking or pondering. It can be omitted if your
\r
11136 * gnuchess is configured to stop thinking immediately on any user
\r
11137 * input. However, that gnuchess feature depends on the FIONREAD
\r
11138 * ioctl, which does not work properly on some flavors of Unix.
\r
11142 ChessProgramState *cps;
\r
11145 if (!cps->useSigint) return;
\r
11146 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
11147 switch (gameMode) {
\r
11148 case MachinePlaysWhite:
\r
11149 case MachinePlaysBlack:
\r
11150 case TwoMachinesPlay:
\r
11151 case IcsPlayingWhite:
\r
11152 case IcsPlayingBlack:
\r
11153 case AnalyzeMode:
\r
11154 case AnalyzeFile:
\r
11155 /* Skip if we know it isn't thinking */
\r
11156 if (!cps->maybeThinking) return;
\r
11157 if (appData.debugMode)
\r
11158 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
11159 InterruptChildProcess(cps->pr);
\r
11160 cps->maybeThinking = FALSE;
\r
11165 #endif /*ATTENTION*/
\r
11171 if (whiteTimeRemaining <= 0) {
\r
11172 if (!whiteFlag) {
\r
11173 whiteFlag = TRUE;
\r
11174 if (appData.icsActive) {
\r
11175 if (appData.autoCallFlag &&
\r
11176 gameMode == IcsPlayingBlack && !blackFlag) {
\r
11177 SendToICS(ics_prefix);
\r
11178 SendToICS("flag\n");
\r
11182 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11184 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
11185 if (appData.autoCallFlag) {
\r
11186 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
11193 if (blackTimeRemaining <= 0) {
\r
11194 if (!blackFlag) {
\r
11195 blackFlag = TRUE;
\r
11196 if (appData.icsActive) {
\r
11197 if (appData.autoCallFlag &&
\r
11198 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
11199 SendToICS(ics_prefix);
\r
11200 SendToICS("flag\n");
\r
11204 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11206 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
11207 if (appData.autoCallFlag) {
\r
11208 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
11219 CheckTimeControl()
\r
11221 if (!appData.clockMode || appData.icsActive ||
\r
11222 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
11224 if (timeIncrement >= 0) {
\r
11225 if (WhiteOnMove(forwardMostMove)) {
\r
11226 blackTimeRemaining += timeIncrement;
\r
11228 whiteTimeRemaining += timeIncrement;
\r
11232 * add time to clocks when time control is achieved
\r
11234 if (movesPerSession) {
\r
11235 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
\r
11237 /* White made time control */
\r
11238 whiteTimeRemaining += GetTimeControlForWhite();
\r
11241 /* Black made time control */
\r
11242 blackTimeRemaining += GetTimeControlForBlack();
\r
11251 DisplayBothClocks()
\r
11253 int wom = gameMode == EditPosition ?
\r
11254 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
11255 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
11256 DisplayBlackClock(blackTimeRemaining, !wom);
\r
11260 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
11261 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
11262 to use other calls if you don't. Clocks will be less accurate if
\r
11263 you have neither ftime nor gettimeofday.
\r
11266 /* Get the current time as a TimeMark */
\r
11271 #if HAVE_GETTIMEOFDAY
\r
11273 struct timeval timeVal;
\r
11274 struct timezone timeZone;
\r
11276 gettimeofday(&timeVal, &timeZone);
\r
11277 tm->sec = (long) timeVal.tv_sec;
\r
11278 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
11280 #else /*!HAVE_GETTIMEOFDAY*/
\r
11283 #include <sys/timeb.h>
\r
11284 struct timeb timeB;
\r
11287 tm->sec = (long) timeB.time;
\r
11288 tm->ms = (int) timeB.millitm;
\r
11290 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
11291 tm->sec = (long) time(NULL);
\r
11297 /* Return the difference in milliseconds between two
\r
11298 time marks. We assume the difference will fit in a long!
\r
11301 SubtractTimeMarks(tm2, tm1)
\r
11302 TimeMark *tm2, *tm1;
\r
11304 return 1000L*(tm2->sec - tm1->sec) +
\r
11305 (long) (tm2->ms - tm1->ms);
\r
11310 * Code to manage the game clocks.
\r
11312 * In tournament play, black starts the clock and then white makes a move.
\r
11313 * We give the human user a slight advantage if he is playing white---the
\r
11314 * clocks don't run until he makes his first move, so it takes zero time.
\r
11315 * Also, we don't account for network lag, so we could get out of sync
\r
11316 * with GNU Chess's clock -- but then, referees are always right.
\r
11319 static TimeMark tickStartTM;
\r
11320 static long intendedTickLength;
\r
11323 NextTickLength(timeRemaining)
\r
11324 long timeRemaining;
\r
11326 long nominalTickLength, nextTickLength;
\r
11328 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
11329 nominalTickLength = 100L;
\r
11331 nominalTickLength = 1000L;
\r
11332 nextTickLength = timeRemaining % nominalTickLength;
\r
11333 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
11335 return nextTickLength;
\r
11338 /* Adjust clock one minute up or down */
\r
11340 AdjustClock(Boolean which, int dir)
\r
11342 if(which) blackTimeRemaining += 60000*dir;
\r
11343 else whiteTimeRemaining += 60000*dir;
\r
11344 DisplayBothClocks();
\r
11347 /* Stop clocks and reset to a fresh time control */
\r
11351 (void) StopClockTimer();
\r
11352 if (appData.icsActive) {
\r
11353 whiteTimeRemaining = blackTimeRemaining = 0;
\r
11355 whiteTimeRemaining = GetTimeControlForWhite();
\r
11356 blackTimeRemaining = GetTimeControlForBlack();
\r
11358 if (whiteFlag || blackFlag) {
\r
11359 DisplayTitle("");
\r
11360 whiteFlag = blackFlag = FALSE;
\r
11362 DisplayBothClocks();
\r
11365 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
11367 /* Decrement running clock by amount of time that has passed */
\r
11369 DecrementClocks()
\r
11371 long timeRemaining;
\r
11372 long lastTickLength, fudge;
\r
11375 if (!appData.clockMode) return;
\r
11376 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11378 GetTimeMark(&now);
\r
11380 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11382 /* Fudge if we woke up a little too soon */
\r
11383 fudge = intendedTickLength - lastTickLength;
\r
11384 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
11386 if (WhiteOnMove(forwardMostMove)) {
\r
11387 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
11388 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
11389 WhiteOnMove(currentMove));
\r
11391 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
11392 DisplayBlackClock(blackTimeRemaining - fudge,
\r
11393 !WhiteOnMove(currentMove));
\r
11396 if (CheckFlags()) return;
\r
11398 tickStartTM = now;
\r
11399 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
11400 StartClockTimer(intendedTickLength);
\r
11402 /* if the time remaining has fallen below the alarm threshold, sound the
\r
11403 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
11404 * with increment) the time remaining has increased to a level above the
\r
11405 * threshold, reset the alarm so it can sound again.
\r
11408 if (appData.icsActive && appData.icsAlarm) {
\r
11410 /* make sure we are dealing with the user's clock */
\r
11411 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
11412 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
11415 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
11416 alarmSounded = FALSE;
\r
11417 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
11418 PlayAlarmSound();
\r
11419 alarmSounded = TRUE;
\r
11425 /* A player has just moved, so stop the previously running
\r
11426 clock and (if in clock mode) start the other one.
\r
11427 We redisplay both clocks in case we're in ICS mode, because
\r
11428 ICS gives us an update to both clocks after every move.
\r
11429 Note that this routine is called *after* forwardMostMove
\r
11430 is updated, so the last fractional tick must be subtracted
\r
11431 from the color that is *not* on move now.
\r
11436 long lastTickLength;
\r
11438 int flagged = FALSE;
\r
11440 GetTimeMark(&now);
\r
11442 if (StopClockTimer() && appData.clockMode) {
\r
11443 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11444 if (WhiteOnMove(forwardMostMove)) {
\r
11445 blackTimeRemaining -= lastTickLength;
\r
11447 whiteTimeRemaining -= lastTickLength;
\r
11449 /* [HGM] save time for PGN file if engine did not give it */
\r
11450 if(pvInfoList[forwardMostMove-1].time == -1)
\r
11451 pvInfoList[forwardMostMove-1].time = lastTickLength/100;
\r
11452 flagged = CheckFlags();
\r
11454 CheckTimeControl();
\r
11456 if (flagged || !appData.clockMode) return;
\r
11458 switch (gameMode) {
\r
11459 case MachinePlaysBlack:
\r
11460 case MachinePlaysWhite:
\r
11461 case BeginningOfGame:
\r
11462 if (pausing) return;
\r
11466 case PlayFromGameFile:
\r
11467 case IcsExamining:
\r
11474 tickStartTM = now;
\r
11475 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11476 whiteTimeRemaining : blackTimeRemaining);
\r
11477 StartClockTimer(intendedTickLength);
\r
11481 /* Stop both clocks */
\r
11485 long lastTickLength;
\r
11488 if (!StopClockTimer()) return;
\r
11489 if (!appData.clockMode) return;
\r
11491 GetTimeMark(&now);
\r
11493 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11494 if (WhiteOnMove(forwardMostMove)) {
\r
11495 whiteTimeRemaining -= lastTickLength;
\r
11496 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
11498 blackTimeRemaining -= lastTickLength;
\r
11499 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
11504 /* Start clock of player on move. Time may have been reset, so
\r
11505 if clock is already running, stop and restart it. */
\r
11509 (void) StopClockTimer(); /* in case it was running already */
\r
11510 DisplayBothClocks();
\r
11511 if (CheckFlags()) return;
\r
11513 if (!appData.clockMode) return;
\r
11514 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11516 GetTimeMark(&tickStartTM);
\r
11517 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11518 whiteTimeRemaining : blackTimeRemaining);
\r
11519 StartClockTimer(intendedTickLength);
\r
11526 long second, minute, hour, day;
\r
11528 static char buf[32];
\r
11530 if (ms > 0 && ms <= 9900) {
\r
11531 /* convert milliseconds to tenths, rounding up */
\r
11532 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
11534 sprintf(buf, " %03.1f ", tenths/10.0);
\r
11538 /* convert milliseconds to seconds, rounding up */
\r
11539 /* use floating point to avoid strangeness of integer division
\r
11540 with negative dividends on many machines */
\r
11541 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
11543 if (second < 0) {
\r
11545 second = -second;
\r
11548 day = second / (60 * 60 * 24);
\r
11549 second = second % (60 * 60 * 24);
\r
11550 hour = second / (60 * 60);
\r
11551 second = second % (60 * 60);
\r
11552 minute = second / 60;
\r
11553 second = second % 60;
\r
11556 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
11557 sign, day, hour, minute, second);
\r
11558 else if (hour > 0)
\r
11559 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
11561 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
11568 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
11571 StrStr(string, match)
\r
11572 char *string, *match;
\r
11576 length = strlen(match);
\r
11578 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
11579 if (!strncmp(match, string, length))
\r
11586 StrCaseStr(string, match)
\r
11587 char *string, *match;
\r
11589 int i, j, length;
\r
11591 length = strlen(match);
\r
11593 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
11594 for (j = 0; j < length; j++) {
\r
11595 if (ToLower(match[j]) != ToLower(string[j]))
\r
11598 if (j == length) return string;
\r
11604 #ifndef _amigados
\r
11606 StrCaseCmp(s1, s2)
\r
11612 c1 = ToLower(*s1++);
\r
11613 c2 = ToLower(*s2++);
\r
11614 if (c1 > c2) return 1;
\r
11615 if (c1 < c2) return -1;
\r
11616 if (c1 == NULLCHAR) return 0;
\r
11625 return isupper(c) ? tolower(c) : c;
\r
11633 return islower(c) ? toupper(c) : c;
\r
11635 #endif /* !_amigados */
\r
11643 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
11650 StrSavePtr(s, savePtr)
\r
11651 char *s, **savePtr;
\r
11656 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
11657 strcpy(*savePtr, s);
\r
11659 return(*savePtr);
\r
11667 char buf[MSG_SIZ];
\r
11669 clock = time((time_t *)NULL);
\r
11670 tm = localtime(&clock);
\r
11671 sprintf(buf, "%04d.%02d.%02d",
\r
11672 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
11673 return StrSave(buf);
\r
11678 PositionToFEN(move, useFEN960)
\r
11682 int i, j, fromX, fromY, toX, toY;
\r
11687 ChessSquare piece;
\r
11689 whiteToPlay = (gameMode == EditPosition) ?
\r
11690 !blackPlaysFirst : (move % 2 == 0);
\r
11693 /* Piece placement data */
\r
11694 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11696 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11697 if (boards[move][i][j] == EmptySquare) {
\r
11699 } else { ChessSquare piece = boards[move][i][j];
\r
11700 if (emptycount > 0) {
\r
11701 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11702 *p++ = '0' + emptycount;
\r
11703 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11706 if(gameInfo.variant == VariantShogi) {
\r
11707 /* [HGM] write Shogi promoted pieces as +<unpromoted> */
\r
11708 if( (int)piece > (int) WhiteCannon && (int)piece < (int) WhiteKing ||
\r
11709 (int)piece > (int) BlackCannon && (int)piece < (int) BlackKing ) {
\r
11711 piece = (ChessSquare)(DEMOTED piece);
\r
11714 *p++ = PieceToChar(piece);
\r
11715 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantBughouse) {
\r
11716 /* [HGM] flag Crazyhouse promoted pieces */
\r
11717 if( (int)piece > (int) WhiteQueen && (int)piece < (int) WhiteKing ||
\r
11718 (int)piece > (int) BlackQueen && (int)piece < (int) BlackKing ) {
\r
11719 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
11725 if (emptycount > 0) {
\r
11726 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11727 *p++ = '0' + emptycount;
\r
11728 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11735 /* Active color */
\r
11736 *p++ = whiteToPlay ? 'w' : 'b';
\r
11739 /* HACK: we don't keep track of castling availability, so fake it! */
\r
11740 /* Tord! please fix with the aid of castlingRights[move][...] */
\r
11742 /* PUSH Fabien & Tord */
\r
11744 /* Declare all potential FRC castling rights (conservative) */
\r
11745 /* outermost rook on each side of the king */
\r
11747 if( gameInfo.variant == VariantFischeRandom ) {
\r
11752 /* White castling rights */
\r
11754 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11756 if (boards[move][0][fk] == WhiteKing) {
\r
11758 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11759 if (boards[move][0][fr] == WhiteRook) {
\r
11760 *p++ = useFEN960 ? 'A' + fr : 'K';
\r
11765 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11766 if (boards[move][0][fr] == WhiteRook) {
\r
11767 *p++ = useFEN960 ? 'A' + fr : 'Q';
\r
11774 /* Black castling rights */
\r
11776 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11778 if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {
\r
11780 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11781 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11782 *p++ = useFEN960 ? 'a' + fr : 'k';
\r
11787 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11788 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11789 *p++ = useFEN960 ? 'a' + fr : 'q';
\r
11796 if (q == p) *p++ = '-'; /* No castling rights */
\r
11802 #ifdef OLDCASTLINGCODE
\r
11803 if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {
\r
11804 if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';
\r
11805 if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';
\r
11807 if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {
\r
11808 if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';
\r
11809 if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';
\r
11812 /* [HGM] write true castling rights */
\r
11813 if( nrCastlingRights == 6 ) {
\r
11814 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
11815 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
11816 if(castlingRights[move][1] == BOARD_LEFT &&
\r
11817 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
11818 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
11819 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
11820 if(castlingRights[move][4] == BOARD_LEFT &&
\r
11821 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
11824 if (q == p) *p++ = '-';
\r
11828 /* POP Fabien & Tord */
\r
11830 /* En passant target square */
\r
11831 if (move > backwardMostMove) {
\r
11832 fromX = moveList[move - 1][0] - AAA;
\r
11833 fromY = moveList[move - 1][1] - ONE;
\r
11834 toX = moveList[move - 1][2] - AAA;
\r
11835 toY = moveList[move - 1][3] - ONE;
\r
11836 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
11837 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
11838 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
11840 /* 2-square pawn move just happened */
\r
11841 *p++ = toX + AAA;
\r
11842 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
11850 /* [HGM] print Crazyhouse or Shogi holdings */
\r
11851 if( gameInfo.holdingsWidth ) {
\r
11852 *p++ = ' '; q = p;
\r
11853 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
11854 piece = boards[move][i][BOARD_WIDTH-1];
\r
11855 if( piece != EmptySquare )
\r
11856 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
11857 *p++ = PieceToChar(piece);
\r
11859 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
11860 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
11861 if( piece != EmptySquare )
\r
11862 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
11863 *p++ = PieceToChar(piece);
\r
11866 if( q == p ) *p++ = '-';
\r
11870 /* [HGM] find reversible plies */
\r
11871 { int i = 0, j=move;
\r
11873 if (appData.debugMode) { int k;
\r
11874 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
11875 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
11876 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
11880 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
11881 if( j == backwardMostMove ) i += initialRulePlies;
\r
11882 sprintf(p, " %d", i);
\r
11883 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
11885 /* Fullmove number */
\r
11886 sprintf(p, " %d", (move / 2) + 1);
\r
11888 return StrSave(buf);
\r
11892 ParseFEN(board, blackPlaysFirst, fen)
\r
11894 int *blackPlaysFirst;
\r
11900 ChessSquare piece;
\r
11904 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
11905 if(gameInfo.holdingsWidth) {
\r
11906 for(i=0; i<BOARD_HEIGHT; i++) {
\r
11907 board[i][0] = EmptySquare; /* black holdings */
\r
11908 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
11909 board[i][1] = (ChessSquare) 0; /* black counts */
\r
11910 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
11914 /* Piece placement data */
\r
11915 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11918 if (*p == '/' || *p == ' ') {
\r
11919 if (*p == '/') p++;
\r
11920 emptycount = gameInfo.boardWidth - j;
\r
11921 while (emptycount--)
\r
11922 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11924 #if(BOARD_SIZE >= 10)
\r
11925 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
11926 p++; emptycount=10;
\r
11927 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11928 while (emptycount--)
\r
11929 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11931 } else if (isdigit(*p)) {
\r
11932 emptycount = *p++ - '0';
\r
11933 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
11934 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
11935 while (emptycount--)
\r
11936 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11937 } else if (*p == '+' || isalpha(*p)) {
\r
11938 if (j >= gameInfo.boardWidth) return FALSE;
\r
11939 if(*p=='+') { piece = (ChessSquare) (PROMOTED CharToPiece(*++p) ); p++; }
\r
11940 else piece = CharToPiece(*p++);
\r
11941 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
11942 piece = (ChessSquare) (PROMOTED piece);
\r
11945 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
11951 while (*p == '/' || *p == ' ') p++;
\r
11953 /* Active color */
\r
11956 *blackPlaysFirst = FALSE;
\r
11959 *blackPlaysFirst = TRUE;
\r
11965 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
11966 /* return the extra info in global variiables */
\r
11968 /* set defaults in case FEN is incomplete */
\r
11969 FENepStatus = EP_UNKNOWN;
\r
11970 for(i=0; i<nrCastlingRights; i++ ) {
\r
11971 FENcastlingRights[i] = initialRights[i];
\r
11972 } /* assume possible unless obviously impossible */
\r
11973 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
11974 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
11975 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
11976 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
11977 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
11978 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
11979 FENrulePlies = 0;
\r
11981 while(*p==' ') p++;
\r
11983 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11984 /* castling indicator present, so default is no castlings */
\r
11985 for(i=0; i<nrCastlingRights; i++ ) {
\r
11986 FENcastlingRights[i] = -1;
\r
11989 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11992 FENcastlingRights[0] = BOARD_RGHT-1;
\r
11993 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11996 FENcastlingRights[1] = BOARD_LEFT;
\r
11997 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
12000 FENcastlingRights[3] = BOARD_RGHT-1;
\r
12001 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
12004 FENcastlingRights[4] = BOARD_LEFT;
\r
12005 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
12011 while(*p==' ') p++;
\r
12015 p++; FENepStatus = EP_NONE;
\r
12017 char c = *p++ - AAA;
\r
12019 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
12020 if(*p >= '0' && *p <='9') *p++;
\r
12024 /* [HGM] look for Crazyhouse holdings here */
\r
12025 while(*p==' ') p++;
\r
12026 if( !isdigit(*p) ) {
\r
12027 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
12028 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
12029 /* if we would allow FEN reading to set board size, we would */
\r
12030 /* have to add holdings and shift the board read so far here */
\r
12031 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
12033 if((int) piece >= (int) BlackPawn ) {
\r
12034 i = (int)piece - (int)BlackPawn;
\r
12035 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12036 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
12037 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
12039 i = (int)piece - (int)WhitePawn;
\r
12040 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12041 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
12042 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
12050 if(sscanf(p, "%d", &i) == 1) {
\r
12051 FENrulePlies = i; /* 50-move ply counter */
\r
12052 /* (The move number is still ignored) */
\r
12059 EditPositionPasteFEN(char *fen)
\r
12061 if (fen != NULL) {
\r
12062 Board initial_position;
\r
12064 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
12065 DisplayError("Bad FEN position in clipboard", 0);
\r
12068 int savedBlackPlaysFirst = blackPlaysFirst;
\r
12069 EditPositionEvent();
\r
12070 blackPlaysFirst = savedBlackPlaysFirst;
\r
12071 CopyBoard(boards[0], initial_position);
\r
12072 /* [HGM] copy FEN attributes as well */
\r
12074 initialRulePlies = FENrulePlies;
\r
12075 epStatus[0] = FENepStatus;
\r
12076 for( i=0; i<nrCastlingRights; i++ )
\r
12077 castlingRights[0][i] = FENcastlingRights[i];
\r
12079 EditPositionDone();
\r
12080 DisplayBothClocks();
\r
12081 DrawPosition(FALSE, boards[currentMove]);
\r