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
1793 if(board != boards[0]) InitPosition(FALSE);
\r
1795 } else gameInfo.variant = newVariant;
\r
1798 static int loggedOn = FALSE;
\r
1800 /*-- Game start info cache: --*/
\r
1802 char gs_kind[MSG_SIZ];
\r
1803 static char player1Name[128] = "";
\r
1804 static char player2Name[128] = "";
\r
1805 static int player1Rating = -1;
\r
1806 static int player2Rating = -1;
\r
1807 /*----------------------------*/
\r
1809 ColorClass curColor = ColorNormal;
\r
1812 read_from_ics(isr, closure, data, count, error)
\r
1813 InputSourceRef isr;
\r
1819 #define BUF_SIZE 8192
\r
1820 #define STARTED_NONE 0
\r
1821 #define STARTED_MOVES 1
\r
1822 #define STARTED_BOARD 2
\r
1823 #define STARTED_OBSERVE 3
\r
1824 #define STARTED_HOLDINGS 4
\r
1825 #define STARTED_CHATTER 5
\r
1826 #define STARTED_COMMENT 6
\r
1827 #define STARTED_MOVES_NOHIDE 7
\r
1829 static int started = STARTED_NONE;
\r
1830 static char parse[20000];
\r
1831 static int parse_pos = 0;
\r
1832 static char buf[BUF_SIZE + 1];
\r
1833 static int firstTime = TRUE, intfSet = FALSE;
\r
1834 static ColorClass prevColor = ColorNormal;
\r
1835 static int savingComment = FALSE;
\r
1844 if (appData.debugMode) {
\r
1846 fprintf(debugFP, "<ICS: ");
\r
1847 show_bytes(debugFP, data, count);
\r
1848 fprintf(debugFP, "\n");
\r
1854 /* If last read ended with a partial line that we couldn't parse,
\r
1855 prepend it to the new read and try again. */
\r
1856 if (leftover_len > 0) {
\r
1857 for (i=0; i<leftover_len; i++)
\r
1858 buf[i] = buf[leftover_start + i];
\r
1861 /* Copy in new characters, removing nulls and \r's */
\r
1862 buf_len = leftover_len;
\r
1863 for (i = 0; i < count; i++) {
\r
1864 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1865 buf[buf_len++] = data[i];
\r
1868 buf[buf_len] = NULLCHAR;
\r
1869 next_out = leftover_len;
\r
1870 leftover_start = 0;
\r
1873 while (i < buf_len) {
\r
1874 /* Deal with part of the TELNET option negotiation
\r
1875 protocol. We refuse to do anything beyond the
\r
1876 defaults, except that we allow the WILL ECHO option,
\r
1877 which ICS uses to turn off password echoing when we are
\r
1878 directly connected to it. We reject this option
\r
1879 if localLineEditing mode is on (always on in xboard)
\r
1880 and we are talking to port 23, which might be a real
\r
1881 telnet server that will try to keep WILL ECHO on permanently.
\r
1883 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1884 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1885 unsigned char option;
\r
1887 switch ((unsigned char) buf[++i]) {
\r
1889 if (appData.debugMode)
\r
1890 fprintf(debugFP, "\n<WILL ");
\r
1891 switch (option = (unsigned char) buf[++i]) {
\r
1893 if (appData.debugMode)
\r
1894 fprintf(debugFP, "ECHO ");
\r
1895 /* Reply only if this is a change, according
\r
1896 to the protocol rules. */
\r
1897 if (remoteEchoOption) break;
\r
1898 if (appData.localLineEditing &&
\r
1899 atoi(appData.icsPort) == TN_PORT) {
\r
1900 TelnetRequest(TN_DONT, TN_ECHO);
\r
1903 TelnetRequest(TN_DO, TN_ECHO);
\r
1904 remoteEchoOption = TRUE;
\r
1908 if (appData.debugMode)
\r
1909 fprintf(debugFP, "%d ", option);
\r
1910 /* Whatever this is, we don't want it. */
\r
1911 TelnetRequest(TN_DONT, option);
\r
1916 if (appData.debugMode)
\r
1917 fprintf(debugFP, "\n<WONT ");
\r
1918 switch (option = (unsigned char) buf[++i]) {
\r
1920 if (appData.debugMode)
\r
1921 fprintf(debugFP, "ECHO ");
\r
1922 /* Reply only if this is a change, according
\r
1923 to the protocol rules. */
\r
1924 if (!remoteEchoOption) break;
\r
1926 TelnetRequest(TN_DONT, TN_ECHO);
\r
1927 remoteEchoOption = FALSE;
\r
1930 if (appData.debugMode)
\r
1931 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1932 /* Whatever this is, it must already be turned
\r
1933 off, because we never agree to turn on
\r
1934 anything non-default, so according to the
\r
1935 protocol rules, we don't reply. */
\r
1940 if (appData.debugMode)
\r
1941 fprintf(debugFP, "\n<DO ");
\r
1942 switch (option = (unsigned char) buf[++i]) {
\r
1944 /* Whatever this is, we refuse to do it. */
\r
1945 if (appData.debugMode)
\r
1946 fprintf(debugFP, "%d ", option);
\r
1947 TelnetRequest(TN_WONT, option);
\r
1952 if (appData.debugMode)
\r
1953 fprintf(debugFP, "\n<DONT ");
\r
1954 switch (option = (unsigned char) buf[++i]) {
\r
1956 if (appData.debugMode)
\r
1957 fprintf(debugFP, "%d ", option);
\r
1958 /* Whatever this is, we are already not doing
\r
1959 it, because we never agree to do anything
\r
1960 non-default, so according to the protocol
\r
1961 rules, we don't reply. */
\r
1966 if (appData.debugMode)
\r
1967 fprintf(debugFP, "\n<IAC ");
\r
1968 /* Doubled IAC; pass it through */
\r
1972 if (appData.debugMode)
\r
1973 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1974 /* Drop all other telnet commands on the floor */
\r
1977 if (oldi > next_out)
\r
1978 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1979 if (++i > next_out)
\r
1984 /* OK, this at least will *usually* work */
\r
1985 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1989 if (loggedOn && !intfSet) {
\r
1990 if (ics_type == ICS_ICC) {
\r
1992 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1995 } else if (ics_type == ICS_CHESSNET) {
\r
1996 sprintf(str, "/style 12\n");
\r
1998 strcpy(str, "alias $ @\n$set interface ");
\r
1999 strcat(str, programVersion);
\r
2000 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2002 strcat(str, "$iset nohighlight 1\n");
\r
2004 strcat(str, "$iset lock 1\n$style 12\n");
\r
2010 if (started == STARTED_COMMENT) {
\r
2011 /* Accumulate characters in comment */
\r
2012 parse[parse_pos++] = buf[i];
\r
2013 if (buf[i] == '\n') {
\r
2014 parse[parse_pos] = NULLCHAR;
\r
2015 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2016 started = STARTED_NONE;
\r
2018 /* Don't match patterns against characters in chatter */
\r
2023 if (started == STARTED_CHATTER) {
\r
2024 if (buf[i] != '\n') {
\r
2025 /* Don't match patterns against characters in chatter */
\r
2029 started = STARTED_NONE;
\r
2032 /* Kludge to deal with rcmd protocol */
\r
2033 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2034 DisplayFatalError(&buf[1], 0, 1);
\r
2037 firstTime = FALSE;
\r
2040 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2041 ics_type = ICS_ICC;
\r
2043 if (appData.debugMode)
\r
2044 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2047 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2048 ics_type = ICS_FICS;
\r
2050 if (appData.debugMode)
\r
2051 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2054 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2055 ics_type = ICS_CHESSNET;
\r
2057 if (appData.debugMode)
\r
2058 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2063 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2064 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2065 looking_at(buf, &i, "will be \"*\""))) {
\r
2066 strcpy(ics_handle, star_match[0]);
\r
2070 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2071 char buf[MSG_SIZ];
\r
2072 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2073 DisplayIcsInteractionTitle(buf);
\r
2074 have_set_title = TRUE;
\r
2077 /* skip finger notes */
\r
2078 if (started == STARTED_NONE &&
\r
2079 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2080 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2081 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2082 started = STARTED_CHATTER;
\r
2087 /* skip formula vars */
\r
2088 if (started == STARTED_NONE &&
\r
2089 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2090 started = STARTED_CHATTER;
\r
2096 if (appData.zippyTalk || appData.zippyPlay) {
\r
2098 if (ZippyControl(buf, &i) ||
\r
2099 ZippyConverse(buf, &i) ||
\r
2100 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2106 if (/* Don't color "message" or "messages" output */
\r
2107 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2108 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2109 looking_at(buf, &i, "--* (*:*): ") ||
\r
2110 /* Regular tells and says */
\r
2111 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2112 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2113 looking_at(buf, &i, "* says: ") ||
\r
2114 /* Message notifications (same color as tells) */
\r
2115 looking_at(buf, &i, "* has left a message ") ||
\r
2116 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2117 /* Whispers and kibitzes */
\r
2118 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2119 looking_at(buf, &i, "* kibitzes: ") ||
\r
2120 /* Channel tells */
\r
2121 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2123 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2124 /* Avoid "tells you:" spoofs in channels */
\r
2127 if (star_match[0][0] == NULLCHAR ||
\r
2128 strchr(star_match[0], ' ') ||
\r
2129 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2130 /* Reject bogus matches */
\r
2133 if (appData.colorize) {
\r
2134 if (oldi > next_out) {
\r
2135 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2140 Colorize(ColorTell, FALSE);
\r
2141 curColor = ColorTell;
\r
2144 Colorize(ColorKibitz, FALSE);
\r
2145 curColor = ColorKibitz;
\r
2148 p = strrchr(star_match[1], '(');
\r
2150 p = star_match[1];
\r
2154 if (atoi(p) == 1) {
\r
2155 Colorize(ColorChannel1, FALSE);
\r
2156 curColor = ColorChannel1;
\r
2158 Colorize(ColorChannel, FALSE);
\r
2159 curColor = ColorChannel;
\r
2163 curColor = ColorNormal;
\r
2167 if (started == STARTED_NONE && appData.autoComment &&
\r
2168 (gameMode == IcsObserving ||
\r
2169 gameMode == IcsPlayingWhite ||
\r
2170 gameMode == IcsPlayingBlack)) {
\r
2171 parse_pos = i - oldi;
\r
2172 memcpy(parse, &buf[oldi], parse_pos);
\r
2173 parse[parse_pos] = NULLCHAR;
\r
2174 started = STARTED_COMMENT;
\r
2175 savingComment = TRUE;
\r
2177 started = STARTED_CHATTER;
\r
2178 savingComment = FALSE;
\r
2185 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2186 looking_at(buf, &i, "* c-shouts: ")) {
\r
2187 if (appData.colorize) {
\r
2188 if (oldi > next_out) {
\r
2189 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2192 Colorize(ColorSShout, FALSE);
\r
2193 curColor = ColorSShout;
\r
2196 started = STARTED_CHATTER;
\r
2200 if (looking_at(buf, &i, "--->")) {
\r
2205 if (looking_at(buf, &i, "* shouts: ") ||
\r
2206 looking_at(buf, &i, "--> ")) {
\r
2207 if (appData.colorize) {
\r
2208 if (oldi > next_out) {
\r
2209 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2212 Colorize(ColorShout, FALSE);
\r
2213 curColor = ColorShout;
\r
2216 started = STARTED_CHATTER;
\r
2220 if (looking_at( buf, &i, "Challenge:")) {
\r
2221 if (appData.colorize) {
\r
2222 if (oldi > next_out) {
\r
2223 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2226 Colorize(ColorChallenge, FALSE);
\r
2227 curColor = ColorChallenge;
\r
2233 if (looking_at(buf, &i, "* offers you") ||
\r
2234 looking_at(buf, &i, "* offers to be") ||
\r
2235 looking_at(buf, &i, "* would like to") ||
\r
2236 looking_at(buf, &i, "* requests to") ||
\r
2237 looking_at(buf, &i, "Your opponent offers") ||
\r
2238 looking_at(buf, &i, "Your opponent requests")) {
\r
2240 if (appData.colorize) {
\r
2241 if (oldi > next_out) {
\r
2242 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2245 Colorize(ColorRequest, FALSE);
\r
2246 curColor = ColorRequest;
\r
2251 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2252 if (appData.colorize) {
\r
2253 if (oldi > next_out) {
\r
2254 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2257 Colorize(ColorSeek, FALSE);
\r
2258 curColor = ColorSeek;
\r
2264 if (looking_at(buf, &i, "\\ ")) {
\r
2265 if (prevColor != ColorNormal) {
\r
2266 if (oldi > next_out) {
\r
2267 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2270 Colorize(prevColor, TRUE);
\r
2271 curColor = prevColor;
\r
2273 if (savingComment) {
\r
2274 parse_pos = i - oldi;
\r
2275 memcpy(parse, &buf[oldi], parse_pos);
\r
2276 parse[parse_pos] = NULLCHAR;
\r
2277 started = STARTED_COMMENT;
\r
2279 started = STARTED_CHATTER;
\r
2284 if (looking_at(buf, &i, "Black Strength :") ||
\r
2285 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2286 looking_at(buf, &i, "<10>") ||
\r
2287 looking_at(buf, &i, "#@#")) {
\r
2288 /* Wrong board style */
\r
2290 SendToICS(ics_prefix);
\r
2291 SendToICS("set style 12\n");
\r
2292 SendToICS(ics_prefix);
\r
2293 SendToICS("refresh\n");
\r
2297 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2299 have_sent_ICS_logon = 1;
\r
2303 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2304 (looking_at(buf, &i, "\n<12> ") ||
\r
2305 looking_at(buf, &i, "<12> "))) {
\r
2307 if (oldi > next_out) {
\r
2308 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2311 started = STARTED_BOARD;
\r
2316 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2317 looking_at(buf, &i, "<b1> ")) {
\r
2318 if (oldi > next_out) {
\r
2319 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2322 started = STARTED_HOLDINGS;
\r
2327 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2329 /* Header for a move list -- first line */
\r
2331 switch (ics_getting_history) {
\r
2333 switch (gameMode) {
\r
2335 case BeginningOfGame:
\r
2336 /* User typed "moves" or "oldmoves" while we
\r
2337 were idle. Pretend we asked for these
\r
2338 moves and soak them up so user can step
\r
2339 through them and/or save them.
\r
2341 Reset(FALSE, TRUE);
\r
2342 gameMode = IcsObserving;
\r
2345 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2347 case EditGame: /*?*/
\r
2348 case EditPosition: /*?*/
\r
2349 /* Should above feature work in these modes too? */
\r
2350 /* For now it doesn't */
\r
2351 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2354 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2359 /* Is this the right one? */
\r
2360 if (gameInfo.white && gameInfo.black &&
\r
2361 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2362 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2364 ics_getting_history = H_GOT_REQ_HEADER;
\r
2367 case H_GOT_REQ_HEADER:
\r
2368 case H_GOT_UNREQ_HEADER:
\r
2369 case H_GOT_UNWANTED_HEADER:
\r
2370 case H_GETTING_MOVES:
\r
2371 /* Should not happen */
\r
2372 DisplayError("Error gathering move list: two headers", 0);
\r
2373 ics_getting_history = H_FALSE;
\r
2377 /* Save player ratings into gameInfo if needed */
\r
2378 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2379 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2380 (gameInfo.whiteRating == -1 ||
\r
2381 gameInfo.blackRating == -1)) {
\r
2383 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2384 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2385 if (appData.debugMode)
\r
2386 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2387 gameInfo.whiteRating, gameInfo.blackRating);
\r
2392 if (looking_at(buf, &i,
\r
2393 "* * match, initial time: * minute*, increment: * second")) {
\r
2394 /* Header for a move list -- second line */
\r
2395 /* Initial board will follow if this is a wild game */
\r
2396 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2397 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2398 gameInfo.event = StrSave(str);
\r
2399 /* [HGM] we switched variant. Translate boards if needed. */
\r
2400 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2404 if (looking_at(buf, &i, "Move ")) {
\r
2405 /* Beginning of a move list */
\r
2406 switch (ics_getting_history) {
\r
2408 /* Normally should not happen */
\r
2409 /* Maybe user hit reset while we were parsing */
\r
2412 /* Happens if we are ignoring a move list that is not
\r
2413 * the one we just requested. Common if the user
\r
2414 * tries to observe two games without turning off
\r
2417 case H_GETTING_MOVES:
\r
2418 /* Should not happen */
\r
2419 DisplayError("Error gathering move list: nested", 0);
\r
2420 ics_getting_history = H_FALSE;
\r
2422 case H_GOT_REQ_HEADER:
\r
2423 ics_getting_history = H_GETTING_MOVES;
\r
2424 started = STARTED_MOVES;
\r
2426 if (oldi > next_out) {
\r
2427 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2430 case H_GOT_UNREQ_HEADER:
\r
2431 ics_getting_history = H_GETTING_MOVES;
\r
2432 started = STARTED_MOVES_NOHIDE;
\r
2435 case H_GOT_UNWANTED_HEADER:
\r
2436 ics_getting_history = H_FALSE;
\r
2442 if (looking_at(buf, &i, "% ") ||
\r
2443 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2444 && looking_at(buf, &i, "}*"))) {
\r
2445 savingComment = FALSE;
\r
2446 switch (started) {
\r
2447 case STARTED_MOVES:
\r
2448 case STARTED_MOVES_NOHIDE:
\r
2449 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2450 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2451 ParseGameHistory(parse);
\r
2453 if (appData.zippyPlay && first.initDone) {
\r
2454 FeedMovesToProgram(&first, forwardMostMove);
\r
2455 if (gameMode == IcsPlayingWhite) {
\r
2456 if (WhiteOnMove(forwardMostMove)) {
\r
2457 if (first.sendTime) {
\r
2458 if (first.useColors) {
\r
2459 SendToProgram("black\n", &first);
\r
2461 SendTimeRemaining(&first, TRUE);
\r
2463 if (first.useColors) {
\r
2464 SendToProgram("white\ngo\n", &first);
\r
2466 SendToProgram("go\n", &first);
\r
2468 first.maybeThinking = TRUE;
\r
2470 if (first.usePlayother) {
\r
2471 if (first.sendTime) {
\r
2472 SendTimeRemaining(&first, TRUE);
\r
2474 SendToProgram("playother\n", &first);
\r
2475 firstMove = FALSE;
\r
2480 } else if (gameMode == IcsPlayingBlack) {
\r
2481 if (!WhiteOnMove(forwardMostMove)) {
\r
2482 if (first.sendTime) {
\r
2483 if (first.useColors) {
\r
2484 SendToProgram("white\n", &first);
\r
2486 SendTimeRemaining(&first, FALSE);
\r
2488 if (first.useColors) {
\r
2489 SendToProgram("black\ngo\n", &first);
\r
2491 SendToProgram("go\n", &first);
\r
2493 first.maybeThinking = TRUE;
\r
2495 if (first.usePlayother) {
\r
2496 if (first.sendTime) {
\r
2497 SendTimeRemaining(&first, FALSE);
\r
2499 SendToProgram("playother\n", &first);
\r
2500 firstMove = FALSE;
\r
2508 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2509 /* Moves came from oldmoves or moves command
\r
2510 while we weren't doing anything else.
\r
2512 currentMove = forwardMostMove;
\r
2513 ClearHighlights();/*!!could figure this out*/
\r
2514 flipView = appData.flipView;
\r
2515 DrawPosition(FALSE, boards[currentMove]);
\r
2516 DisplayBothClocks();
\r
2517 sprintf(str, "%s vs. %s",
\r
2518 gameInfo.white, gameInfo.black);
\r
2519 DisplayTitle(str);
\r
2520 gameMode = IcsIdle;
\r
2522 /* Moves were history of an active game */
\r
2523 if (gameInfo.resultDetails != NULL) {
\r
2524 free(gameInfo.resultDetails);
\r
2525 gameInfo.resultDetails = NULL;
\r
2528 HistorySet(parseList, backwardMostMove,
\r
2529 forwardMostMove, currentMove-1);
\r
2530 DisplayMove(currentMove - 1);
\r
2531 if (started == STARTED_MOVES) next_out = i;
\r
2532 started = STARTED_NONE;
\r
2533 ics_getting_history = H_FALSE;
\r
2536 case STARTED_OBSERVE:
\r
2537 started = STARTED_NONE;
\r
2538 SendToICS(ics_prefix);
\r
2539 SendToICS("refresh\n");
\r
2548 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2549 started == STARTED_HOLDINGS ||
\r
2550 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2551 /* Accumulate characters in move list or board */
\r
2552 parse[parse_pos++] = buf[i];
\r
2555 /* Start of game messages. Mostly we detect start of game
\r
2556 when the first board image arrives. On some versions
\r
2557 of the ICS, though, we need to do a "refresh" after starting
\r
2558 to observe in order to get the current board right away. */
\r
2559 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2560 started = STARTED_OBSERVE;
\r
2564 /* Handle auto-observe */
\r
2565 if (appData.autoObserve &&
\r
2566 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2567 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2569 /* Choose the player that was highlighted, if any. */
\r
2570 if (star_match[0][0] == '\033' ||
\r
2571 star_match[1][0] != '\033') {
\r
2572 player = star_match[0];
\r
2574 player = star_match[2];
\r
2576 sprintf(str, "%sobserve %s\n",
\r
2577 ics_prefix, StripHighlightAndTitle(player));
\r
2580 /* Save ratings from notify string */
\r
2581 strcpy(player1Name, star_match[0]);
\r
2582 player1Rating = string_to_rating(star_match[1]);
\r
2583 strcpy(player2Name, star_match[2]);
\r
2584 player2Rating = string_to_rating(star_match[3]);
\r
2586 if (appData.debugMode)
\r
2588 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2589 player1Name, player1Rating,
\r
2590 player2Name, player2Rating);
\r
2595 /* Deal with automatic examine mode after a game,
\r
2596 and with IcsObserving -> IcsExamining transition */
\r
2597 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2598 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2600 int gamenum = atoi(star_match[0]);
\r
2601 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2602 gamenum == ics_gamenum) {
\r
2603 /* We were already playing or observing this game;
\r
2604 no need to refetch history */
\r
2605 gameMode = IcsExamining;
\r
2607 pauseExamForwardMostMove = forwardMostMove;
\r
2608 } else if (currentMove < forwardMostMove) {
\r
2609 ForwardInner(forwardMostMove);
\r
2612 /* I don't think this case really can happen */
\r
2613 SendToICS(ics_prefix);
\r
2614 SendToICS("refresh\n");
\r
2619 /* Error messages */
\r
2620 if (ics_user_moved) {
\r
2621 if (looking_at(buf, &i, "Illegal move") ||
\r
2622 looking_at(buf, &i, "Not a legal move") ||
\r
2623 looking_at(buf, &i, "Your king is in check") ||
\r
2624 looking_at(buf, &i, "It isn't your turn") ||
\r
2625 looking_at(buf, &i, "It is not your move")) {
\r
2626 /* Illegal move */
\r
2627 ics_user_moved = 0;
\r
2628 if (forwardMostMove > backwardMostMove) {
\r
2629 currentMove = --forwardMostMove;
\r
2630 DisplayMove(currentMove - 1); /* before DMError */
\r
2631 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2632 DrawPosition(FALSE, boards[currentMove]);
\r
2634 DisplayBothClocks();
\r
2640 if (looking_at(buf, &i, "still have time") ||
\r
2641 looking_at(buf, &i, "not out of time") ||
\r
2642 looking_at(buf, &i, "either player is out of time") ||
\r
2643 looking_at(buf, &i, "has timeseal; checking")) {
\r
2644 /* We must have called his flag a little too soon */
\r
2645 whiteFlag = blackFlag = FALSE;
\r
2649 if (looking_at(buf, &i, "added * seconds to") ||
\r
2650 looking_at(buf, &i, "seconds were added to")) {
\r
2651 /* Update the clocks */
\r
2652 SendToICS(ics_prefix);
\r
2653 SendToICS("refresh\n");
\r
2657 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2658 ics_clock_paused = TRUE;
\r
2663 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2664 ics_clock_paused = FALSE;
\r
2669 /* Grab player ratings from the Creating: message.
\r
2670 Note we have to check for the special case when
\r
2671 the ICS inserts things like [white] or [black]. */
\r
2672 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2673 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2675 0 player 1 name (not necessarily white)
\r
2677 2 empty, white, or black (IGNORED)
\r
2678 3 player 2 name (not necessarily black)
\r
2681 The names/ratings are sorted out when the game
\r
2682 actually starts (below).
\r
2684 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2685 player1Rating = string_to_rating(star_match[1]);
\r
2686 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2687 player2Rating = string_to_rating(star_match[4]);
\r
2689 if (appData.debugMode)
\r
2691 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2692 player1Name, player1Rating,
\r
2693 player2Name, player2Rating);
\r
2698 /* Improved generic start/end-of-game messages */
\r
2699 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2700 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2701 /* If tkind == 0: */
\r
2702 /* star_match[0] is the game number */
\r
2703 /* [1] is the white player's name */
\r
2704 /* [2] is the black player's name */
\r
2705 /* For end-of-game: */
\r
2706 /* [3] is the reason for the game end */
\r
2707 /* [4] is a PGN end game-token, preceded by " " */
\r
2708 /* For start-of-game: */
\r
2709 /* [3] begins with "Creating" or "Continuing" */
\r
2710 /* [4] is " *" or empty (don't care). */
\r
2711 int gamenum = atoi(star_match[0]);
\r
2712 char *whitename, *blackname, *why, *endtoken;
\r
2713 ChessMove endtype = (ChessMove) 0;
\r
2716 whitename = star_match[1];
\r
2717 blackname = star_match[2];
\r
2718 why = star_match[3];
\r
2719 endtoken = star_match[4];
\r
2721 whitename = star_match[1];
\r
2722 blackname = star_match[3];
\r
2723 why = star_match[5];
\r
2724 endtoken = star_match[6];
\r
2727 /* Game start messages */
\r
2728 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2729 strncmp(why, "Continuing ", 11) == 0) {
\r
2730 gs_gamenum = gamenum;
\r
2731 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2733 if (appData.zippyPlay) {
\r
2734 ZippyGameStart(whitename, blackname);
\r
2740 /* Game end messages */
\r
2741 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2742 ics_gamenum != gamenum) {
\r
2745 while (endtoken[0] == ' ') endtoken++;
\r
2746 switch (endtoken[0]) {
\r
2749 endtype = GameUnfinished;
\r
2752 endtype = BlackWins;
\r
2755 if (endtoken[1] == '/')
\r
2756 endtype = GameIsDrawn;
\r
2758 endtype = WhiteWins;
\r
2761 GameEnds(endtype, why, GE_ICS);
\r
2763 if (appData.zippyPlay && first.initDone) {
\r
2764 ZippyGameEnd(endtype, why);
\r
2765 if (first.pr == NULL) {
\r
2766 /* Start the next process early so that we'll
\r
2767 be ready for the next challenge */
\r
2768 StartChessProgram(&first);
\r
2770 /* Send "new" early, in case this command takes
\r
2771 a long time to finish, so that we'll be ready
\r
2772 for the next challenge. */
\r
2773 Reset(TRUE, TRUE);
\r
2779 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2780 looking_at(buf, &i, "no longer observing game *") ||
\r
2781 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2782 if (gameMode == IcsObserving &&
\r
2783 atoi(star_match[0]) == ics_gamenum)
\r
2786 gameMode = IcsIdle;
\r
2788 ics_user_moved = FALSE;
\r
2793 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2794 if (gameMode == IcsExamining &&
\r
2795 atoi(star_match[0]) == ics_gamenum)
\r
2797 gameMode = IcsIdle;
\r
2799 ics_user_moved = FALSE;
\r
2804 /* Advance leftover_start past any newlines we find,
\r
2805 so only partial lines can get reparsed */
\r
2806 if (looking_at(buf, &i, "\n")) {
\r
2807 prevColor = curColor;
\r
2808 if (curColor != ColorNormal) {
\r
2809 if (oldi > next_out) {
\r
2810 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2813 Colorize(ColorNormal, FALSE);
\r
2814 curColor = ColorNormal;
\r
2816 if (started == STARTED_BOARD) {
\r
2817 started = STARTED_NONE;
\r
2818 parse[parse_pos] = NULLCHAR;
\r
2819 ParseBoard12(parse);
\r
2820 ics_user_moved = 0;
\r
2822 /* Send premove here */
\r
2823 if (appData.premove) {
\r
2824 char str[MSG_SIZ];
\r
2825 if (currentMove == 0 &&
\r
2826 gameMode == IcsPlayingWhite &&
\r
2827 appData.premoveWhite) {
\r
2828 sprintf(str, "%s%s\n", ics_prefix,
\r
2829 appData.premoveWhiteText);
\r
2830 if (appData.debugMode)
\r
2831 fprintf(debugFP, "Sending premove:\n");
\r
2833 } else if (currentMove == 1 &&
\r
2834 gameMode == IcsPlayingBlack &&
\r
2835 appData.premoveBlack) {
\r
2836 sprintf(str, "%s%s\n", ics_prefix,
\r
2837 appData.premoveBlackText);
\r
2838 if (appData.debugMode)
\r
2839 fprintf(debugFP, "Sending premove:\n");
\r
2841 } else if (gotPremove) {
\r
2843 ClearPremoveHighlights();
\r
2844 if (appData.debugMode)
\r
2845 fprintf(debugFP, "Sending premove:\n");
\r
2846 UserMoveEvent(premoveFromX, premoveFromY,
\r
2847 premoveToX, premoveToY,
\r
2848 premovePromoChar);
\r
2852 /* Usually suppress following prompt */
\r
2853 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2854 if (looking_at(buf, &i, "*% ")) {
\r
2855 savingComment = FALSE;
\r
2859 } else if (started == STARTED_HOLDINGS) {
\r
2861 char new_piece[MSG_SIZ];
\r
2862 started = STARTED_NONE;
\r
2863 parse[parse_pos] = NULLCHAR;
\r
2864 if (appData.debugMode)
\r
2865 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
2866 parse, currentMove);
\r
2867 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2868 gamenum == ics_gamenum) {
\r
2869 if (gameInfo.variant == VariantNormal) {
\r
2870 /* [HGM] We seem to switch variant during a game!
\r
2871 * Presumably no holdings were displayed, so we have
\r
2872 * to move the position two files to the right to
\r
2873 * create room for them!
\r
2875 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
2876 /* Get a move list just to see the header, which
\r
2877 will tell us whether this is really bug or zh */
\r
2878 if (ics_getting_history == H_FALSE) {
\r
2879 ics_getting_history = H_REQUESTED;
\r
2880 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2884 new_piece[0] = NULLCHAR;
\r
2885 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2886 &gamenum, white_holding, black_holding,
\r
2888 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2889 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2890 /* [HGM] copy holdings to board holdings area */
\r
2891 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2892 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2894 if (appData.zippyPlay && first.initDone) {
\r
2895 ZippyHoldings(white_holding, black_holding,
\r
2899 if (tinyLayout || smallLayout) {
\r
2900 char wh[16], bh[16];
\r
2901 PackHolding(wh, white_holding);
\r
2902 PackHolding(bh, black_holding);
\r
2903 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2904 gameInfo.white, gameInfo.black);
\r
2906 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2907 gameInfo.white, white_holding,
\r
2908 gameInfo.black, black_holding);
\r
2911 DrawPosition(FALSE, boards[currentMove]);
\r
2912 DisplayTitle(str);
\r
2914 /* Suppress following prompt */
\r
2915 if (looking_at(buf, &i, "*% ")) {
\r
2916 savingComment = FALSE;
\r
2923 i++; /* skip unparsed character and loop back */
\r
2926 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2927 started != STARTED_HOLDINGS && i > next_out) {
\r
2928 SendToPlayer(&buf[next_out], i - next_out);
\r
2932 leftover_len = buf_len - leftover_start;
\r
2933 /* if buffer ends with something we couldn't parse,
\r
2934 reparse it after appending the next read */
\r
2936 } else if (count == 0) {
\r
2937 RemoveInputSource(isr);
\r
2938 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2940 DisplayFatalError("Error reading from ICS", error, 1);
\r
2945 /* Board style 12 looks like this:
\r
2947 <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
2949 * The "<12> " is stripped before it gets to this routine. The two
\r
2950 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2951 * some chess servers may not have them, or may have only the first.
\r
2952 * Additional trailing fields may be added in the future.
\r
2955 #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
2957 #define RELATION_OBSERVING_PLAYED 0
\r
2958 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2959 #define RELATION_PLAYING_MYMOVE 1
\r
2960 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2961 #define RELATION_EXAMINING 2
\r
2962 #define RELATION_ISOLATED_BOARD -3
\r
2963 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2966 ParseBoard12(string)
\r
2969 GameMode newGameMode;
\r
2970 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2971 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2972 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2973 char to_play, board_chars[72];
\r
2974 char move_str[500], str[500], elapsed_time[500];
\r
2975 char black[32], white[32];
\r
2977 int prevMove = currentMove;
\r
2979 ChessMove moveType;
\r
2980 int fromX, fromY, toX, toY;
\r
2983 fromX = fromY = toX = toY = -1;
\r
2987 if (appData.debugMode)
\r
2988 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2990 move_str[0] = NULLCHAR;
\r
2991 elapsed_time[0] = NULLCHAR;
\r
2992 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2993 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2994 &gamenum, white, black, &relation, &basetime, &increment,
\r
2995 &white_stren, &black_stren, &white_time, &black_time,
\r
2996 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3000 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
3001 DisplayError(str, 0);
\r
3005 /* Convert the move number to internal form */
\r
3006 moveNum = (moveNum - 1) * 2;
\r
3007 if (to_play == 'B') moveNum++;
\r
3008 if (moveNum >= MAX_MOVES) {
\r
3009 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3014 switch (relation) {
\r
3015 case RELATION_OBSERVING_PLAYED:
\r
3016 case RELATION_OBSERVING_STATIC:
\r
3017 if (gamenum == -1) {
\r
3018 /* Old ICC buglet */
\r
3019 relation = RELATION_OBSERVING_STATIC;
\r
3021 newGameMode = IcsObserving;
\r
3023 case RELATION_PLAYING_MYMOVE:
\r
3024 case RELATION_PLAYING_NOTMYMOVE:
\r
3026 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3027 IcsPlayingWhite : IcsPlayingBlack;
\r
3029 case RELATION_EXAMINING:
\r
3030 newGameMode = IcsExamining;
\r
3032 case RELATION_ISOLATED_BOARD:
\r
3034 /* Just display this board. If user was doing something else,
\r
3035 we will forget about it until the next board comes. */
\r
3036 newGameMode = IcsIdle;
\r
3038 case RELATION_STARTING_POSITION:
\r
3039 newGameMode = gameMode;
\r
3043 /* Modify behavior for initial board display on move listing
\r
3046 switch (ics_getting_history) {
\r
3050 case H_GOT_REQ_HEADER:
\r
3051 case H_GOT_UNREQ_HEADER:
\r
3052 /* This is the initial position of the current game */
\r
3053 gamenum = ics_gamenum;
\r
3054 moveNum = 0; /* old ICS bug workaround */
\r
3055 if (to_play == 'B') {
\r
3056 startedFromSetupPosition = TRUE;
\r
3057 blackPlaysFirst = TRUE;
\r
3059 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3060 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3061 if (currentMove == 0) currentMove = 1;
\r
3063 newGameMode = gameMode;
\r
3064 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3066 case H_GOT_UNWANTED_HEADER:
\r
3067 /* This is an initial board that we don't want */
\r
3069 case H_GETTING_MOVES:
\r
3070 /* Should not happen */
\r
3071 DisplayError("Error gathering move list: extra board", 0);
\r
3072 ics_getting_history = H_FALSE;
\r
3076 /* Take action if this is the first board of a new game, or of a
\r
3077 different game than is currently being displayed. */
\r
3078 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3079 relation == RELATION_ISOLATED_BOARD) {
\r
3081 /* Forget the old game and get the history (if any) of the new one */
\r
3082 if (gameMode != BeginningOfGame) {
\r
3083 Reset(FALSE, TRUE);
\r
3086 if (appData.autoRaiseBoard) BoardToTop();
\r
3088 if (gamenum == -1) {
\r
3089 newGameMode = IcsIdle;
\r
3090 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3091 appData.getMoveList) {
\r
3092 /* Need to get game history */
\r
3093 ics_getting_history = H_REQUESTED;
\r
3094 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3098 /* Initially flip the board to have black on the bottom if playing
\r
3099 black or if the ICS flip flag is set, but let the user change
\r
3100 it with the Flip View button. */
\r
3101 flipView = appData.autoFlipView ?
\r
3102 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3105 /* Done with values from previous mode; copy in new ones */
\r
3106 gameMode = newGameMode;
\r
3108 ics_gamenum = gamenum;
\r
3109 if (gamenum == gs_gamenum) {
\r
3110 int klen = strlen(gs_kind);
\r
3111 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3112 sprintf(str, "ICS %s", gs_kind);
\r
3113 gameInfo.event = StrSave(str);
\r
3115 gameInfo.event = StrSave("ICS game");
\r
3117 gameInfo.site = StrSave(appData.icsHost);
\r
3118 gameInfo.date = PGNDate();
\r
3119 gameInfo.round = StrSave("-");
\r
3120 gameInfo.white = StrSave(white);
\r
3121 gameInfo.black = StrSave(black);
\r
3122 timeControl = basetime * 60 * 1000;
\r
3123 timeControl_2 = 0;
\r
3124 timeIncrement = increment * 1000;
\r
3125 movesPerSession = 0;
\r
3126 gameInfo.timeControl = TimeControlTagValue();
\r
3127 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3128 if (appData.debugMode) {
\r
3129 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3130 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3131 setbuf(debugFP, NULL);
\r
3134 gameInfo.outOfBook = NULL;
\r
3136 /* Do we have the ratings? */
\r
3137 if (strcmp(player1Name, white) == 0 &&
\r
3138 strcmp(player2Name, black) == 0) {
\r
3139 if (appData.debugMode)
\r
3140 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3141 player1Rating, player2Rating);
\r
3142 gameInfo.whiteRating = player1Rating;
\r
3143 gameInfo.blackRating = player2Rating;
\r
3144 } else if (strcmp(player2Name, white) == 0 &&
\r
3145 strcmp(player1Name, black) == 0) {
\r
3146 if (appData.debugMode)
\r
3147 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3148 player2Rating, player1Rating);
\r
3149 gameInfo.whiteRating = player2Rating;
\r
3150 gameInfo.blackRating = player1Rating;
\r
3152 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3154 /* Silence shouts if requested */
\r
3155 if (appData.quietPlay &&
\r
3156 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3157 SendToICS(ics_prefix);
\r
3158 SendToICS("set shout 0\n");
\r
3162 /* Deal with midgame name changes */
\r
3164 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3165 if (gameInfo.white) free(gameInfo.white);
\r
3166 gameInfo.white = StrSave(white);
\r
3168 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3169 if (gameInfo.black) free(gameInfo.black);
\r
3170 gameInfo.black = StrSave(black);
\r
3174 /* Throw away game result if anything actually changes in examine mode */
\r
3175 if (gameMode == IcsExamining && !newGame) {
\r
3176 gameInfo.result = GameUnfinished;
\r
3177 if (gameInfo.resultDetails != NULL) {
\r
3178 free(gameInfo.resultDetails);
\r
3179 gameInfo.resultDetails = NULL;
\r
3183 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3184 in if they are in a different variation than we are. */
\r
3185 if (pauseExamInvalid) return;
\r
3186 if (pausing && gameMode == IcsExamining) {
\r
3187 if (moveNum <= pauseExamForwardMostMove) {
\r
3188 pauseExamInvalid = TRUE;
\r
3189 forwardMostMove = pauseExamForwardMostMove;
\r
3194 /* Parse the board */
\r
3195 for (k = 0; k < 8; k++) {
\r
3196 for (j = 0; j < 8; j++)
\r
3197 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3198 if(gameInfo.holdingsWidth > 1) {
\r
3199 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3200 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3203 CopyBoard(boards[moveNum], board);
\r
3204 if (moveNum == 0) {
\r
3205 startedFromSetupPosition =
\r
3206 !CompareBoards(board, initialPosition);
\r
3209 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3210 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3211 /* This was an initial position from a move list, not
\r
3212 the current position */
\r
3216 /* Update currentMove and known move number limits */
\r
3217 newMove = newGame || moveNum > forwardMostMove;
\r
3219 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3220 if (gameMode == IcsExamining && moveNum == 0) {
\r
3221 /* Workaround for ICS limitation: we are not told the wild
\r
3222 type when starting to examine a game. But if we ask for
\r
3223 the move list, the move list header will tell us */
\r
3224 ics_getting_history = H_REQUESTED;
\r
3225 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3228 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3229 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3230 forwardMostMove = moveNum;
\r
3231 if (!pausing || currentMove > forwardMostMove)
\r
3232 currentMove = forwardMostMove;
\r
3234 /* New part of history that is not contiguous with old part */
\r
3235 if (pausing && gameMode == IcsExamining) {
\r
3236 pauseExamInvalid = TRUE;
\r
3237 forwardMostMove = pauseExamForwardMostMove;
\r
3240 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3241 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3242 ics_getting_history = H_REQUESTED;
\r
3243 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3248 /* Update the clocks */
\r
3249 if (strchr(elapsed_time, '.')) {
\r
3250 /* Time is in ms */
\r
3251 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3252 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3254 /* Time is in seconds */
\r
3255 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3256 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3261 if (appData.zippyPlay && newGame &&
\r
3262 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3263 gameMode != IcsExamining)
\r
3264 ZippyFirstBoard(moveNum, basetime, increment);
\r
3267 /* Put the move on the move list, first converting
\r
3268 to canonical algebraic form. */
\r
3269 if (moveNum > 0) {
\r
3270 if (appData.debugMode) {
\r
3271 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3272 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3273 setbuf(debugFP, NULL);
\r
3275 if (moveNum <= backwardMostMove) {
\r
3276 /* We don't know what the board looked like before
\r
3277 this move. Punt. */
\r
3278 strcpy(parseList[moveNum - 1], move_str);
\r
3279 strcat(parseList[moveNum - 1], " ");
\r
3280 strcat(parseList[moveNum - 1], elapsed_time);
\r
3281 moveList[moveNum - 1][0] = NULLCHAR;
\r
3282 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3283 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3284 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3285 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3286 fromY, fromX, toY, toX, promoChar,
\r
3287 parseList[moveNum-1]);
\r
3288 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3289 castlingRights[moveNum]) ) {
\r
3291 case MT_STALEMATE:
\r
3295 if(gameInfo.variant != VariantShogi)
\r
3296 strcat(parseList[moveNum - 1], "+");
\r
3298 case MT_CHECKMATE:
\r
3299 strcat(parseList[moveNum - 1], "#");
\r
3302 strcat(parseList[moveNum - 1], " ");
\r
3303 strcat(parseList[moveNum - 1], elapsed_time);
\r
3304 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3305 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3306 strcat(moveList[moveNum - 1], "\n");
\r
3307 } else if (strcmp(move_str, "none") == 0) {
\r
3308 /* Again, we don't know what the board looked like;
\r
3309 this is really the start of the game. */
\r
3310 parseList[moveNum - 1][0] = NULLCHAR;
\r
3311 moveList[moveNum - 1][0] = NULLCHAR;
\r
3312 backwardMostMove = moveNum;
\r
3313 startedFromSetupPosition = TRUE;
\r
3314 fromX = fromY = toX = toY = -1;
\r
3316 /* Move from ICS was illegal!? Punt. */
\r
3317 if (appData.debugMode) {
\r
3318 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3319 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3322 if (appData.testLegality && appData.debugMode) {
\r
3323 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3324 DisplayError(str, 0);
\r
3327 strcpy(parseList[moveNum - 1], move_str);
\r
3328 strcat(parseList[moveNum - 1], " ");
\r
3329 strcat(parseList[moveNum - 1], elapsed_time);
\r
3330 moveList[moveNum - 1][0] = NULLCHAR;
\r
3331 fromX = fromY = toX = toY = -1;
\r
3333 if (appData.debugMode) {
\r
3334 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3335 setbuf(debugFP, NULL);
\r
3339 /* Send move to chess program (BEFORE animating it). */
\r
3340 if (appData.zippyPlay && !newGame && newMove &&
\r
3341 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3343 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3344 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3345 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3346 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3348 DisplayError(str, 0);
\r
3350 if (first.sendTime) {
\r
3351 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3353 SendMoveToProgram(moveNum - 1, &first);
\r
3355 firstMove = FALSE;
\r
3356 if (first.useColors) {
\r
3357 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3359 "black\ngo\n", &first);
\r
3361 SendToProgram("go\n", &first);
\r
3363 first.maybeThinking = TRUE;
\r
3366 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3367 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3368 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3369 DisplayError(str, 0);
\r
3371 SendMoveToProgram(moveNum - 1, &first);
\r
3378 if (moveNum > 0 && !gotPremove) {
\r
3379 /* If move comes from a remote source, animate it. If it
\r
3380 isn't remote, it will have already been animated. */
\r
3381 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3382 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3384 if (!pausing && appData.highlightLastMove) {
\r
3385 SetHighlights(fromX, fromY, toX, toY);
\r
3389 /* Start the clocks */
\r
3390 whiteFlag = blackFlag = FALSE;
\r
3391 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3392 if (ticking == 0) {
\r
3393 ics_clock_paused = TRUE;
\r
3395 } else if (ticking == 1) {
\r
3396 ics_clock_paused = FALSE;
\r
3398 if (gameMode == IcsIdle ||
\r
3399 relation == RELATION_OBSERVING_STATIC ||
\r
3400 relation == RELATION_EXAMINING ||
\r
3402 DisplayBothClocks();
\r
3406 /* Display opponents and material strengths */
\r
3407 if (gameInfo.variant != VariantBughouse &&
\r
3408 gameInfo.variant != VariantCrazyhouse) {
\r
3409 if (tinyLayout || smallLayout) {
\r
3410 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3411 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3412 basetime, increment);
\r
3414 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3415 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3416 basetime, increment);
\r
3418 DisplayTitle(str);
\r
3422 /* Display the board */
\r
3425 if (appData.premove)
\r
3426 if (!gotPremove ||
\r
3427 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3428 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3429 ClearPremoveHighlights();
\r
3431 DrawPosition(FALSE, boards[currentMove]);
\r
3432 DisplayMove(moveNum - 1);
\r
3433 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3437 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3441 GetMoveListEvent()
\r
3443 char buf[MSG_SIZ];
\r
3444 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3445 ics_getting_history = H_REQUESTED;
\r
3446 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3452 AnalysisPeriodicEvent(force)
\r
3455 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3456 && !force) || !appData.periodicUpdates)
\r
3459 /* Send . command to Crafty to collect stats */
\r
3460 SendToProgram(".\n", &first);
\r
3462 /* Don't send another until we get a response (this makes
\r
3463 us stop sending to old Crafty's which don't understand
\r
3464 the "." command (sending illegal cmds resets node count & time,
\r
3465 which looks bad)) */
\r
3466 programStats.ok_to_send = 0;
\r
3470 SendMoveToProgram(moveNum, cps)
\r
3472 ChessProgramState *cps;
\r
3474 char buf[MSG_SIZ];
\r
3475 if (cps->useUsermove) {
\r
3476 SendToProgram("usermove ", cps);
\r
3478 if (cps->useSAN) {
\r
3480 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3481 int len = space - parseList[moveNum];
\r
3482 memcpy(buf, parseList[moveNum], len);
\r
3483 buf[len++] = '\n';
\r
3484 buf[len] = NULLCHAR;
\r
3486 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3488 SendToProgram(buf, cps);
\r
3490 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3491 * the engine. It would be nice to have a better way to identify castle
\r
3493 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3494 int fromX = moveList[moveNum][0] - AAA;
\r
3495 int fromY = moveList[moveNum][1] - ONE;
\r
3496 int toX = moveList[moveNum][2] - AAA;
\r
3497 int toY = moveList[moveNum][3] - ONE;
\r
3498 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3499 && boards[currentMove][toY][toX] == WhiteRook)
\r
3500 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3501 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3502 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3503 else SendToProgram("O-O-O\n", cps);
\r
3505 else SendToProgram(moveList[moveNum], cps);
\r
3507 else SendToProgram(moveList[moveNum], cps);
\r
3508 /* End of additions by Tord */
\r
3513 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3514 ChessMove moveType;
\r
3515 int fromX, fromY, toX, toY;
\r
3517 char user_move[MSG_SIZ];
\r
3519 switch (moveType) {
\r
3521 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3522 (int)moveType, fromX, fromY, toX, toY);
\r
3523 DisplayError(user_move + strlen("say "), 0);
\r
3525 case WhiteKingSideCastle:
\r
3526 case BlackKingSideCastle:
\r
3527 case WhiteQueenSideCastleWild:
\r
3528 case BlackQueenSideCastleWild:
\r
3530 case WhiteHSideCastleFR:
\r
3531 case BlackHSideCastleFR:
\r
3533 sprintf(user_move, "o-o\n");
\r
3535 case WhiteQueenSideCastle:
\r
3536 case BlackQueenSideCastle:
\r
3537 case WhiteKingSideCastleWild:
\r
3538 case BlackKingSideCastleWild:
\r
3540 case WhiteASideCastleFR:
\r
3541 case BlackASideCastleFR:
\r
3543 sprintf(user_move, "o-o-o\n");
\r
3545 case WhitePromotionQueen:
\r
3546 case BlackPromotionQueen:
\r
3547 case WhitePromotionRook:
\r
3548 case BlackPromotionRook:
\r
3549 case WhitePromotionBishop:
\r
3550 case BlackPromotionBishop:
\r
3551 case WhitePromotionKnight:
\r
3552 case BlackPromotionKnight:
\r
3553 case WhitePromotionKing:
\r
3554 case BlackPromotionKing:
\r
3556 case WhitePromotionChancellor:
\r
3557 case BlackPromotionChancellor:
\r
3558 case WhitePromotionArchbishop:
\r
3559 case BlackPromotionArchbishop:
\r
3561 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3562 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3563 PieceToChar(PromoPiece(moveType)));
\r
3567 sprintf(user_move, "%c@%c%c\n",
\r
3568 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3569 AAA + toX, ONE + toY);
\r
3572 case WhiteCapturesEnPassant:
\r
3573 case BlackCapturesEnPassant:
\r
3574 case IllegalMove: /* could be a variant we don't quite understand */
\r
3575 sprintf(user_move, "%c%c%c%c\n",
\r
3576 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3579 SendToICS(user_move);
\r
3583 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3584 int rf, ff, rt, ft;
\r
3588 if (rf == DROP_RANK) {
\r
3589 sprintf(move, "%c@%c%c\n",
\r
3590 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3592 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3593 sprintf(move, "%c%c%c%c\n",
\r
3594 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3596 sprintf(move, "%c%c%c%c%c\n",
\r
3597 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3600 AlphaRank(move, 4);
\r
3604 ProcessICSInitScript(f)
\r
3607 char buf[MSG_SIZ];
\r
3609 while (fgets(buf, MSG_SIZ, f)) {
\r
3610 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3617 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3619 AlphaRank(char *move, int n)
\r
3621 char *p = move, c; int x, y;
\r
3623 if( !appData.alphaRank ) return;
\r
3625 if (appData.debugMode) {
\r
3626 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
3629 if(move[1]=='*' &&
\r
3630 move[2]>='0' && move[2]<='9' &&
\r
3631 move[3]>='a' && move[3]<='x' ) {
\r
3632 move[2] = (move[2]-'1')+BOARD_LEFT + AAA;
\r
3633 move[3] = (move[3]-'a') + ONE;
\r
3635 if(move[0]>='0' && move[0]<='9' &&
\r
3636 move[1]>='a' && move[1]<='x' &&
\r
3637 move[2]>='0' && move[2]<='9' &&
\r
3638 move[3]>='a' && move[3]<='x' ) {
\r
3639 /* input move, Shogi -> normal */
\r
3641 move[0] = BOARD_RGHT -1-(move[0]-'1') + AAA;
\r
3642 move[1] = BOARD_HEIGHT-1-(move[1]-'a') + ONE;
\r
3643 move[2] = BOARD_RGHT -1-(move[2]-'1') + AAA;
\r
3644 move[3] = BOARD_HEIGHT-1-(move[3]-'a') + ONE;
\r
3646 move[0] = (move[0]-'1')+BOARD_LEFT + AAA;
\r
3647 move[1] = (move[1]-'a') + ONE;
\r
3648 move[2] = (move[2]-'1')+BOARD_LEFT + AAA;
\r
3649 move[3] = (move[3]-'a') + ONE;
\r
3651 if(move[1]=='@' &&
\r
3652 move[3]>='0' && move[3]<='9' &&
\r
3653 move[2]>='a' && move[2]<='x' ) {
\r
3655 move[2] = (move[2]-AAA)-BOARD_LEFT + '1';
\r
3656 move[3] = (move[3]-ONE) + 'a';
\r
3659 move[0]>='a' && move[0]<='x' &&
\r
3660 move[3]>='0' && move[3]<='9' &&
\r
3661 move[2]>='a' && move[2]<='x' ) {
\r
3662 /* output move, normal -> Shogi */
\r
3664 move[0] = BOARD_RGHT -1-(move[0]-AAA) + '1';
\r
3665 move[1] = BOARD_HEIGHT-1-(move[1]-ONE) + 'a';
\r
3666 move[2] = BOARD_RGHT -1-(move[2]-AAA) + '1';
\r
3667 move[3] = BOARD_HEIGHT-1-(move[3]-ONE) + 'a';
\r
3669 move[0] = (move[0]-AAA)-BOARD_LEFT + '1';
\r
3670 move[1] = (move[1]-ONE) + 'a';
\r
3671 move[2] = (move[2]-AAA)-BOARD_LEFT + '1';
\r
3672 move[3] = (move[3]-ONE) + 'a';
\r
3673 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
3675 if (appData.debugMode) {
\r
3676 fprintf(debugFP, " out = '%s'\n", move);
\r
3680 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3682 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3685 ChessMove *moveType;
\r
3686 int *fromX, *fromY, *toX, *toY;
\r
3689 if (appData.debugMode) {
\r
3690 fprintf(debugFP, "move to parse: %s\n", move);
\r
3692 *moveType = yylexstr(moveNum, move);
\r
3694 switch (*moveType) {
\r
3695 case WhitePromotionChancellor:
\r
3696 case BlackPromotionChancellor:
\r
3697 case WhitePromotionArchbishop:
\r
3698 case BlackPromotionArchbishop:
\r
3699 case WhitePromotionQueen:
\r
3700 case BlackPromotionQueen:
\r
3701 case WhitePromotionRook:
\r
3702 case BlackPromotionRook:
\r
3703 case WhitePromotionBishop:
\r
3704 case BlackPromotionBishop:
\r
3705 case WhitePromotionKnight:
\r
3706 case BlackPromotionKnight:
\r
3707 case WhitePromotionKing:
\r
3708 case BlackPromotionKing:
\r
3710 case WhiteCapturesEnPassant:
\r
3711 case BlackCapturesEnPassant:
\r
3712 case WhiteKingSideCastle:
\r
3713 case WhiteQueenSideCastle:
\r
3714 case BlackKingSideCastle:
\r
3715 case BlackQueenSideCastle:
\r
3716 case WhiteKingSideCastleWild:
\r
3717 case WhiteQueenSideCastleWild:
\r
3718 case BlackKingSideCastleWild:
\r
3719 case BlackQueenSideCastleWild:
\r
3720 /* Code added by Tord: */
\r
3721 case WhiteHSideCastleFR:
\r
3722 case WhiteASideCastleFR:
\r
3723 case BlackHSideCastleFR:
\r
3724 case BlackASideCastleFR:
\r
3725 /* End of code added by Tord */
\r
3726 case IllegalMove: /* bug or odd chess variant */
\r
3727 *fromX = currentMoveString[0] - AAA;
\r
3728 *fromY = currentMoveString[1] - ONE;
\r
3729 *toX = currentMoveString[2] - AAA;
\r
3730 *toY = currentMoveString[3] - ONE;
\r
3731 *promoChar = currentMoveString[4];
\r
3732 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3733 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3734 if (appData.debugMode) {
\r
3735 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
3737 *fromX = *fromY = *toX = *toY = 0;
\r
3740 if (appData.testLegality) {
\r
3741 return (*moveType != IllegalMove);
\r
3743 return !(fromX == fromY && toX == toY);
\r
3748 *fromX = *moveType == WhiteDrop ?
\r
3749 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3750 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3751 *fromY = DROP_RANK;
\r
3752 *toX = currentMoveString[2] - AAA;
\r
3753 *toY = currentMoveString[3] - ONE;
\r
3754 *promoChar = NULLCHAR;
\r
3757 case AmbiguousMove:
\r
3758 case ImpossibleMove:
\r
3759 case (ChessMove) 0: /* end of file */
\r
3768 if (appData.debugMode) {
\r
3769 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
3772 *fromX = *fromY = *toX = *toY = 0;
\r
3773 *promoChar = NULLCHAR;
\r
3778 /* [AS] FRC game initialization */
\r
3779 static int FindEmptySquare( Board board, int n )
\r
3784 while( board[0][i] != EmptySquare ) i++;
\r
3794 static void ShuffleFRC( Board board )
\r
3800 for( i=0; i<8; i++ ) {
\r
3801 board[0][i] = EmptySquare;
\r
3804 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3805 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
3806 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
3807 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
3808 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
3809 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3810 board[0][FindEmptySquare(board, 0)] = WhiteKing;
\r
3811 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3813 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3814 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3818 static unsigned char FRC_KnightTable[10] = {
\r
3819 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
3822 static void SetupFRC( Board board, int pos_index )
\r
3825 unsigned char knights;
\r
3827 /* Bring the position index into a safe range (just in case...) */
\r
3828 if( pos_index < 0 ) pos_index = 0;
\r
3832 /* Clear the board */
\r
3833 for( i=0; i<8; i++ ) {
\r
3834 board[0][i] = EmptySquare;
\r
3837 /* Place bishops and queen */
\r
3838 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
3841 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3844 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
3847 /* Place knigths */
\r
3848 knights = FRC_KnightTable[ pos_index ];
\r
3850 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
3851 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
3853 /* Place rooks and king */
\r
3854 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3855 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
\r
3856 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3858 /* Mirror piece placement for black */
\r
3859 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
3860 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3864 BOOL SetCharTable( char *table, const char * map )
\r
3865 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
3866 /* Basically a safe strcpy that uses the last character as King */
\r
3868 BOOL result = FALSE; int NrPieces;
\r
3870 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
3871 && NrPieces >= 12 && !(NrPieces&1)) {
\r
3872 int i; /* [HGM] Accept even length from 12 to 34 */
\r
3874 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
3875 for( i=0; i<NrPieces/2-1; i++ ) {
\r
3876 table[i] = map[i];
\r
3877 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
3879 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
3880 table[(int) BlackKing] = map[NrPieces-1];
\r
3889 InitPosition(redraw)
\r
3892 ChessSquare (* pieces)[BOARD_SIZE];
\r
3893 int i, j, pawnRow, overrule,
\r
3894 oldx = gameInfo.boardWidth,
\r
3895 oldy = gameInfo.boardHeight,
\r
3896 oldh = gameInfo.holdingsWidth,
\r
3897 oldv = gameInfo.variant;
\r
3899 currentMove = forwardMostMove = backwardMostMove = 0;
\r
3901 /* [AS] Initialize pv info list [HGM] and game status */
\r
3903 for( i=0; i<MAX_MOVES; i++ ) {
\r
3904 pvInfoList[i].depth = 0;
\r
3905 epStatus[i]=EP_NONE;
\r
3906 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
3909 initialRulePlies = 0; /* 50-move counter start */
\r
3911 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
3912 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
3916 /* [HGM] logic here is completely changed. In stead of full positions */
\r
3917 /* the initialized data only consist of the two backranks. The switch */
\r
3918 /* selects which one we will use, which is than copied to the Board */
\r
3919 /* initialPosition, which for the rest is initialized by Pawns and */
\r
3920 /* empty squares. This initial position is then copied to boards[0], */
\r
3921 /* possibly after shuffling, so that it remains available. */
\r
3923 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
3924 gameInfo.boardWidth = 8;
\r
3925 gameInfo.boardHeight = 8;
\r
3926 gameInfo.holdingsSize = 0;
\r
3927 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
3928 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
3929 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
3931 switch (gameInfo.variant) {
\r
3933 pieces = FIDEArray;
\r
3935 case VariantShatranj:
\r
3936 pieces = ShatranjArray;
\r
3937 nrCastlingRights = 0;
\r
3939 case VariantTwoKings:
\r
3940 pieces = twoKingsArray;
\r
3941 nrCastlingRights = 8; /* add rights for second King */
\r
3942 castlingRights[0][6] = initialRights[2] = 5;
\r
3943 castlingRights[0][7] = initialRights[5] = 5;
\r
3944 castlingRank[6] = 0;
\r
3945 castlingRank[7] = BOARD_HEIGHT-1;
\r
3946 startedFromSetupPosition = TRUE;
\r
3948 case VariantCapablanca:
\r
3949 pieces = CapablancaArray;
\r
3950 gameInfo.boardWidth = 10;
\r
3951 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3953 case VariantGothic:
\r
3954 pieces = GothicArray;
\r
3955 gameInfo.boardWidth = 10;
\r
3956 SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k");
\r
3958 case VariantXiangqi:
\r
3959 pieces = XiangqiArray;
\r
3960 gameInfo.boardWidth = 9;
\r
3961 gameInfo.boardHeight = 10;
\r
3962 nrCastlingRights = 0;
\r
3963 SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c.......");
\r
3965 case VariantShogi:
\r
3966 pieces = ShogiArray;
\r
3967 gameInfo.boardWidth = 9;
\r
3968 gameInfo.boardHeight = 9;
\r
3969 gameInfo.holdingsSize = 7;
\r
3970 nrCastlingRights = 0;
\r
3971 SetCharTable(pieceToChar, "PNBRLSG...++++++Kpnbrlsg...++++++k");
\r
3973 case VariantShowgi:
\r
3974 pieces = ShogiArray;
\r
3975 gameInfo.boardWidth = 9;
\r
3976 gameInfo.boardHeight = 9;
\r
3977 gameInfo.holdingsSize = 7;
\r
3978 nrCastlingRights = 0;
\r
3979 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3980 SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k");
\r
3982 case VariantCourier:
\r
3983 pieces = CourierArray;
\r
3984 gameInfo.boardWidth = 12;
\r
3985 nrCastlingRights = 0;
\r
3986 SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k");
\r
3987 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3989 case VariantKnightmate:
\r
3990 pieces = KnightmateArray;
\r
3991 SetCharTable(pieceToChar, "P.BRQ...M.K......p.brq...m.k......");
\r
3993 case VariantFairy:
\r
3994 pieces = fairyArray;
\r
3995 SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk");
\r
3996 startedFromSetupPosition = TRUE;
\r
3998 case VariantCrazyhouse:
\r
3999 case VariantBughouse:
\r
4000 pieces = FIDEArray;
\r
4001 SetCharTable(pieceToChar, "PNBRQ......~~~~.Kpnbrq......~~~~.k");
\r
4002 gameInfo.holdingsSize = 5;
\r
4004 case VariantWildCastle:
\r
4005 pieces = FIDEArray;
\r
4006 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4008 case VariantNoCastle:
\r
4009 pieces = FIDEArray;
\r
4010 nrCastlingRights = 0;
\r
4011 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4012 /* !!?unconstrained back-rank shuffle */
\r
4017 if(appData.NrFiles >= 0) {
\r
4018 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4019 gameInfo.boardWidth = appData.NrFiles;
\r
4021 if(appData.NrRanks >= 0) {
\r
4022 gameInfo.boardHeight = appData.NrRanks;
\r
4024 if(appData.holdingsSize >= 0) {
\r
4025 i = appData.holdingsSize;
\r
4026 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4027 gameInfo.holdingsSize = i;
\r
4029 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4030 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4031 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
4033 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4034 if(pawnRow < 1) pawnRow = 1;
\r
4036 /* User pieceToChar list overrules defaults */
\r
4037 if(appData.pieceToCharTable != NULL)
\r
4038 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4040 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4042 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4043 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4044 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4045 initialPosition[i][j] = s;
\r
4047 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4048 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4049 initialPosition[pawnRow][j] = WhitePawn;
\r
4050 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4051 if(gameInfo.variant == VariantXiangqi) {
\r
4053 initialPosition[pawnRow][j] =
\r
4054 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4055 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4056 initialPosition[2][j] = WhiteCannon;
\r
4057 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4061 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4063 if( (gameInfo.variant == VariantShogi
\r
4064 ||gameInfo.variant == VariantShowgi
\r
4065 ) && !overrule ) {
\r
4067 initialPosition[1][j] = WhiteBishop;
\r
4068 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4070 initialPosition[1][j] = WhiteRook;
\r
4071 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4074 if( nrCastlingRights == -1) {
\r
4075 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4076 /* This sets default castling rights from none to normal corners */
\r
4077 /* Variants with other castling rights must set them themselves above */
\r
4078 nrCastlingRights = 6;
\r
4080 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4081 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4082 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4083 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4084 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4085 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4088 if(gameInfo.variant == VariantFischeRandom) {
\r
4089 if( appData.defaultFrcPosition < 0 ) {
\r
4090 ShuffleFRC( initialPosition );
\r
4093 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4097 CopyBoard(boards[0], initialPosition);
\r
4099 if(oldx != gameInfo.boardWidth ||
\r
4100 oldy != gameInfo.boardHeight ||
\r
4101 oldh != gameInfo.holdingsWidth
\r
4103 || oldv == VariantGothic ||
\r
4104 gameInfo.variant == VariantGothic
\r
4107 InitDrawingSizes(-2 ,0);
\r
4110 DrawPosition(TRUE, boards[currentMove]);
\r
4114 SendBoard(cps, moveNum)
\r
4115 ChessProgramState *cps;
\r
4118 char message[MSG_SIZ];
\r
4120 if (cps->useSetboard) {
\r
4121 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4122 sprintf(message, "setboard %s\n", fen);
\r
4123 SendToProgram(message, cps);
\r
4129 /* Kludge to set black to move, avoiding the troublesome and now
\r
4130 * deprecated "black" command.
\r
4132 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4134 SendToProgram("edit\n", cps);
\r
4135 SendToProgram("#\n", cps);
\r
4136 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4137 bp = &boards[moveNum][i][0];
\r
4138 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4139 if ((int) *bp < (int) BlackPawn) {
\r
4140 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4141 AAA + j, ONE + i);
\r
4142 SendToProgram(message, cps);
\r
4147 SendToProgram("c\n", cps);
\r
4148 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4149 bp = &boards[moveNum][i][0];
\r
4150 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4151 if (((int) *bp != (int) EmptySquare)
\r
4152 && ((int) *bp >= (int) BlackPawn)) {
\r
4153 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4154 AAA + j, ONE + i);
\r
4155 SendToProgram(message, cps);
\r
4160 SendToProgram(".\n", cps);
\r
4165 IsPromotion(fromX, fromY, toX, toY)
\r
4166 int fromX, fromY, toX, toY;
\r
4168 /* [HGM] add Shogi promotions */
\r
4169 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4170 ChessSquare piece;
\r
4172 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4173 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4174 /* [HGM] Note to self: line above also weeds out drops */
\r
4175 piece = boards[currentMove][fromY][fromX];
\r
4176 if(gameInfo.variant == VariantShogi) {
\r
4177 promotionZoneSize = 3;
\r
4178 highestPromotingPiece = (int)WhiteKing;
\r
4179 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4180 and if in normal chess we then allow promotion to King, why not
\r
4181 allow promotion of other piece in Shogi? */
\r
4183 if((int)piece >= BlackPawn) {
\r
4184 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4186 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4188 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4189 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4191 return ( (int)piece <= highestPromotingPiece );
\r
4195 InPalace(row, column)
\r
4197 { /* [HGM] for Xiangqi */
\r
4198 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4199 column < (BOARD_WIDTH + 4)/2 &&
\r
4200 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4205 PieceForSquare (x, y)
\r
4209 if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)
\r
4212 return boards[currentMove][y][x];
\r
4216 OKToStartUserMove(x, y)
\r
4219 ChessSquare from_piece;
\r
4222 if (matchMode) return FALSE;
\r
4223 if (gameMode == EditPosition) return TRUE;
\r
4225 if (x >= 0 && y >= 0)
\r
4226 from_piece = boards[currentMove][y][x];
\r
4228 from_piece = EmptySquare;
\r
4230 if (from_piece == EmptySquare) return FALSE;
\r
4232 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4233 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4235 switch (gameMode) {
\r
4236 case PlayFromGameFile:
\r
4238 case TwoMachinesPlay:
\r
4242 case IcsObserving:
\r
4246 case MachinePlaysWhite:
\r
4247 case IcsPlayingBlack:
\r
4248 if (appData.zippyPlay) return FALSE;
\r
4249 if (white_piece) {
\r
4250 DisplayMoveError("You are playing Black");
\r
4255 case MachinePlaysBlack:
\r
4256 case IcsPlayingWhite:
\r
4257 if (appData.zippyPlay) return FALSE;
\r
4258 if (!white_piece) {
\r
4259 DisplayMoveError("You are playing White");
\r
4265 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4266 DisplayMoveError("It is White's turn");
\r
4269 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4270 DisplayMoveError("It is Black's turn");
\r
4273 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4274 /* Editing correspondence game history */
\r
4275 /* Could disallow this or prompt for confirmation */
\r
4276 cmailOldMove = -1;
\r
4278 if (currentMove < forwardMostMove) {
\r
4279 /* Discarding moves */
\r
4280 /* Could prompt for confirmation here,
\r
4281 but I don't think that's such a good idea */
\r
4282 forwardMostMove = currentMove;
\r
4286 case BeginningOfGame:
\r
4287 if (appData.icsActive) return FALSE;
\r
4288 if (!appData.noChessProgram) {
\r
4289 if (!white_piece) {
\r
4290 DisplayMoveError("You are playing White");
\r
4297 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4298 DisplayMoveError("It is White's turn");
\r
4301 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4302 DisplayMoveError("It is Black's turn");
\r
4308 case IcsExamining:
\r
4311 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4312 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4313 DisplayMoveError("Displayed position is not current");
\r
4319 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4320 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4321 int lastLoadGameUseList = FALSE;
\r
4322 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4323 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4327 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4328 int fromX, fromY, toX, toY;
\r
4331 ChessMove moveType;
\r
4332 ChessSquare pdown, pup;
\r
4334 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4335 if ((fromX == toX) && (fromY == toY)) {
\r
4336 return ImpossibleMove;
\r
4339 /* [HGM] suppress all moves into holdings area and guard band */
\r
4340 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4341 return ImpossibleMove;
\r
4343 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4344 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
4345 pdown = boards[currentMove][fromY][fromX];
\r
4346 pup = boards[currentMove][toY][toX];
\r
4347 if ( gameMode != EditPosition &&
\r
4348 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4349 WhitePawn <= pup && pup < BlackPawn ||
\r
4350 BlackPawn <= pdown && pdown < EmptySquare &&
\r
4351 BlackPawn <= pup && pup < EmptySquare) )
\r
4352 return ImpossibleMove;
\r
4354 /* Check if the user is playing in turn. This is complicated because we
\r
4355 let the user "pick up" a piece before it is his turn. So the piece he
\r
4356 tried to pick up may have been captured by the time he puts it down!
\r
4357 Therefore we use the color the user is supposed to be playing in this
\r
4358 test, not the color of the piece that is currently on the starting
\r
4359 square---except in EditGame mode, where the user is playing both
\r
4360 sides; fortunately there the capture race can't happen. (It can
\r
4361 now happen in IcsExamining mode, but that's just too bad. The user
\r
4362 will get a somewhat confusing message in that case.)
\r
4365 switch (gameMode) {
\r
4366 case PlayFromGameFile:
\r
4368 case TwoMachinesPlay:
\r
4370 case IcsObserving:
\r
4372 /* We switched into a game mode where moves are not accepted,
\r
4373 perhaps while the mouse button was down. */
\r
4374 return ImpossibleMove;
\r
4376 case MachinePlaysWhite:
\r
4377 /* User is moving for Black */
\r
4378 if (WhiteOnMove(currentMove)) {
\r
4379 DisplayMoveError("It is White's turn");
\r
4380 return ImpossibleMove;
\r
4384 case MachinePlaysBlack:
\r
4385 /* User is moving for White */
\r
4386 if (!WhiteOnMove(currentMove)) {
\r
4387 DisplayMoveError("It is Black's turn");
\r
4388 return ImpossibleMove;
\r
4393 case IcsExamining:
\r
4394 case BeginningOfGame:
\r
4397 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4398 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4399 /* User is moving for Black */
\r
4400 if (WhiteOnMove(currentMove)) {
\r
4401 DisplayMoveError("It is White's turn");
\r
4402 return ImpossibleMove;
\r
4405 /* User is moving for White */
\r
4406 if (!WhiteOnMove(currentMove)) {
\r
4407 DisplayMoveError("It is Black's turn");
\r
4408 return ImpossibleMove;
\r
4413 case IcsPlayingBlack:
\r
4414 /* User is moving for Black */
\r
4415 if (WhiteOnMove(currentMove)) {
\r
4416 if (!appData.premove) {
\r
4417 DisplayMoveError("It is White's turn");
\r
4418 } else if (toX >= 0 && toY >= 0) {
\r
4421 premoveFromX = fromX;
\r
4422 premoveFromY = fromY;
\r
4423 premovePromoChar = promoChar;
\r
4425 if (appData.debugMode)
\r
4426 fprintf(debugFP, "Got premove: fromX %d,"
\r
4427 "fromY %d, toX %d, toY %d\n",
\r
4428 fromX, fromY, toX, toY);
\r
4430 return ImpossibleMove;
\r
4434 case IcsPlayingWhite:
\r
4435 /* User is moving for White */
\r
4436 if (!WhiteOnMove(currentMove)) {
\r
4437 if (!appData.premove) {
\r
4438 DisplayMoveError("It is Black's turn");
\r
4439 } else if (toX >= 0 && toY >= 0) {
\r
4442 premoveFromX = fromX;
\r
4443 premoveFromY = fromY;
\r
4444 premovePromoChar = promoChar;
\r
4446 if (appData.debugMode)
\r
4447 fprintf(debugFP, "Got premove: fromX %d,"
\r
4448 "fromY %d, toX %d, toY %d\n",
\r
4449 fromX, fromY, toX, toY);
\r
4451 return ImpossibleMove;
\r
4458 case EditPosition:
\r
4459 /* EditPosition, empty square, or different color piece;
\r
4460 click-click move is possible */
\r
4461 if (toX == -2 || toY == -2) {
\r
4462 boards[0][fromY][fromX] = EmptySquare;
\r
4463 DrawPosition(FALSE, boards[currentMove]);
\r
4464 } else if (toX >= 0 && toY >= 0) {
\r
4465 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4466 boards[0][fromY][fromX] = EmptySquare;
\r
4467 DrawPosition(FALSE, boards[currentMove]);
\r
4469 return ImpossibleMove;
\r
4472 /* [HGM] If move started in holdings, it means a drop */
\r
4473 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4474 if( pup != EmptySquare ) return ImpossibleMove;
\r
4475 if(appData.testLegality) {
\r
4476 /* it would be more logical if LegalityTest() also figured out
\r
4477 * which drops are legal. For now we forbid pawns on back rank.
\r
4478 * Shogi is on its own here...
\r
4480 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
4481 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
4482 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
4484 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4487 userOfferedDraw = FALSE;
\r
4489 /* [HGM] always test for legality, to get promotion info */
\r
4490 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4491 epStatus[currentMove], castlingRights[currentMove],
\r
4492 fromY, fromX, toY, toX, promoChar);
\r
4494 /* [HGM] but possibly ignore an IllegalMove result */
\r
4495 if (appData.testLegality) {
\r
4496 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4497 DisplayMoveError("Illegal move");
\r
4498 return ImpossibleMove;
\r
4503 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4504 function is made into one that returns an OK move type if FinishMove
\r
4505 should be called. This to give the calling driver routine the
\r
4506 opportunity to finish the userMove input with a promotion popup,
\r
4507 without bothering the user with this for invalid or illegal moves */
\r
4509 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4512 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4514 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4515 ChessMove moveType;
\r
4516 int fromX, fromY, toX, toY;
\r
4517 /*char*/int promoChar;
\r
4519 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4520 move type in caller when we know the move is a legal promotion */
\r
4521 if(moveType == NormalMove)
\r
4522 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4524 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4525 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4526 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4527 fromX = boards[currentMove][fromY][fromX];
\r
4528 fromY = DROP_RANK;
\r
4531 /* [HGM] <popupFix> The following if has been moved here from
\r
4532 UserMoveEnevt(). Because it seemed to belon here (why not allow
\r
4533 piece drops in training games?), and because it can only be
\r
4534 performed after it is known to what we promote. */
\r
4535 if (gameMode == Training) {
\r
4536 /* compare the move played on the board to the next move in the
\r
4537 * game. If they match, display the move and the opponent's response.
\r
4538 * If they don't match, display an error message.
\r
4542 CopyBoard(testBoard, boards[currentMove]);
\r
4543 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4545 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4546 ForwardInner(currentMove+1);
\r
4548 /* Autoplay the opponent's response.
\r
4549 * if appData.animate was TRUE when Training mode was entered,
\r
4550 * the response will be animated.
\r
4552 saveAnimate = appData.animate;
\r
4553 appData.animate = animateTraining;
\r
4554 ForwardInner(currentMove+1);
\r
4555 appData.animate = saveAnimate;
\r
4557 /* check for the end of the game */
\r
4558 if (currentMove >= forwardMostMove) {
\r
4559 gameMode = PlayFromGameFile;
\r
4561 SetTrainingModeOff();
\r
4562 DisplayInformation("End of game");
\r
4565 DisplayError("Incorrect move", 0);
\r
4570 /* Ok, now we know that the move is good, so we can kill
\r
4571 the previous line in Analysis Mode */
\r
4572 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4573 forwardMostMove = currentMove;
\r
4576 /* If we need the chess program but it's dead, restart it */
\r
4577 ResurrectChessProgram();
\r
4579 /* A user move restarts a paused game*/
\r
4583 thinkOutput[0] = NULLCHAR;
\r
4585 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4587 if (gameMode == BeginningOfGame) {
\r
4588 if (appData.noChessProgram) {
\r
4589 gameMode = EditGame;
\r
4592 char buf[MSG_SIZ];
\r
4593 gameMode = MachinePlaysBlack;
\r
4595 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4596 DisplayTitle(buf);
\r
4597 if (first.sendName) {
\r
4598 sprintf(buf, "name %s\n", gameInfo.white);
\r
4599 SendToProgram(buf, &first);
\r
4605 /* Relay move to ICS or chess engine */
\r
4606 if (appData.icsActive) {
\r
4607 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4608 gameMode == IcsExamining) {
\r
4609 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4610 ics_user_moved = 1;
\r
4613 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4614 gameMode == MachinePlaysWhite ||
\r
4615 gameMode == MachinePlaysBlack)) {
\r
4616 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4618 SendMoveToProgram(forwardMostMove-1, &first);
\r
4619 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4620 first.maybeThinking = TRUE;
\r
4622 if (currentMove == cmailOldMove + 1) {
\r
4623 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4627 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4629 switch (gameMode) {
\r
4631 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4632 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4636 case MT_CHECKMATE:
\r
4637 if (WhiteOnMove(currentMove)) {
\r
4638 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4640 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4643 case MT_STALEMATE:
\r
4644 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4649 case MachinePlaysBlack:
\r
4650 case MachinePlaysWhite:
\r
4651 /* disable certain menu options while machine is thinking */
\r
4652 SetMachineThinkingEnables();
\r
4661 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
4662 int fromX, fromY, toX, toY;
\r
4665 /* [HGM] This routine was added to allow calling of its two logical
\r
4666 parts from other modules in the old way. Before, UserMoveEvent()
\r
4667 automatically called FinishMove() if the move was OK, and returned
\r
4668 otherwise. I separated the two, in order to make it possible to
\r
4669 slip a promotion popup in between. But that it always needs two
\r
4670 calls, to the first part, (now called UserMoveTest() ), and to
\r
4671 FinishMove if the first part succeeded. Calls that do not need
\r
4672 to do anything in between, can call this routine the old way.
\r
4674 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
4676 if(moveType != ImpossibleMove)
\r
4677 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4680 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4682 char * hint = lastHint;
\r
4683 FrontEndProgramStats stats;
\r
4685 stats.which = cps == &first ? 0 : 1;
\r
4686 stats.depth = cpstats->depth;
\r
4687 stats.nodes = cpstats->nodes;
\r
4688 stats.score = cpstats->score;
\r
4689 stats.time = cpstats->time;
\r
4690 stats.pv = cpstats->movelist;
\r
4691 stats.hint = lastHint;
\r
4692 stats.an_move_index = 0;
\r
4693 stats.an_move_count = 0;
\r
4695 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4696 stats.hint = cpstats->move_name;
\r
4697 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4698 stats.an_move_count = cpstats->nr_moves;
\r
4701 SetProgramStats( &stats );
\r
4705 HandleMachineMove(message, cps)
\r
4707 ChessProgramState *cps;
\r
4709 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4710 char realname[MSG_SIZ];
\r
4711 int fromX, fromY, toX, toY;
\r
4712 ChessMove moveType;
\r
4718 * Kludge to ignore BEL characters
\r
4720 while (*message == '\007') message++;
\r
4723 * Look for book output
\r
4725 if (cps == &first && bookRequested) {
\r
4726 if (message[0] == '\t' || message[0] == ' ') {
\r
4727 /* Part of the book output is here; append it */
\r
4728 strcat(bookOutput, message);
\r
4729 strcat(bookOutput, " \n");
\r
4731 } else if (bookOutput[0] != NULLCHAR) {
\r
4732 /* All of book output has arrived; display it */
\r
4733 char *p = bookOutput;
\r
4734 while (*p != NULLCHAR) {
\r
4735 if (*p == '\t') *p = ' ';
\r
4738 DisplayInformation(bookOutput);
\r
4739 bookRequested = FALSE;
\r
4740 /* Fall through to parse the current output */
\r
4745 * Look for machine move.
\r
4747 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4748 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4750 /* This method is only useful on engines that support ping */
\r
4751 if (cps->lastPing != cps->lastPong) {
\r
4752 if (gameMode == BeginningOfGame) {
\r
4753 /* Extra move from before last new; ignore */
\r
4754 if (appData.debugMode) {
\r
4755 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4758 if (appData.debugMode) {
\r
4759 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4760 cps->which, gameMode);
\r
4763 SendToProgram("undo\n", cps);
\r
4768 switch (gameMode) {
\r
4769 case BeginningOfGame:
\r
4770 /* Extra move from before last reset; ignore */
\r
4771 if (appData.debugMode) {
\r
4772 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4779 /* Extra move after we tried to stop. The mode test is
\r
4780 not a reliable way of detecting this problem, but it's
\r
4781 the best we can do on engines that don't support ping.
\r
4783 if (appData.debugMode) {
\r
4784 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4785 cps->which, gameMode);
\r
4787 SendToProgram("undo\n", cps);
\r
4790 case MachinePlaysWhite:
\r
4791 case IcsPlayingWhite:
\r
4792 machineWhite = TRUE;
\r
4795 case MachinePlaysBlack:
\r
4796 case IcsPlayingBlack:
\r
4797 machineWhite = FALSE;
\r
4800 case TwoMachinesPlay:
\r
4801 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
4804 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
4805 if (appData.debugMode) {
\r
4807 "Ignoring move out of turn by %s, gameMode %d"
\r
4808 ", forwardMost %d\n",
\r
4809 cps->which, gameMode, forwardMostMove);
\r
4814 AlphaRank(machineMove, 4);
\r
4815 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
4816 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4817 /* Machine move could not be parsed; ignore it. */
\r
4818 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
4819 machineMove, cps->which);
\r
4820 DisplayError(buf1, 0);
\r
4821 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
4822 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4823 if (gameMode == TwoMachinesPlay) {
\r
4824 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4830 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
4831 /* So we have to redo legality test with true e.p. status here, */
\r
4832 /* to make sure an illegal e.p. capture does not slip through, */
\r
4833 /* to cause a forfeit on a justified illegal-move complaint */
\r
4834 /* of the opponent. */
\r
4835 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
4836 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
4838 ChessMove moveType;
\r
4839 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
4840 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
4841 fromY, fromX, toY, toX, promoChar);
\r
4842 if (appData.debugMode) {
\r
4844 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
4845 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
4846 fprintf(debugFP, "castling rights\n");
\r
4848 if(moveType == IllegalMove) {
\r
4849 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
4850 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
4851 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4853 } else if(gameInfo.variant != VariantFischeRandom)
\r
4854 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
4855 when they shouldn't (like TSCP-Gothic) */
\r
4856 switch(moveType) {
\r
4857 case WhiteASideCastleFR:
\r
4858 case BlackASideCastleFR:
\r
4860 currentMoveString[2]++;
\r
4862 case WhiteHSideCastleFR:
\r
4863 case BlackHSideCastleFR:
\r
4865 currentMoveString[2]--;
\r
4869 hintRequested = FALSE;
\r
4870 lastHint[0] = NULLCHAR;
\r
4871 bookRequested = FALSE;
\r
4872 /* Program may be pondering now */
\r
4873 cps->maybeThinking = TRUE;
\r
4874 if (cps->sendTime == 2) cps->sendTime = 1;
\r
4875 if (cps->offeredDraw) cps->offeredDraw--;
\r
4878 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
4880 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4881 ics_user_moved = 1;
\r
4884 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
4885 strcpy(machineMove, currentMoveString);
\r
4886 strcat(machineMove, "\n");
\r
4887 strcpy(moveList[forwardMostMove], machineMove);
\r
4889 /* [AS] Save move info and clear stats for next move */
\r
4890 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
4891 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
4892 pvInfoList[ forwardMostMove ].time = -1;
\r
4893 ClearProgramStats();
\r
4894 thinkOutput[0] = NULLCHAR;
\r
4895 hiddenThinkOutputState = 0;
\r
4897 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
4899 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
4900 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
4903 while( count < adjudicateLossPlies ) {
\r
4904 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
4907 score = -score; /* Flip score for winning side */
\r
4910 if( score > adjudicateLossThreshold ) {
\r
4917 if( count >= adjudicateLossPlies ) {
\r
4918 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4920 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
4921 "Xboard adjudication",
\r
4928 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
4930 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
4931 int count = 0, epFile = epStatus[forwardMostMove];
\r
4933 if(appData.testLegality && appData.checkMates)
\r
4934 // don't wait for engine to announce game end if we can judge ourselves
\r
4935 switch (MateTest(boards[forwardMostMove],
\r
4936 PosFlags(forwardMostMove), epFile,
\r
4937 castlingRights[forwardMostMove]) ) {
\r
4942 case MT_STALEMATE:
\r
4943 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4944 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
4947 case MT_CHECKMATE:
\r
4948 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4949 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
4950 "Xboard adjudication: Checkmate",
\r
4955 if( appData.testLegality )
\r
4956 { /* [HGM] Some more adjudications for obstinate engines */
\r
4957 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
4959 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
4960 static int moveCount;
\r
4962 /* First absolutely insufficient mating material. Count what is on board. */
\r
4963 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
4964 { ChessSquare p = boards[forwardMostMove][i][j];
\r
4968 { /* count B,N,R and other of each side */
\r
4985 case EmptySquare:
\r
4990 PawnAdvance += m; NrPawns++;
\r
4992 NrPieces += (p != EmptySquare);
\r
4995 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )
\r
4996 { /* KBK, KNK or KK */
\r
4998 /* always flag draws, for judging claims */
\r
4999 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5001 if(appData.materialDraws) {
\r
5002 /* but only adjudicate them if adjudication enabled */
\r
5003 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5004 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5009 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5010 if(NrPieces == 4 &&
\r
5011 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5012 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5013 || NrWN==2 || NrBN==2 /* KNNK */
\r
5014 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5016 if(--moveCount < 0 && appData.trivialDraws)
\r
5017 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5019 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5022 } else moveCount = 6;
\r
5024 if (appData.debugMode) { int i;
\r
5025 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5026 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5027 appData.drawRepeats);
\r
5028 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5029 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5032 /* Check for rep-draws */
\r
5034 for(k = forwardMostMove-2;
\r
5035 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5036 epStatus[k] < EP_UNKNOWN &&
\r
5037 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5040 if (appData.debugMode) {
\r
5041 fprintf(debugFP, " loop\n");
\r
5043 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5044 if (appData.debugMode) {
\r
5045 fprintf(debugFP, "match\n");
\r
5047 /* compare castling rights */
\r
5048 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5049 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5050 rights++; /* King lost rights, while rook still had them */
\r
5051 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5052 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5053 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5054 rights++; /* but at least one rook lost them */
\r
5056 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5057 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5059 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5060 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5061 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5064 if (appData.debugMode) {
\r
5065 for(i=0; i<nrCastlingRights; i++)
\r
5066 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5069 if (appData.debugMode) {
\r
5070 fprintf(debugFP, " %d %d\n", rights, k);
\r
5072 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5073 && appData.drawRepeats > 1) {
\r
5074 /* adjudicate after user-specified nr of repeats */
\r
5075 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5076 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5079 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5080 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5084 /* Now we test for 50-move draws. Determine ply count */
\r
5085 count = forwardMostMove;
\r
5086 /* look for last irreversble move */
\r
5087 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5089 /* if we hit starting position, add initial plies */
\r
5090 if( count == backwardMostMove )
\r
5091 count -= initialRulePlies;
\r
5092 count = forwardMostMove - count;
\r
5094 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5095 /* this is used to judge if draw claims are legal */
\r
5096 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5097 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5098 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5106 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5107 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5109 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5114 if (gameMode == TwoMachinesPlay) {
\r
5115 if (cps->other->sendTime) {
\r
5116 SendTimeRemaining(cps->other,
\r
5117 cps->other->twoMachinesColor[0] == 'w');
\r
5119 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
5121 firstMove = FALSE;
\r
5122 if (cps->other->useColors) {
\r
5123 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5125 SendToProgram("go\n", cps->other);
\r
5127 cps->other->maybeThinking = TRUE;
\r
5130 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5132 if (!pausing && appData.ringBellAfterMoves) {
\r
5137 * Reenable menu items that were disabled while
\r
5138 * machine was thinking
\r
5140 if (gameMode != TwoMachinesPlay)
\r
5141 SetUserThinkingEnables();
\r
5146 /* Set special modes for chess engines. Later something general
\r
5147 * could be added here; for now there is just one kludge feature,
\r
5148 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5149 * when "xboard" is given as an interactive command.
\r
5151 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5152 cps->useSigint = FALSE;
\r
5153 cps->useSigterm = FALSE;
\r
5156 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5157 * want this, I was asked to put it in, and obliged.
\r
5159 if (!strncmp(message, "setboard ", 9)) {
\r
5160 Board initial_position; int i;
\r
5162 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
5164 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5165 DisplayError("Bad FEN received from engine", 0);
\r
5168 Reset(FALSE, FALSE);
\r
5169 CopyBoard(boards[0], initial_position);
\r
5170 initialRulePlies = FENrulePlies;
\r
5171 epStatus[0] = FENepStatus;
\r
5172 for( i=0; i<nrCastlingRights; i++ )
\r
5173 castlingRights[0][i] = FENcastlingRights[i];
\r
5174 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5175 else gameMode = MachinePlaysBlack;
\r
5176 DrawPosition(FALSE, boards[currentMove]);
\r
5182 * Look for communication commands
\r
5184 if (!strncmp(message, "telluser ", 9)) {
\r
5185 DisplayNote(message + 9);
\r
5188 if (!strncmp(message, "tellusererror ", 14)) {
\r
5189 DisplayError(message + 14, 0);
\r
5192 if (!strncmp(message, "tellopponent ", 13)) {
\r
5193 if (appData.icsActive) {
\r
5195 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5199 DisplayNote(message + 13);
\r
5203 if (!strncmp(message, "tellothers ", 11)) {
\r
5204 if (appData.icsActive) {
\r
5206 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5212 if (!strncmp(message, "tellall ", 8)) {
\r
5213 if (appData.icsActive) {
\r
5215 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5219 DisplayNote(message + 8);
\r
5223 if (strncmp(message, "warning", 7) == 0) {
\r
5224 /* Undocumented feature, use tellusererror in new code */
\r
5225 DisplayError(message, 0);
\r
5228 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5229 strcpy(realname, cps->tidy);
\r
5230 strcat(realname, " query");
\r
5231 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5234 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5235 * sent until we are logged on. Crafty kibitzes have been known to
\r
5236 * interfere with the login process.
\r
5239 if (!strncmp(message, "tellics ", 8)) {
\r
5240 SendToICS(message + 8);
\r
5244 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5245 SendToICS(ics_prefix);
\r
5246 SendToICS(message + 15);
\r
5250 /* The following are for backward compatibility only */
\r
5251 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5252 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5253 SendToICS(ics_prefix);
\r
5254 SendToICS(message);
\r
5259 if (strncmp(message, "feature ", 8) == 0) {
\r
5260 ParseFeatures(message+8, cps);
\r
5262 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5266 * If the move is illegal, cancel it and redraw the board.
\r
5267 * Also deal with other error cases. Matching is rather loose
\r
5268 * here to accommodate engines written before the spec.
\r
5270 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5271 strncmp(message, "Error", 5) == 0) {
\r
5272 if (StrStr(message, "name") ||
\r
5273 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5274 StrStr(message, "result") || StrStr(message, "board") ||
\r
5275 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5276 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5277 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5278 StrStr(message, "accepted")) {
\r
5281 if (StrStr(message, "protover")) {
\r
5282 /* Program is responding to input, so it's apparently done
\r
5283 initializing, and this error message indicates it is
\r
5284 protocol version 1. So we don't need to wait any longer
\r
5285 for it to initialize and send feature commands. */
\r
5286 FeatureDone(cps, 1);
\r
5287 cps->protocolVersion = 1;
\r
5290 cps->maybeThinking = FALSE;
\r
5292 if (StrStr(message, "draw")) {
\r
5293 /* Program doesn't have "draw" command */
\r
5294 cps->sendDrawOffers = 0;
\r
5297 if (cps->sendTime != 1 &&
\r
5298 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5299 /* Program apparently doesn't have "time" or "otim" command */
\r
5300 cps->sendTime = 0;
\r
5303 if (StrStr(message, "analyze")) {
\r
5304 cps->analysisSupport = FALSE;
\r
5305 cps->analyzing = FALSE;
\r
5306 Reset(FALSE, TRUE);
\r
5307 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5308 DisplayError(buf2, 0);
\r
5311 if (StrStr(message, "(no matching move)st")) {
\r
5312 /* Special kludge for GNU Chess 4 only */
\r
5313 cps->stKludge = TRUE;
\r
5314 SendTimeControl(cps, movesPerSession, timeControl,
\r
5315 timeIncrement, appData.searchDepth,
\r
5319 if (StrStr(message, "(no matching move)sd")) {
\r
5320 /* Special kludge for GNU Chess 4 only */
\r
5321 cps->sdKludge = TRUE;
\r
5322 SendTimeControl(cps, movesPerSession, timeControl,
\r
5323 timeIncrement, appData.searchDepth,
\r
5327 if (!StrStr(message, "llegal")) {
\r
5330 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5331 gameMode == IcsIdle) return;
\r
5332 if (forwardMostMove <= backwardMostMove) return;
\r
5334 /* Following removed: it caused a bug where a real illegal move
\r
5335 message in analyze mored would be ignored. */
\r
5336 if (cps == &first && programStats.ok_to_send == 0) {
\r
5337 /* Bogus message from Crafty responding to "." This filtering
\r
5338 can miss some of the bad messages, but fortunately the bug
\r
5339 is fixed in current Crafty versions, so it doesn't matter. */
\r
5343 if (pausing) PauseEvent();
\r
5344 if (gameMode == PlayFromGameFile) {
\r
5345 /* Stop reading this game file */
\r
5346 gameMode = EditGame;
\r
5349 currentMove = --forwardMostMove;
\r
5350 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5352 DisplayBothClocks();
\r
5353 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5354 parseList[currentMove], cps->which);
\r
5355 DisplayMoveError(buf1);
\r
5356 DrawPosition(FALSE, boards[currentMove]);
\r
5358 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5359 /* only passes fully legal moves */
\r
5360 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5361 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5362 "False illegal-move claim", GE_XBOARD );
\r
5366 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5367 /* Program has a broken "time" command that
\r
5368 outputs a string not ending in newline.
\r
5370 cps->sendTime = 0;
\r
5374 * If chess program startup fails, exit with an error message.
\r
5375 * Attempts to recover here are futile.
\r
5377 if ((StrStr(message, "unknown host") != NULL)
\r
5378 || (StrStr(message, "No remote directory") != NULL)
\r
5379 || (StrStr(message, "not found") != NULL)
\r
5380 || (StrStr(message, "No such file") != NULL)
\r
5381 || (StrStr(message, "can't alloc") != NULL)
\r
5382 || (StrStr(message, "Permission denied") != NULL)) {
\r
5384 cps->maybeThinking = FALSE;
\r
5385 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5386 cps->which, cps->program, cps->host, message);
\r
5387 RemoveInputSource(cps->isr);
\r
5388 DisplayFatalError(buf1, 0, 1);
\r
5393 * Look for hint output
\r
5395 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5396 if (cps == &first && hintRequested) {
\r
5397 hintRequested = FALSE;
\r
5398 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5399 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5400 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5401 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5402 fromY, fromX, toY, toX, promoChar, buf1);
\r
5403 sprintf(buf2, "Hint: %s", buf1);
\r
5404 DisplayInformation(buf2);
\r
5406 /* Hint move could not be parsed!? */
\r
5408 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5409 buf1, cps->which);
\r
5410 DisplayError(buf2, 0);
\r
5413 strcpy(lastHint, buf1);
\r
5419 * Ignore other messages if game is not in progress
\r
5421 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5422 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5425 * look for win, lose, draw, or draw offer
\r
5427 if (strncmp(message, "1-0", 3) == 0) {
\r
5428 char *p, *q, *r = "";
\r
5429 p = strchr(message, '{');
\r
5431 q = strchr(p, '}');
\r
5437 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5439 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5440 char *p, *q, *r = "";
\r
5441 p = strchr(message, '{');
\r
5443 q = strchr(p, '}');
\r
5449 /* Kludge for Arasan 4.1 bug */
\r
5450 if (strcmp(r, "Black resigns") == 0) {
\r
5451 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5454 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5456 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5457 char *p, *q, *r = "";
\r
5458 p = strchr(message, '{');
\r
5460 q = strchr(p, '}');
\r
5467 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
5470 } else if (strncmp(message, "White resign", 12) == 0) {
\r
5471 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5473 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
5474 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5476 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
5477 strncmp(message, "Black matches", 13) == 0 ) {
\r
5478 /* [HGM] ignore GNUShogi noises */
\r
5480 } else if (strncmp(message, "White", 5) == 0 &&
\r
5481 message[5] != '(' &&
\r
5482 StrStr(message, "Black") == NULL) {
\r
5483 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5485 } else if (strncmp(message, "Black", 5) == 0 &&
\r
5486 message[5] != '(') {
\r
5487 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5489 } else if (strcmp(message, "resign") == 0 ||
\r
5490 strcmp(message, "computer resigns") == 0) {
\r
5491 switch (gameMode) {
\r
5492 case MachinePlaysBlack:
\r
5493 case IcsPlayingBlack:
\r
5494 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5496 case MachinePlaysWhite:
\r
5497 case IcsPlayingWhite:
\r
5498 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5500 case TwoMachinesPlay:
\r
5501 if (cps->twoMachinesColor[0] == 'w')
\r
5502 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5504 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5507 /* can't happen */
\r
5511 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5512 switch (gameMode) {
\r
5513 case MachinePlaysBlack:
\r
5514 case IcsPlayingBlack:
\r
5515 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5517 case MachinePlaysWhite:
\r
5518 case IcsPlayingWhite:
\r
5519 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5521 case TwoMachinesPlay:
\r
5522 if (cps->twoMachinesColor[0] == 'w')
\r
5523 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5525 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5528 /* can't happen */
\r
5532 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5533 switch (gameMode) {
\r
5534 case MachinePlaysBlack:
\r
5535 case IcsPlayingBlack:
\r
5536 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5538 case MachinePlaysWhite:
\r
5539 case IcsPlayingWhite:
\r
5540 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5542 case TwoMachinesPlay:
\r
5543 if (cps->twoMachinesColor[0] == 'w')
\r
5544 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5546 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5549 /* can't happen */
\r
5553 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5554 if (WhiteOnMove(forwardMostMove)) {
\r
5555 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5557 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5560 } else if (strstr(message, "Draw") != NULL ||
\r
5561 strstr(message, "game is a draw") != NULL) {
\r
5562 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5564 } else if (strstr(message, "offer") != NULL &&
\r
5565 strstr(message, "draw") != NULL) {
\r
5567 if (appData.zippyPlay && first.initDone) {
\r
5568 /* Relay offer to ICS */
\r
5569 SendToICS(ics_prefix);
\r
5570 SendToICS("draw\n");
\r
5573 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5574 if (gameMode == TwoMachinesPlay) {
\r
5575 if (cps->other->offeredDraw) {
\r
5576 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5578 if (cps->other->sendDrawOffers) {
\r
5579 SendToProgram("draw\n", cps->other);
\r
5582 } else if (gameMode == MachinePlaysWhite ||
\r
5583 gameMode == MachinePlaysBlack) {
\r
5584 if (userOfferedDraw) {
\r
5585 DisplayInformation("Machine accepts your draw offer");
\r
5586 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5588 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5595 * Look for thinking output
\r
5597 if ( appData.showThinking) {
\r
5598 int plylev, mvleft, mvtot, curscore, time;
\r
5599 char mvname[MOVE_LEN];
\r
5600 unsigned long nodes;
\r
5602 int ignore = FALSE;
\r
5603 int prefixHint = FALSE;
\r
5604 mvname[0] = NULLCHAR;
\r
5606 switch (gameMode) {
\r
5607 case MachinePlaysBlack:
\r
5608 case IcsPlayingBlack:
\r
5609 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5611 case MachinePlaysWhite:
\r
5612 case IcsPlayingWhite:
\r
5613 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5618 case TwoMachinesPlay:
\r
5619 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5629 buf1[0] = NULLCHAR;
\r
5630 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5631 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5633 if (plyext != ' ' && plyext != '\t') {
\r
5637 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5638 if( cps->scoreIsAbsolute &&
\r
5639 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5641 curscore = -curscore;
\r
5645 programStats.depth = plylev;
\r
5646 programStats.nodes = nodes;
\r
5647 programStats.time = time;
\r
5648 programStats.score = curscore;
\r
5649 programStats.got_only_move = 0;
\r
5651 /* Buffer overflow protection */
\r
5652 if (buf1[0] != NULLCHAR) {
\r
5653 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5654 && appData.debugMode) {
\r
5656 "PV is too long; using the first %d bytes.\n",
\r
5657 sizeof(programStats.movelist) - 1);
\r
5660 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5662 sprintf(programStats.movelist, " no PV\n");
\r
5665 if (programStats.seen_stat) {
\r
5666 programStats.ok_to_send = 1;
\r
5669 if (strchr(programStats.movelist, '(') != NULL) {
\r
5670 programStats.line_is_book = 1;
\r
5671 programStats.nr_moves = 0;
\r
5672 programStats.moves_left = 0;
\r
5674 programStats.line_is_book = 0;
\r
5677 SendProgramStatsToFrontend( cps, &programStats );
\r
5680 [AS] Protect the thinkOutput buffer from overflow... this
\r
5681 is only useful if buf1 hasn't overflowed first!
\r
5683 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5685 (gameMode == TwoMachinesPlay ?
\r
5686 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5687 ((double) curscore) / 100.0,
\r
5688 prefixHint ? lastHint : "",
\r
5689 prefixHint ? " " : "" );
\r
5691 if( buf1[0] != NULLCHAR ) {
\r
5692 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5694 if( strlen(buf1) > max_len ) {
\r
5695 if( appData.debugMode) {
\r
5696 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5698 buf1[max_len+1] = '\0';
\r
5701 strcat( thinkOutput, buf1 );
\r
5704 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5705 DisplayMove(currentMove - 1);
\r
5706 DisplayAnalysis();
\r
5710 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
5711 /* crafty (9.25+) says "(only move) <move>"
\r
5712 * if there is only 1 legal move
\r
5714 sscanf(p, "(only move) %s", buf1);
\r
5715 sprintf(thinkOutput, "%s (only move)", buf1);
\r
5716 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
5717 programStats.depth = 1;
\r
5718 programStats.nr_moves = 1;
\r
5719 programStats.moves_left = 1;
\r
5720 programStats.nodes = 1;
\r
5721 programStats.time = 1;
\r
5722 programStats.got_only_move = 1;
\r
5724 /* Not really, but we also use this member to
\r
5725 mean "line isn't going to change" (Crafty
\r
5726 isn't searching, so stats won't change) */
\r
5727 programStats.line_is_book = 1;
\r
5729 SendProgramStatsToFrontend( cps, &programStats );
\r
5731 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5732 DisplayMove(currentMove - 1);
\r
5733 DisplayAnalysis();
\r
5736 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
5737 &time, &nodes, &plylev, &mvleft,
\r
5738 &mvtot, mvname) >= 5) {
\r
5739 /* The stat01: line is from Crafty (9.29+) in response
\r
5740 to the "." command */
\r
5741 programStats.seen_stat = 1;
\r
5742 cps->maybeThinking = TRUE;
\r
5744 if (programStats.got_only_move || !appData.periodicUpdates)
\r
5747 programStats.depth = plylev;
\r
5748 programStats.time = time;
\r
5749 programStats.nodes = nodes;
\r
5750 programStats.moves_left = mvleft;
\r
5751 programStats.nr_moves = mvtot;
\r
5752 strcpy(programStats.move_name, mvname);
\r
5753 programStats.ok_to_send = 1;
\r
5754 programStats.movelist[0] = '\0';
\r
5756 SendProgramStatsToFrontend( cps, &programStats );
\r
5758 DisplayAnalysis();
\r
5761 } else if (strncmp(message,"++",2) == 0) {
\r
5762 /* Crafty 9.29+ outputs this */
\r
5763 programStats.got_fail = 2;
\r
5766 } else if (strncmp(message,"--",2) == 0) {
\r
5767 /* Crafty 9.29+ outputs this */
\r
5768 programStats.got_fail = 1;
\r
5771 } else if (thinkOutput[0] != NULLCHAR &&
\r
5772 strncmp(message, " ", 4) == 0) {
\r
5773 unsigned message_len;
\r
5776 while (*p && *p == ' ') p++;
\r
5778 message_len = strlen( p );
\r
5780 /* [AS] Avoid buffer overflow */
\r
5781 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
5782 strcat(thinkOutput, " ");
\r
5783 strcat(thinkOutput, p);
\r
5786 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
5787 strcat(programStats.movelist, " ");
\r
5788 strcat(programStats.movelist, p);
\r
5791 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5792 DisplayMove(currentMove - 1);
\r
5793 DisplayAnalysis();
\r
5799 buf1[0] = NULLCHAR;
\r
5801 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5802 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
5804 ChessProgramStats cpstats;
\r
5806 if (plyext != ' ' && plyext != '\t') {
\r
5810 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5811 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
5812 curscore = -curscore;
\r
5815 cpstats.depth = plylev;
\r
5816 cpstats.nodes = nodes;
\r
5817 cpstats.time = time;
\r
5818 cpstats.score = curscore;
\r
5819 cpstats.got_only_move = 0;
\r
5820 cpstats.movelist[0] = '\0';
\r
5822 if (buf1[0] != NULLCHAR) {
\r
5823 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
5826 cpstats.ok_to_send = 0;
\r
5827 cpstats.line_is_book = 0;
\r
5828 cpstats.nr_moves = 0;
\r
5829 cpstats.moves_left = 0;
\r
5831 SendProgramStatsToFrontend( cps, &cpstats );
\r
5838 /* Parse a game score from the character string "game", and
\r
5839 record it as the history of the current game. The game
\r
5840 score is NOT assumed to start from the standard position.
\r
5841 The display is not updated in any way.
\r
5844 ParseGameHistory(game)
\r
5847 ChessMove moveType;
\r
5848 int fromX, fromY, toX, toY, boardIndex;
\r
5851 char buf[MSG_SIZ];
\r
5853 if (appData.debugMode)
\r
5854 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
5856 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
5857 gameInfo.site = StrSave(appData.icsHost);
\r
5858 gameInfo.date = PGNDate();
\r
5859 gameInfo.round = StrSave("-");
\r
5861 /* Parse out names of players */
\r
5862 while (*game == ' ') game++;
\r
5864 while (*game != ' ') *p++ = *game++;
\r
5866 gameInfo.white = StrSave(buf);
\r
5867 while (*game == ' ') game++;
\r
5869 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
5871 gameInfo.black = StrSave(buf);
\r
5874 boardIndex = blackPlaysFirst ? 1 : 0;
\r
5877 yyboardindex = boardIndex;
\r
5878 moveType = (ChessMove) yylex();
\r
5879 switch (moveType) {
\r
5880 case IllegalMove: /* maybe suicide chess, etc. */
\r
5881 if (appData.debugMode) {
\r
5882 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
5883 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
5884 setbuf(debugFP, NULL);
\r
5886 case WhitePromotionChancellor:
\r
5887 case BlackPromotionChancellor:
\r
5888 case WhitePromotionArchbishop:
\r
5889 case BlackPromotionArchbishop:
\r
5890 case WhitePromotionQueen:
\r
5891 case BlackPromotionQueen:
\r
5892 case WhitePromotionRook:
\r
5893 case BlackPromotionRook:
\r
5894 case WhitePromotionBishop:
\r
5895 case BlackPromotionBishop:
\r
5896 case WhitePromotionKnight:
\r
5897 case BlackPromotionKnight:
\r
5898 case WhitePromotionKing:
\r
5899 case BlackPromotionKing:
\r
5901 case WhiteCapturesEnPassant:
\r
5902 case BlackCapturesEnPassant:
\r
5903 case WhiteKingSideCastle:
\r
5904 case WhiteQueenSideCastle:
\r
5905 case BlackKingSideCastle:
\r
5906 case BlackQueenSideCastle:
\r
5907 case WhiteKingSideCastleWild:
\r
5908 case WhiteQueenSideCastleWild:
\r
5909 case BlackKingSideCastleWild:
\r
5910 case BlackQueenSideCastleWild:
\r
5912 case WhiteHSideCastleFR:
\r
5913 case WhiteASideCastleFR:
\r
5914 case BlackHSideCastleFR:
\r
5915 case BlackASideCastleFR:
\r
5917 fromX = currentMoveString[0] - AAA;
\r
5918 fromY = currentMoveString[1] - ONE;
\r
5919 toX = currentMoveString[2] - AAA;
\r
5920 toY = currentMoveString[3] - ONE;
\r
5921 promoChar = currentMoveString[4];
\r
5925 fromX = moveType == WhiteDrop ?
\r
5926 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
5927 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
5928 fromY = DROP_RANK;
\r
5929 toX = currentMoveString[2] - AAA;
\r
5930 toY = currentMoveString[3] - ONE;
\r
5931 promoChar = NULLCHAR;
\r
5933 case AmbiguousMove:
\r
5935 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
5936 if (appData.debugMode) {
\r
5937 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
5938 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
5939 setbuf(debugFP, NULL);
\r
5941 DisplayError(buf, 0);
\r
5943 case ImpossibleMove:
\r
5945 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
5946 if (appData.debugMode) {
\r
5947 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
5948 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
5949 setbuf(debugFP, NULL);
\r
5951 DisplayError(buf, 0);
\r
5953 case (ChessMove) 0: /* end of file */
\r
5954 if (boardIndex < backwardMostMove) {
\r
5955 /* Oops, gap. How did that happen? */
\r
5956 DisplayError("Gap in move list", 0);
\r
5959 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5960 if (boardIndex > forwardMostMove) {
\r
5961 forwardMostMove = boardIndex;
\r
5965 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
5966 strcat(parseList[boardIndex-1], " ");
\r
5967 strcat(parseList[boardIndex-1], yy_text);
\r
5979 case GameUnfinished:
\r
5980 if (gameMode == IcsExamining) {
\r
5981 if (boardIndex < backwardMostMove) {
\r
5982 /* Oops, gap. How did that happen? */
\r
5985 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5988 gameInfo.result = moveType;
\r
5989 p = strchr(yy_text, '{');
\r
5990 if (p == NULL) p = strchr(yy_text, '(');
\r
5993 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
5995 q = strchr(p, *p == '{' ? '}' : ')');
\r
5996 if (q != NULL) *q = NULLCHAR;
\r
5999 gameInfo.resultDetails = StrSave(p);
\r
6002 if (boardIndex >= forwardMostMove &&
\r
6003 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6004 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6007 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6008 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6009 parseList[boardIndex]);
\r
6010 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6011 /* currentMoveString is set as a side-effect of yylex */
\r
6012 strcpy(moveList[boardIndex], currentMoveString);
\r
6013 strcat(moveList[boardIndex], "\n");
\r
6015 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6016 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6017 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6019 case MT_STALEMATE:
\r
6023 if(gameInfo.variant != VariantShogi)
\r
6024 strcat(parseList[boardIndex - 1], "+");
\r
6026 case MT_CHECKMATE:
\r
6027 strcat(parseList[boardIndex - 1], "#");
\r
6034 /* Apply a move to the given board */
\r
6036 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
6037 int fromX, fromY, toX, toY;
\r
6041 ChessSquare captured = board[toY][toX], piece; int p;
\r
6043 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
6044 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
6045 && promoChar != 0) promoChar = 'F';
\r
6047 if (fromX == toX && fromY == toY) return;
\r
6049 if (fromY == DROP_RANK) {
\r
6050 /* must be first */
\r
6051 board[toY][toX] = (ChessSquare) fromX;
\r
6053 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
6055 /* Code added by Tord: */
\r
6056 /* FRC castling assumed when king captures friendly rook. */
\r
6057 if (board[fromY][fromX] == WhiteKing &&
\r
6058 board[toY][toX] == WhiteRook) {
\r
6059 board[fromY][fromX] = EmptySquare;
\r
6060 board[toY][toX] = EmptySquare;
\r
6062 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
6064 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
6066 } else if (board[fromY][fromX] == BlackKing &&
\r
6067 board[toY][toX] == BlackRook) {
\r
6068 board[fromY][fromX] = EmptySquare;
\r
6069 board[toY][toX] = EmptySquare;
\r
6071 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
6073 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
6075 /* End of code added by Tord */
\r
6077 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
6078 && board[fromY][fromX] == WhiteKing
\r
6079 && toY == fromY && toX > fromX+1) {
\r
6080 board[fromY][fromX] = EmptySquare;
\r
6081 board[toY][toX] = WhiteKing;
\r
6082 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6083 board[toY][toX-1] = WhiteRook;
\r
6084 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
6085 && board[fromY][fromX] == WhiteKing
\r
6086 && toY == fromY && toX < fromX-1) {
\r
6087 board[fromY][fromX] = EmptySquare;
\r
6088 board[toY][toX] = WhiteKing;
\r
6089 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6090 board[toY][toX+1] = WhiteRook;
\r
6091 } else if (fromY == 0 && fromX == 3
\r
6092 && board[fromY][fromX] == WhiteKing
\r
6093 && toY == 0 && toX == 5) {
\r
6094 board[fromY][fromX] = EmptySquare;
\r
6095 board[toY][toX] = WhiteKing;
\r
6096 board[fromY][7] = EmptySquare;
\r
6097 board[toY][4] = WhiteRook;
\r
6098 } else if (fromY == 0 && fromX == 3
\r
6099 && board[fromY][fromX] == WhiteKing
\r
6100 && toY == 0 && toX == 1) {
\r
6101 board[fromY][fromX] = EmptySquare;
\r
6102 board[toY][toX] = WhiteKing;
\r
6103 board[fromY][0] = EmptySquare;
\r
6104 board[toY][2] = WhiteRook;
\r
6105 } else if (board[fromY][fromX] == WhitePawn
\r
6106 && toY == BOARD_HEIGHT-1
\r
6107 && gameInfo.variant != VariantXiangqi
\r
6109 /* white pawn promotion */
\r
6110 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6111 if (board[toY][toX] == EmptySquare) {
\r
6112 board[toY][toX] = WhiteQueen;
\r
6114 if(gameInfo.variant==VariantBughouse ||
\r
6115 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6116 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6117 board[fromY][fromX] = EmptySquare;
\r
6118 } else if ((fromY == BOARD_HEIGHT-4)
\r
6120 && (board[fromY][fromX] == WhitePawn)
\r
6121 && (board[toY][toX] == EmptySquare)) {
\r
6122 board[fromY][fromX] = EmptySquare;
\r
6123 board[toY][toX] = WhitePawn;
\r
6124 captured = board[toY - 1][toX];
\r
6125 board[toY - 1][toX] = EmptySquare;
\r
6126 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6127 && board[fromY][fromX] == BlackKing
\r
6128 && toY == fromY && toX > fromX+1) {
\r
6129 board[fromY][fromX] = EmptySquare;
\r
6130 board[toY][toX] = BlackKing;
\r
6131 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6132 board[toY][toX-1] = BlackRook;
\r
6133 } else if (initialPosition[fromY][fromX] == BlackKing
\r
6134 && board[fromY][fromX] == BlackKing
\r
6135 && toY == fromY && toX < fromX-1) {
\r
6136 board[fromY][fromX] = EmptySquare;
\r
6137 board[toY][toX] = BlackKing;
\r
6138 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6139 board[toY][toX+1] = BlackRook;
\r
6140 } else if (fromY == 7 && fromX == 3
\r
6141 && board[fromY][fromX] == BlackKing
\r
6142 && toY == 7 && toX == 5) {
\r
6143 board[fromY][fromX] = EmptySquare;
\r
6144 board[toY][toX] = BlackKing;
\r
6145 board[fromY][7] = EmptySquare;
\r
6146 board[toY][4] = BlackRook;
\r
6147 } else if (fromY == 7 && fromX == 3
\r
6148 && board[fromY][fromX] == BlackKing
\r
6149 && toY == 7 && toX == 1) {
\r
6150 board[fromY][fromX] = EmptySquare;
\r
6151 board[toY][toX] = BlackKing;
\r
6152 board[fromY][0] = EmptySquare;
\r
6153 board[toY][2] = BlackRook;
\r
6154 } else if (board[fromY][fromX] == BlackPawn
\r
6156 && gameInfo.variant != VariantXiangqi
\r
6158 /* black pawn promotion */
\r
6159 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6160 if (board[0][toX] == EmptySquare) {
\r
6161 board[0][toX] = BlackQueen;
\r
6163 if(gameInfo.variant==VariantBughouse ||
\r
6164 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6165 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6166 board[fromY][fromX] = EmptySquare;
\r
6167 } else if ((fromY == 3)
\r
6169 && (board[fromY][fromX] == BlackPawn)
\r
6170 && (board[toY][toX] == EmptySquare)) {
\r
6171 board[fromY][fromX] = EmptySquare;
\r
6172 board[toY][toX] = BlackPawn;
\r
6173 captured = board[toY + 1][toX];
\r
6174 board[toY + 1][toX] = EmptySquare;
\r
6176 board[toY][toX] = board[fromY][fromX];
\r
6177 board[fromY][fromX] = EmptySquare;
\r
6180 /* [HGM] now we promote for Shogi, if needed */
\r
6181 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
6182 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6185 if (gameInfo.holdingsWidth != 0) {
\r
6187 /* !!A lot more code needs to be written to support holdings */
\r
6188 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
6189 /* penultimate board files, so they are automaticlly stored */
\r
6190 /* in the game history. */
\r
6191 if (fromY == DROP_RANK) {
\r
6192 /* Delete from holdings, by decreasing count */
\r
6193 /* and erasing image if necessary */
\r
6195 if(p < (int) BlackPawn) { /* white drop */
\r
6196 p -= (int)WhitePawn;
\r
6197 if(p >= gameInfo.holdingsSize) p = 0;
\r
6198 if(--board[p][BOARD_WIDTH-2] == 0)
\r
6199 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
6200 } else { /* black drop */
\r
6201 p -= (int)BlackPawn;
\r
6202 if(p >= gameInfo.holdingsSize) p = 0;
\r
6203 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
6204 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
6207 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
6208 && gameInfo.variant != VariantBughouse ) {
\r
6209 /* Add to holdings, if holdings exist */
\r
6210 p = (int) captured;
\r
6211 if (p >= (int) BlackPawn) {
\r
6212 p -= (int)BlackPawn;
\r
6213 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6214 /* in Shogi restore piece to its original first */
\r
6215 captured = (ChessSquare) (DEMOTED captured);
\r
6218 p = PieceToNumber((ChessSquare)p);
\r
6219 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
6220 board[p][BOARD_WIDTH-2]++;
\r
6221 board[p][BOARD_WIDTH-1] =
\r
6222 BLACK_TO_WHITE captured;
\r
6224 p -= (int)WhitePawn;
\r
6225 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6226 captured = (ChessSquare) (DEMOTED captured);
\r
6229 p = PieceToNumber((ChessSquare)p);
\r
6230 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
6231 board[BOARD_HEIGHT-1-p][1]++;
\r
6232 board[BOARD_HEIGHT-1-p][0] =
\r
6233 WHITE_TO_BLACK captured;
\r
6237 } else if (gameInfo.variant == VariantAtomic) {
\r
6238 if (captured != EmptySquare) {
\r
6240 for (y = toY-1; y <= toY+1; y++) {
\r
6241 for (x = toX-1; x <= toX+1; x++) {
\r
6242 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
6243 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
6244 board[y][x] = EmptySquare;
\r
6248 board[toY][toX] = EmptySquare;
\r
6251 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
6252 /* [HGM] Shogi promotions */
\r
6253 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6258 /* Updates forwardMostMove */
\r
6260 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6261 int fromX, fromY, toX, toY;
\r
6264 forwardMostMove++;
\r
6266 if (forwardMostMove >= MAX_MOVES) {
\r
6267 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6272 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6273 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6274 if (commentList[forwardMostMove] != NULL) {
\r
6275 free(commentList[forwardMostMove]);
\r
6276 commentList[forwardMostMove] = NULL;
\r
6278 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6279 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6282 epStatus[forwardMostMove] = EP_NONE;
\r
6284 if( boards[forwardMostMove][toY][toX] != EmptySquare )
\r
6285 epStatus[forwardMostMove] = EP_CAPTURE;
\r
6287 if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {
\r
6288 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6289 if( toY-fromY==2 &&
\r
6290 (toX>BOARD_LEFT && boards[forwardMostMove][toY][toX-1] == BlackPawn ||
\r
6291 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )
\r
6292 epStatus[forwardMostMove] = toX;
\r
6294 if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {
\r
6295 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
6296 if( toY-fromY== -2 &&
\r
6297 (toX>BOARD_LEFT && boards[forwardMostMove][toY][toX-1] == WhitePawn ||
\r
6298 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )
\r
6299 epStatus[forwardMostMove] = toX;
\r
6302 for(i=0; i<nrCastlingRights; i++) {
\r
6303 castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];
\r
6304 if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||
\r
6305 castlingRights[forwardMostMove][i] == toX && castlingRank[i] == toY
\r
6306 ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece
\r
6313 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6314 gameInfo.result = GameUnfinished;
\r
6315 if (gameInfo.resultDetails != NULL) {
\r
6316 free(gameInfo.resultDetails);
\r
6317 gameInfo.resultDetails = NULL;
\r
6319 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6320 moveList[forwardMostMove - 1]);
\r
6321 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6322 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6323 fromY, fromX, toY, toX, promoChar,
\r
6324 parseList[forwardMostMove - 1]);
\r
6325 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6326 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6327 castlingRights[forwardMostMove]) ) {
\r
6329 case MT_STALEMATE:
\r
6333 if(gameInfo.variant != VariantShogi)
\r
6334 strcat(parseList[forwardMostMove - 1], "+");
\r
6336 case MT_CHECKMATE:
\r
6337 strcat(parseList[forwardMostMove - 1], "#");
\r
6340 if (appData.debugMode) {
\r
6341 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
6346 /* Updates currentMove if not pausing */
\r
6348 ShowMove(fromX, fromY, toX, toY)
\r
6350 int instant = (gameMode == PlayFromGameFile) ?
\r
6351 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6352 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6354 if (forwardMostMove == currentMove + 1) {
\r
6355 AnimateMove(boards[forwardMostMove - 1],
\r
6356 fromX, fromY, toX, toY);
\r
6358 if (appData.highlightLastMove) {
\r
6359 SetHighlights(fromX, fromY, toX, toY);
\r
6362 currentMove = forwardMostMove;
\r
6365 if (instant) return;
\r
6367 DisplayMove(currentMove - 1);
\r
6368 DrawPosition(FALSE, boards[currentMove]);
\r
6369 DisplayBothClocks();
\r
6370 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6375 InitChessProgram(cps)
\r
6376 ChessProgramState *cps;
\r
6378 char buf[MSG_SIZ], *b; int overruled;
\r
6379 if (appData.noChessProgram) return;
\r
6380 hintRequested = FALSE;
\r
6381 bookRequested = FALSE;
\r
6382 SendToProgram(cps->initString, cps);
\r
6383 if (gameInfo.variant != VariantNormal &&
\r
6384 gameInfo.variant != VariantLoadable
\r
6385 /* [HGM] also send variant if board size non-standard */
\r
6386 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8
\r
6388 char *v = VariantName(gameInfo.variant);
\r
6389 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
6390 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
6391 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
6392 DisplayFatalError(buf, 0, 1);
\r
6396 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
6397 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6398 if( gameInfo.variant == VariantXiangqi )
\r
6399 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
6400 if( gameInfo.variant == VariantShogi )
\r
6401 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
6402 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
6403 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
6404 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )
\r
6405 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6406 if( gameInfo.variant == VariantCourier )
\r
6407 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6410 if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {
\r
6411 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
6412 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
6413 DisplayFatalError(buf, 0, 1);
\r
6416 /* [HGM] here we really should compare with the maximum supported board size */
\r
6417 sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,
\r
6418 gameInfo.boardHeight, gameInfo.holdingsSize );
\r
6419 while(*b++ != '_');
\r
6421 sprintf(b, "variant %s\n", VariantName(gameInfo.variant));
\r
6422 SendToProgram(buf, cps);
\r
6424 if (cps->sendICS) {
\r
6425 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
6426 SendToProgram(buf, cps);
\r
6428 cps->maybeThinking = FALSE;
\r
6429 cps->offeredDraw = 0;
\r
6430 if (!appData.icsActive) {
\r
6431 SendTimeControl(cps, movesPerSession, timeControl,
\r
6432 timeIncrement, appData.searchDepth,
\r
6435 if (appData.showThinking) {
\r
6436 SendToProgram("post\n", cps);
\r
6438 SendToProgram("hard\n", cps);
\r
6439 if (!appData.ponderNextMove) {
\r
6440 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
6441 it without being sure what state we are in first. "hard"
\r
6442 is not a toggle, so that one is OK.
\r
6444 SendToProgram("easy\n", cps);
\r
6446 if (cps->usePing) {
\r
6447 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
6448 SendToProgram(buf, cps);
\r
6450 cps->initDone = TRUE;
\r
6455 StartChessProgram(cps)
\r
6456 ChessProgramState *cps;
\r
6458 char buf[MSG_SIZ];
\r
6461 if (appData.noChessProgram) return;
\r
6462 cps->initDone = FALSE;
\r
6464 if (strcmp(cps->host, "localhost") == 0) {
\r
6465 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
6466 } else if (*appData.remoteShell == NULLCHAR) {
\r
6467 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
6469 if (*appData.remoteUser == NULLCHAR) {
\r
6470 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
6473 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
6474 cps->host, appData.remoteUser, cps->program);
\r
6476 err = StartChildProcess(buf, "", &cps->pr);
\r
6480 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
6481 DisplayFatalError(buf, err, 1);
\r
6487 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
6488 if (cps->protocolVersion > 1) {
\r
6489 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
6490 SendToProgram(buf, cps);
\r
6492 SendToProgram("xboard\n", cps);
\r
6498 TwoMachinesEventIfReady P((void))
\r
6500 if (first.lastPing != first.lastPong) {
\r
6501 DisplayMessage("", "Waiting for first chess program");
\r
6502 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6505 if (second.lastPing != second.lastPong) {
\r
6506 DisplayMessage("", "Waiting for second chess program");
\r
6507 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6511 TwoMachinesEvent();
\r
6515 NextMatchGame P((void))
\r
6517 Reset(FALSE, TRUE);
\r
6518 if (*appData.loadGameFile != NULLCHAR) {
\r
6519 LoadGameFromFile(appData.loadGameFile,
\r
6520 appData.loadGameIndex,
\r
6521 appData.loadGameFile, FALSE);
\r
6522 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
6523 LoadPositionFromFile(appData.loadPositionFile,
\r
6524 appData.loadPositionIndex,
\r
6525 appData.loadPositionFile);
\r
6527 TwoMachinesEventIfReady();
\r
6530 void UserAdjudicationEvent( int result )
\r
6532 ChessMove gameResult = GameIsDrawn;
\r
6534 if( result > 0 ) {
\r
6535 gameResult = WhiteWins;
\r
6537 else if( result < 0 ) {
\r
6538 gameResult = BlackWins;
\r
6541 if( gameMode == TwoMachinesPlay ) {
\r
6542 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
6548 GameEnds(result, resultDetails, whosays)
\r
6550 char *resultDetails;
\r
6553 GameMode nextGameMode;
\r
6555 char buf[MSG_SIZ];
\r
6557 if (appData.debugMode) {
\r
6558 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
6559 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6562 if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
6563 /* If we are playing on ICS, the server decides when the
\r
6564 game is over, but the engine can offer to draw, claim
\r
6565 a draw, or resign.
\r
6568 if (appData.zippyPlay && first.initDone) {
\r
6569 if (result == GameIsDrawn) {
\r
6570 /* In case draw still needs to be claimed */
\r
6571 SendToICS(ics_prefix);
\r
6572 SendToICS("draw\n");
\r
6573 } else if (StrCaseStr(resultDetails, "resign")) {
\r
6574 SendToICS(ics_prefix);
\r
6575 SendToICS("resign\n");
\r
6582 /* If we're loading the game from a file, stop */
\r
6583 if (whosays == GE_FILE) {
\r
6584 (void) StopLoadGameTimer();
\r
6585 gameFileFP = NULL;
\r
6588 /* Cancel draw offers */
\r
6589 first.offeredDraw = second.offeredDraw = 0;
\r
6591 /* If this is an ICS game, only ICS can really say it's done;
\r
6592 if not, anyone can. */
\r
6593 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6594 gameMode == IcsPlayingBlack ||
\r
6595 gameMode == IcsObserving ||
\r
6596 gameMode == IcsExamining);
\r
6598 if (!isIcsGame || whosays == GE_ICS) {
\r
6599 /* OK -- not an ICS game, or ICS said it was done */
\r
6601 if (appData.debugMode) {
\r
6602 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6603 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6605 if (!isIcsGame && !appData.noChessProgram)
\r
6606 SetUserThinkingEnables();
\r
6608 /* [HGM] if a machine claims the game end we verify this claim */
\r
6609 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6610 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6613 if (appData.debugMode) {
\r
6614 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6615 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6617 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6618 first.twoMachinesColor[0] :
\r
6619 second.twoMachinesColor[0] ;
\r
6620 if( gameInfo.holdingsWidth == 0 &&
\r
6621 (result == WhiteWins && claimer == 'w' ||
\r
6622 result == BlackWins && claimer == 'b' ) ) {
\r
6623 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6624 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6625 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6626 resultDetails = buf;
\r
6628 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {
\r
6629 /* Draw that was not flagged by Xboard is false */
\r
6630 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6631 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6632 resultDetails = buf;
\r
6634 /* (Claiming a loss is accepted no questions asked!) */
\r
6637 if (appData.debugMode) {
\r
6638 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6639 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6641 if (resultDetails != NULL) {
\r
6642 gameInfo.result = result;
\r
6643 gameInfo.resultDetails = StrSave(resultDetails);
\r
6645 /* Tell program how game ended in case it is learning */
\r
6646 if (gameMode == MachinePlaysWhite ||
\r
6647 gameMode == MachinePlaysBlack ||
\r
6648 gameMode == TwoMachinesPlay ||
\r
6649 gameMode == IcsPlayingWhite ||
\r
6650 gameMode == IcsPlayingBlack ||
\r
6651 gameMode == BeginningOfGame) {
\r
6652 char buf[MSG_SIZ];
\r
6653 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
6655 if (first.pr != NoProc) {
\r
6656 SendToProgram(buf, &first);
\r
6658 if (second.pr != NoProc &&
\r
6659 gameMode == TwoMachinesPlay) {
\r
6660 SendToProgram(buf, &second);
\r
6664 /* display last move only if game was not loaded from file */
\r
6665 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
6666 DisplayMove(currentMove - 1);
\r
6668 if (forwardMostMove != 0) {
\r
6669 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
6670 if (*appData.saveGameFile != NULLCHAR) {
\r
6671 SaveGameToFile(appData.saveGameFile, TRUE);
\r
6672 } else if (appData.autoSaveGames) {
\r
6675 if (*appData.savePositionFile != NULLCHAR) {
\r
6676 SavePositionToFile(appData.savePositionFile);
\r
6682 if (appData.icsActive) {
\r
6683 if (appData.quietPlay &&
\r
6684 (gameMode == IcsPlayingWhite ||
\r
6685 gameMode == IcsPlayingBlack)) {
\r
6686 SendToICS(ics_prefix);
\r
6687 SendToICS("set shout 1\n");
\r
6689 nextGameMode = IcsIdle;
\r
6690 ics_user_moved = FALSE;
\r
6691 /* clean up premove. It's ugly when the game has ended and the
\r
6692 * premove highlights are still on the board.
\r
6695 gotPremove = FALSE;
\r
6696 ClearPremoveHighlights();
\r
6697 DrawPosition(FALSE, boards[currentMove]);
\r
6699 if (whosays == GE_ICS) {
\r
6702 if (gameMode == IcsPlayingWhite)
\r
6703 PlayIcsWinSound();
\r
6704 else if(gameMode == IcsPlayingBlack)
\r
6705 PlayIcsLossSound();
\r
6708 if (gameMode == IcsPlayingBlack)
\r
6709 PlayIcsWinSound();
\r
6710 else if(gameMode == IcsPlayingWhite)
\r
6711 PlayIcsLossSound();
\r
6714 PlayIcsDrawSound();
\r
6717 PlayIcsUnfinishedSound();
\r
6720 } else if (gameMode == EditGame ||
\r
6721 gameMode == PlayFromGameFile ||
\r
6722 gameMode == AnalyzeMode ||
\r
6723 gameMode == AnalyzeFile) {
\r
6724 nextGameMode = gameMode;
\r
6726 nextGameMode = EndOfGame;
\r
6731 nextGameMode = gameMode;
\r
6734 if (appData.noChessProgram) {
\r
6735 gameMode = nextGameMode;
\r
6740 if (first.reuse) {
\r
6741 /* Put first chess program into idle state */
\r
6742 if (first.pr != NoProc &&
\r
6743 (gameMode == MachinePlaysWhite ||
\r
6744 gameMode == MachinePlaysBlack ||
\r
6745 gameMode == TwoMachinesPlay ||
\r
6746 gameMode == IcsPlayingWhite ||
\r
6747 gameMode == IcsPlayingBlack ||
\r
6748 gameMode == BeginningOfGame)) {
\r
6749 SendToProgram("force\n", &first);
\r
6750 if (first.usePing) {
\r
6751 char buf[MSG_SIZ];
\r
6752 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
6753 SendToProgram(buf, &first);
\r
6756 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6757 /* Kill off first chess program */
\r
6758 if (first.isr != NULL)
\r
6759 RemoveInputSource(first.isr);
\r
6762 if (first.pr != NoProc) {
\r
6763 ExitAnalyzeMode();
\r
6764 DoSleep( appData.delayBeforeQuit );
\r
6765 SendToProgram("quit\n", &first);
\r
6766 DoSleep( appData.delayAfterQuit );
\r
6767 DestroyChildProcess(first.pr, first.useSigterm);
\r
6769 first.pr = NoProc;
\r
6771 if (second.reuse) {
\r
6772 /* Put second chess program into idle state */
\r
6773 if (second.pr != NoProc &&
\r
6774 gameMode == TwoMachinesPlay) {
\r
6775 SendToProgram("force\n", &second);
\r
6776 if (second.usePing) {
\r
6777 char buf[MSG_SIZ];
\r
6778 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
6779 SendToProgram(buf, &second);
\r
6782 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6783 /* Kill off second chess program */
\r
6784 if (second.isr != NULL)
\r
6785 RemoveInputSource(second.isr);
\r
6786 second.isr = NULL;
\r
6788 if (second.pr != NoProc) {
\r
6789 DoSleep( appData.delayBeforeQuit );
\r
6790 SendToProgram("quit\n", &second);
\r
6791 DoSleep( appData.delayAfterQuit );
\r
6792 DestroyChildProcess(second.pr, second.useSigterm);
\r
6794 second.pr = NoProc;
\r
6797 if (matchMode && gameMode == TwoMachinesPlay) {
\r
6800 if (first.twoMachinesColor[0] == 'w') {
\r
6801 first.matchWins++;
\r
6803 second.matchWins++;
\r
6807 if (first.twoMachinesColor[0] == 'b') {
\r
6808 first.matchWins++;
\r
6810 second.matchWins++;
\r
6816 if (matchGame < appData.matchGames) {
\r
6818 tmp = first.twoMachinesColor;
\r
6819 first.twoMachinesColor = second.twoMachinesColor;
\r
6820 second.twoMachinesColor = tmp;
\r
6821 gameMode = nextGameMode;
\r
6823 if(appData.matchPause>10000 || appData.matchPause<10)
\r
6824 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
6825 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
6828 char buf[MSG_SIZ];
\r
6829 gameMode = nextGameMode;
\r
6830 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
6831 first.tidy, second.tidy,
\r
6832 first.matchWins, second.matchWins,
\r
6833 appData.matchGames - (first.matchWins + second.matchWins));
\r
6834 DisplayFatalError(buf, 0, 0);
\r
6837 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6838 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
6839 ExitAnalyzeMode();
\r
6840 gameMode = nextGameMode;
\r
6844 /* Assumes program was just initialized (initString sent).
\r
6845 Leaves program in force mode. */
\r
6847 FeedMovesToProgram(cps, upto)
\r
6848 ChessProgramState *cps;
\r
6853 if (appData.debugMode)
\r
6854 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
6855 startedFromSetupPosition ? "position and " : "",
\r
6856 backwardMostMove, upto, cps->which);
\r
6857 SendToProgram("force\n", cps);
\r
6858 if (startedFromSetupPosition) {
\r
6859 SendBoard(cps, backwardMostMove);
\r
6861 for (i = backwardMostMove; i < upto; i++) {
\r
6862 SendMoveToProgram(i, cps);
\r
6868 ResurrectChessProgram()
\r
6870 /* The chess program may have exited.
\r
6871 If so, restart it and feed it all the moves made so far. */
\r
6873 if (appData.noChessProgram || first.pr != NoProc) return;
\r
6875 StartChessProgram(&first);
\r
6876 InitChessProgram(&first);
\r
6877 FeedMovesToProgram(&first, currentMove);
\r
6879 if (!first.sendTime) {
\r
6880 /* can't tell gnuchess what its clock should read,
\r
6881 so we bow to its notion. */
\r
6883 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
6884 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
6887 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6888 first.analysisSupport) {
\r
6889 SendToProgram("analyze\n", &first);
\r
6890 first.analyzing = TRUE;
\r
6895 * Button procedures
\r
6898 Reset(redraw, init)
\r
6903 if (appData.debugMode) {
\r
6904 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
6905 redraw, init, gameMode);
\r
6908 pausing = pauseExamInvalid = FALSE;
\r
6909 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
6911 whiteFlag = blackFlag = FALSE;
\r
6912 userOfferedDraw = FALSE;
\r
6913 hintRequested = bookRequested = FALSE;
\r
6914 first.maybeThinking = FALSE;
\r
6915 second.maybeThinking = FALSE;
\r
6916 thinkOutput[0] = NULLCHAR;
\r
6917 lastHint[0] = NULLCHAR;
\r
6918 ClearGameInfo(&gameInfo);
\r
6919 gameInfo.variant = StringToVariant(appData.variant);
\r
6920 ics_user_moved = ics_clock_paused = FALSE;
\r
6921 ics_getting_history = H_FALSE;
\r
6923 white_holding[0] = black_holding[0] = NULLCHAR;
\r
6924 ClearProgramStats();
\r
6927 ClearHighlights();
\r
6928 flipView = appData.flipView;
\r
6929 ClearPremoveHighlights();
\r
6930 gotPremove = FALSE;
\r
6931 alarmSounded = FALSE;
\r
6933 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
6934 ExitAnalyzeMode();
\r
6935 gameMode = BeginningOfGame;
\r
6937 InitPosition(redraw);
\r
6938 for (i = 0; i < MAX_MOVES; i++) {
\r
6939 if (commentList[i] != NULL) {
\r
6940 free(commentList[i]);
\r
6941 commentList[i] = NULL;
\r
6945 timeRemaining[0][0] = whiteTimeRemaining;
\r
6946 timeRemaining[1][0] = blackTimeRemaining;
\r
6947 if (first.pr == NULL) {
\r
6948 StartChessProgram(&first);
\r
6950 if (init) InitChessProgram(&first);
\r
6952 DisplayMessage("", "");
\r
6953 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
6957 AutoPlayGameLoop()
\r
6960 if (!AutoPlayOneMove())
\r
6962 if (matchMode || appData.timeDelay == 0)
\r
6964 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
6966 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
6975 int fromX, fromY, toX, toY;
\r
6977 if (appData.debugMode) {
\r
6978 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
6981 if (gameMode != PlayFromGameFile)
\r
6984 if (currentMove >= forwardMostMove) {
\r
6985 gameMode = EditGame;
\r
6988 /* [AS] Clear current move marker at the end of a game */
\r
6989 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
6994 toX = moveList[currentMove][2] - AAA;
\r
6995 toY = moveList[currentMove][3] - ONE;
\r
6997 if (moveList[currentMove][1] == '@') {
\r
6998 if (appData.highlightLastMove) {
\r
6999 SetHighlights(-1, -1, toX, toY);
\r
7002 fromX = moveList[currentMove][0] - AAA;
\r
7003 fromY = moveList[currentMove][1] - ONE;
\r
7005 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
7007 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
7009 if (appData.highlightLastMove) {
\r
7010 SetHighlights(fromX, fromY, toX, toY);
\r
7013 DisplayMove(currentMove);
\r
7014 SendMoveToProgram(currentMove++, &first);
\r
7015 DisplayBothClocks();
\r
7016 DrawPosition(FALSE, boards[currentMove]);
\r
7017 if (commentList[currentMove] != NULL) {
\r
7018 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7025 LoadGameOneMove(readAhead)
\r
7026 ChessMove readAhead;
\r
7028 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
7029 char promoChar = NULLCHAR;
\r
7030 ChessMove moveType;
\r
7031 char move[MSG_SIZ];
\r
7034 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
7035 gameMode != AnalyzeMode && gameMode != Training) {
\r
7036 gameFileFP = NULL;
\r
7040 yyboardindex = forwardMostMove;
\r
7041 if (readAhead != (ChessMove)0) {
\r
7042 moveType = readAhead;
\r
7044 if (gameFileFP == NULL)
\r
7046 moveType = (ChessMove) yylex();
\r
7050 switch (moveType) {
\r
7052 if (appData.debugMode)
\r
7053 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7055 if (*p == '{' || *p == '[' || *p == '(') {
\r
7056 p[strlen(p) - 1] = NULLCHAR;
\r
7060 /* append the comment but don't display it */
\r
7061 while (*p == '\n') p++;
\r
7062 AppendComment(currentMove, p);
\r
7065 case WhiteCapturesEnPassant:
\r
7066 case BlackCapturesEnPassant:
\r
7067 case WhitePromotionChancellor:
\r
7068 case BlackPromotionChancellor:
\r
7069 case WhitePromotionArchbishop:
\r
7070 case BlackPromotionArchbishop:
\r
7071 case WhitePromotionQueen:
\r
7072 case BlackPromotionQueen:
\r
7073 case WhitePromotionRook:
\r
7074 case BlackPromotionRook:
\r
7075 case WhitePromotionBishop:
\r
7076 case BlackPromotionBishop:
\r
7077 case WhitePromotionKnight:
\r
7078 case BlackPromotionKnight:
\r
7079 case WhitePromotionKing:
\r
7080 case BlackPromotionKing:
\r
7082 case WhiteKingSideCastle:
\r
7083 case WhiteQueenSideCastle:
\r
7084 case BlackKingSideCastle:
\r
7085 case BlackQueenSideCastle:
\r
7086 case WhiteKingSideCastleWild:
\r
7087 case WhiteQueenSideCastleWild:
\r
7088 case BlackKingSideCastleWild:
\r
7089 case BlackQueenSideCastleWild:
\r
7091 case WhiteHSideCastleFR:
\r
7092 case WhiteASideCastleFR:
\r
7093 case BlackHSideCastleFR:
\r
7094 case BlackASideCastleFR:
\r
7096 if (appData.debugMode)
\r
7097 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7098 fromX = currentMoveString[0] - AAA;
\r
7099 fromY = currentMoveString[1] - ONE;
\r
7100 toX = currentMoveString[2] - AAA;
\r
7101 toY = currentMoveString[3] - ONE;
\r
7102 promoChar = currentMoveString[4];
\r
7107 if (appData.debugMode)
\r
7108 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7109 fromX = moveType == WhiteDrop ?
\r
7110 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
7111 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
7112 fromY = DROP_RANK;
\r
7113 toX = currentMoveString[2] - AAA;
\r
7114 toY = currentMoveString[3] - ONE;
\r
7120 case GameUnfinished:
\r
7121 if (appData.debugMode)
\r
7122 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
7123 p = strchr(yy_text, '{');
\r
7124 if (p == NULL) p = strchr(yy_text, '(');
\r
7127 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7129 q = strchr(p, *p == '{' ? '}' : ')');
\r
7130 if (q != NULL) *q = NULLCHAR;
\r
7133 GameEnds(moveType, p, GE_FILE);
\r
7135 if (cmailMsgLoaded) {
\r
7136 ClearHighlights();
\r
7137 flipView = WhiteOnMove(currentMove);
\r
7138 if (moveType == GameUnfinished) flipView = !flipView;
\r
7139 if (appData.debugMode)
\r
7140 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
7144 case (ChessMove) 0: /* end of file */
\r
7145 if (appData.debugMode)
\r
7146 fprintf(debugFP, "Parser hit end of file\n");
\r
7147 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7148 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7152 case MT_CHECKMATE:
\r
7153 if (WhiteOnMove(currentMove)) {
\r
7154 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7156 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7159 case MT_STALEMATE:
\r
7160 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7166 case MoveNumberOne:
\r
7167 if (lastLoadGameStart == GNUChessGame) {
\r
7168 /* GNUChessGames have numbers, but they aren't move numbers */
\r
7169 if (appData.debugMode)
\r
7170 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7171 yy_text, (int) moveType);
\r
7172 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7174 /* else fall thru */
\r
7177 case GNUChessGame:
\r
7179 /* Reached start of next game in file */
\r
7180 if (appData.debugMode)
\r
7181 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
7182 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7183 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7187 case MT_CHECKMATE:
\r
7188 if (WhiteOnMove(currentMove)) {
\r
7189 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7191 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7194 case MT_STALEMATE:
\r
7195 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7201 case PositionDiagram: /* should not happen; ignore */
\r
7202 case ElapsedTime: /* ignore */
\r
7203 case NAG: /* ignore */
\r
7204 if (appData.debugMode)
\r
7205 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7206 yy_text, (int) moveType);
\r
7207 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7210 if (appData.testLegality) {
\r
7211 if (appData.debugMode)
\r
7212 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
7213 sprintf(move, "Illegal move: %d.%s%s",
\r
7214 (forwardMostMove / 2) + 1,
\r
7215 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7216 DisplayError(move, 0);
\r
7219 if (appData.debugMode)
\r
7220 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
7221 yy_text, currentMoveString);
\r
7222 fromX = currentMoveString[0] - AAA;
\r
7223 fromY = currentMoveString[1] - ONE;
\r
7224 toX = currentMoveString[2] - AAA;
\r
7225 toY = currentMoveString[3] - ONE;
\r
7226 promoChar = currentMoveString[4];
\r
7230 case AmbiguousMove:
\r
7231 if (appData.debugMode)
\r
7232 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
7233 sprintf(move, "Ambiguous move: %d.%s%s",
\r
7234 (forwardMostMove / 2) + 1,
\r
7235 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7236 DisplayError(move, 0);
\r
7241 case ImpossibleMove:
\r
7242 if (appData.debugMode)
\r
7243 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
7244 sprintf(move, "Illegal move: %d.%s%s",
\r
7245 (forwardMostMove / 2) + 1,
\r
7246 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7247 DisplayError(move, 0);
\r
7253 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
7254 DrawPosition(FALSE, boards[currentMove]);
\r
7255 DisplayBothClocks();
\r
7256 if (!appData.matchMode && commentList[currentMove] != NULL)
\r
7257 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7259 (void) StopLoadGameTimer();
\r
7260 gameFileFP = NULL;
\r
7261 cmailOldMove = forwardMostMove;
\r
7264 /* currentMoveString is set as a side-effect of yylex */
\r
7265 strcat(currentMoveString, "\n");
\r
7266 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7268 thinkOutput[0] = NULLCHAR;
\r
7269 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7270 currentMove = forwardMostMove;
\r
7275 /* Load the nth game from the given file */
\r
7277 LoadGameFromFile(filename, n, title, useList)
\r
7281 /*Boolean*/ int useList;
\r
7284 char buf[MSG_SIZ];
\r
7286 if (strcmp(filename, "-") == 0) {
\r
7290 f = fopen(filename, "rb");
\r
7292 sprintf(buf, "Can't open \"%s\"", filename);
\r
7293 DisplayError(buf, errno);
\r
7297 if (fseek(f, 0, 0) == -1) {
\r
7298 /* f is not seekable; probably a pipe */
\r
7301 if (useList && n == 0) {
\r
7302 int error = GameListBuild(f);
\r
7304 DisplayError("Cannot build game list", error);
\r
7305 } else if (!ListEmpty(&gameList) &&
\r
7306 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7307 GameListPopUp(f, title);
\r
7310 GameListDestroy();
\r
7313 if (n == 0) n = 1;
\r
7314 return LoadGame(f, n, title, FALSE);
\r
7319 MakeRegisteredMove()
\r
7321 int fromX, fromY, toX, toY;
\r
7323 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7324 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
7327 if (appData.debugMode)
\r
7328 fprintf(debugFP, "Restoring %s for game %d\n",
\r
7329 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
7331 thinkOutput[0] = NULLCHAR;
\r
7332 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
7333 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
7334 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
7335 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
7336 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
7337 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
7338 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7339 ShowMove(fromX, fromY, toX, toY);
\r
7341 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7342 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7347 case MT_CHECKMATE:
\r
7348 if (WhiteOnMove(currentMove)) {
\r
7349 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
7351 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
7355 case MT_STALEMATE:
\r
7356 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
7362 case CMAIL_RESIGN:
\r
7363 if (WhiteOnMove(currentMove)) {
\r
7364 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
7366 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
7370 case CMAIL_ACCEPT:
\r
7371 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
7382 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
7384 CmailLoadGame(f, gameNumber, title, useList)
\r
7392 if (gameNumber > nCmailGames) {
\r
7393 DisplayError("No more games in this message", 0);
\r
7396 if (f == lastLoadGameFP) {
\r
7397 int offset = gameNumber - lastLoadGameNumber;
\r
7398 if (offset == 0) {
\r
7399 cmailMsg[0] = NULLCHAR;
\r
7400 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7401 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
7402 nCmailMovesRegistered--;
\r
7404 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
7405 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
7406 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
7409 if (! RegisterMove()) return FALSE;
\r
7413 retVal = LoadGame(f, gameNumber, title, useList);
\r
7415 /* Make move registered during previous look at this game, if any */
\r
7416 MakeRegisteredMove();
\r
7418 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
7419 commentList[currentMove]
\r
7420 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
7421 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7427 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
7429 ReloadGame(offset)
\r
7432 int gameNumber = lastLoadGameNumber + offset;
\r
7433 if (lastLoadGameFP == NULL) {
\r
7434 DisplayError("No game has been loaded yet", 0);
\r
7437 if (gameNumber <= 0) {
\r
7438 DisplayError("Can't back up any further", 0);
\r
7441 if (cmailMsgLoaded) {
\r
7442 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
7443 lastLoadGameTitle, lastLoadGameUseList);
\r
7445 return LoadGame(lastLoadGameFP, gameNumber,
\r
7446 lastLoadGameTitle, lastLoadGameUseList);
\r
7452 /* Load the nth game from open file f */
\r
7454 LoadGame(f, gameNumber, title, useList)
\r
7461 char buf[MSG_SIZ];
\r
7462 int gn = gameNumber;
\r
7463 ListGame *lg = NULL;
\r
7464 int numPGNTags = 0;
\r
7466 GameMode oldGameMode;
\r
7468 if (appData.debugMode)
\r
7469 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
7471 if (gameMode == Training )
\r
7472 SetTrainingModeOff();
\r
7474 oldGameMode = gameMode;
\r
7475 if (gameMode != BeginningOfGame) {
\r
7476 Reset(FALSE, TRUE);
\r
7480 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
7481 fclose(lastLoadGameFP);
\r
7485 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
7488 fseek(f, lg->offset, 0);
\r
7489 GameListHighlight(gameNumber);
\r
7493 DisplayError("Game number out of range", 0);
\r
7497 GameListDestroy();
\r
7498 if (fseek(f, 0, 0) == -1) {
\r
7499 if (f == lastLoadGameFP ?
\r
7500 gameNumber == lastLoadGameNumber + 1 :
\r
7501 gameNumber == 1) {
\r
7504 DisplayError("Can't seek on game file", 0);
\r
7509 lastLoadGameFP = f;
\r
7510 lastLoadGameNumber = gameNumber;
\r
7511 strcpy(lastLoadGameTitle, title);
\r
7512 lastLoadGameUseList = useList;
\r
7517 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
7518 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
7519 lg->gameInfo.black);
\r
7520 DisplayTitle(buf);
\r
7521 } else if (*title != NULLCHAR) {
\r
7522 if (gameNumber > 1) {
\r
7523 sprintf(buf, "%s %d", title, gameNumber);
\r
7524 DisplayTitle(buf);
\r
7526 DisplayTitle(title);
\r
7530 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
7531 gameMode = PlayFromGameFile;
\r
7535 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7536 CopyBoard(boards[0], initialPosition);
\r
7540 * Skip the first gn-1 games in the file.
\r
7541 * Also skip over anything that precedes an identifiable
\r
7542 * start of game marker, to avoid being confused by
\r
7543 * garbage at the start of the file. Currently
\r
7544 * recognized start of game markers are the move number "1",
\r
7545 * the pattern "gnuchess .* game", the pattern
\r
7546 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
7547 * A game that starts with one of the latter two patterns
\r
7548 * will also have a move number 1, possibly
\r
7549 * following a position diagram.
\r
7550 * 5-4-02: Let's try being more lenient and allowing a game to
\r
7551 * start with an unnumbered move. Does that break anything?
\r
7553 cm = lastLoadGameStart = (ChessMove) 0;
\r
7555 yyboardindex = forwardMostMove;
\r
7556 cm = (ChessMove) yylex();
\r
7558 case (ChessMove) 0:
\r
7559 if (cmailMsgLoaded) {
\r
7560 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
7562 Reset(TRUE, TRUE);
\r
7563 DisplayError("Game not found in file", 0);
\r
7567 case GNUChessGame:
\r
7570 lastLoadGameStart = cm;
\r
7573 case MoveNumberOne:
\r
7574 switch (lastLoadGameStart) {
\r
7575 case GNUChessGame:
\r
7579 case MoveNumberOne:
\r
7580 case (ChessMove) 0:
\r
7581 gn--; /* count this game */
\r
7582 lastLoadGameStart = cm;
\r
7591 switch (lastLoadGameStart) {
\r
7592 case GNUChessGame:
\r
7594 case MoveNumberOne:
\r
7595 case (ChessMove) 0:
\r
7596 gn--; /* count this game */
\r
7597 lastLoadGameStart = cm;
\r
7600 lastLoadGameStart = cm; /* game counted already */
\r
7608 yyboardindex = forwardMostMove;
\r
7609 cm = (ChessMove) yylex();
\r
7610 } while (cm == PGNTag || cm == Comment);
\r
7617 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
7618 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
7619 != CMAIL_OLD_RESULT) {
\r
7620 nCmailResults ++ ;
\r
7621 cmailResult[ CMAIL_MAX_GAMES
\r
7622 - gn - 1] = CMAIL_OLD_RESULT;
\r
7628 /* Only a NormalMove can be at the start of a game
\r
7629 * without a position diagram. */
\r
7630 if (lastLoadGameStart == (ChessMove) 0) {
\r
7632 lastLoadGameStart = MoveNumberOne;
\r
7641 if (appData.debugMode)
\r
7642 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
7644 if (cm == XBoardGame) {
\r
7645 /* Skip any header junk before position diagram and/or move 1 */
\r
7647 yyboardindex = forwardMostMove;
\r
7648 cm = (ChessMove) yylex();
\r
7650 if (cm == (ChessMove) 0 ||
\r
7651 cm == GNUChessGame || cm == XBoardGame) {
\r
7652 /* Empty game; pretend end-of-file and handle later */
\r
7653 cm = (ChessMove) 0;
\r
7657 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
7658 cm == PGNTag || cm == Comment)
\r
7661 } else if (cm == GNUChessGame) {
\r
7662 if (gameInfo.event != NULL) {
\r
7663 free(gameInfo.event);
\r
7665 gameInfo.event = StrSave(yy_text);
\r
7668 startedFromSetupPosition = FALSE;
\r
7669 while (cm == PGNTag) {
\r
7670 if (appData.debugMode)
\r
7671 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
7672 err = ParsePGNTag(yy_text, &gameInfo);
\r
7673 if (!err) numPGNTags++;
\r
7675 if (gameInfo.fen != NULL) {
\r
7676 Board initial_position;
\r
7677 startedFromSetupPosition = TRUE;
\r
7678 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
7679 Reset(TRUE, TRUE);
\r
7680 DisplayError("Bad FEN position in file", 0);
\r
7683 CopyBoard(boards[0], initial_position);
\r
7684 /* [HGM] copy FEN attributes as well */
\r
7686 initialRulePlies = FENrulePlies;
\r
7687 epStatus[0] = FENepStatus;
\r
7688 for( i=0; i< nrCastlingRights; i++ )
\r
7689 castlingRights[0][i] = FENcastlingRights[i];
\r
7691 if (blackPlaysFirst) {
\r
7692 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7693 CopyBoard(boards[1], initial_position);
\r
7694 strcpy(moveList[0], "");
\r
7695 strcpy(parseList[0], "");
\r
7696 timeRemaining[0][1] = whiteTimeRemaining;
\r
7697 timeRemaining[1][1] = blackTimeRemaining;
\r
7698 if (commentList[0] != NULL) {
\r
7699 commentList[1] = commentList[0];
\r
7700 commentList[0] = NULL;
\r
7703 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7705 yyboardindex = forwardMostMove;
\r
7706 free(gameInfo.fen);
\r
7707 gameInfo.fen = NULL;
\r
7710 yyboardindex = forwardMostMove;
\r
7711 cm = (ChessMove) yylex();
\r
7713 /* Handle comments interspersed among the tags */
\r
7714 while (cm == Comment) {
\r
7716 if (appData.debugMode)
\r
7717 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7719 if (*p == '{' || *p == '[' || *p == '(') {
\r
7720 p[strlen(p) - 1] = NULLCHAR;
\r
7723 while (*p == '\n') p++;
\r
7724 AppendComment(currentMove, p);
\r
7725 yyboardindex = forwardMostMove;
\r
7726 cm = (ChessMove) yylex();
\r
7730 /* don't rely on existence of Event tag since if game was
\r
7731 * pasted from clipboard the Event tag may not exist
\r
7733 if (numPGNTags > 0){
\r
7735 if (gameInfo.variant == VariantNormal) {
\r
7736 gameInfo.variant = StringToVariant(gameInfo.event);
\r
7739 if( appData.autoDisplayTags ) {
\r
7740 tags = PGNTags(&gameInfo);
\r
7741 TagsPopUp(tags, CmailMsg());
\r
7746 /* Make something up, but don't display it now */
\r
7751 if (cm == PositionDiagram) {
\r
7754 Board initial_position;
\r
7756 if (appData.debugMode)
\r
7757 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
7759 if (!startedFromSetupPosition) {
\r
7761 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
7762 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
7772 initial_position[i][j++] = CharToPiece(*p);
\r
7775 while (*p == ' ' || *p == '\t' ||
\r
7776 *p == '\n' || *p == '\r') p++;
\r
7778 if (strncmp(p, "black", strlen("black"))==0)
\r
7779 blackPlaysFirst = TRUE;
\r
7781 blackPlaysFirst = FALSE;
\r
7782 startedFromSetupPosition = TRUE;
\r
7784 CopyBoard(boards[0], initial_position);
\r
7785 if (blackPlaysFirst) {
\r
7786 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7787 CopyBoard(boards[1], initial_position);
\r
7788 strcpy(moveList[0], "");
\r
7789 strcpy(parseList[0], "");
\r
7790 timeRemaining[0][1] = whiteTimeRemaining;
\r
7791 timeRemaining[1][1] = blackTimeRemaining;
\r
7792 if (commentList[0] != NULL) {
\r
7793 commentList[1] = commentList[0];
\r
7794 commentList[0] = NULL;
\r
7797 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7800 yyboardindex = forwardMostMove;
\r
7801 cm = (ChessMove) yylex();
\r
7804 if (first.pr == NoProc) {
\r
7805 StartChessProgram(&first);
\r
7807 InitChessProgram(&first);
\r
7808 SendToProgram("force\n", &first);
\r
7809 if (startedFromSetupPosition) {
\r
7810 SendBoard(&first, forwardMostMove);
\r
7811 DisplayBothClocks();
\r
7814 while (cm == Comment) {
\r
7816 if (appData.debugMode)
\r
7817 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7819 if (*p == '{' || *p == '[' || *p == '(') {
\r
7820 p[strlen(p) - 1] = NULLCHAR;
\r
7823 while (*p == '\n') p++;
\r
7824 AppendComment(currentMove, p);
\r
7825 yyboardindex = forwardMostMove;
\r
7826 cm = (ChessMove) yylex();
\r
7829 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
7830 cm == WhiteWins || cm == BlackWins ||
\r
7831 cm == GameIsDrawn || cm == GameUnfinished) {
\r
7832 DisplayMessage("", "No moves in game");
\r
7833 if (cmailMsgLoaded) {
\r
7834 if (appData.debugMode)
\r
7835 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
7836 ClearHighlights();
\r
7839 DrawPosition(FALSE, boards[currentMove]);
\r
7840 DisplayBothClocks();
\r
7841 gameMode = EditGame;
\r
7843 gameFileFP = NULL;
\r
7848 if (commentList[currentMove] != NULL) {
\r
7849 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
7850 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7853 if (!matchMode && appData.timeDelay != 0)
\r
7854 DrawPosition(FALSE, boards[currentMove]);
\r
7856 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
7857 programStats.ok_to_send = 1;
\r
7860 /* if the first token after the PGN tags is a move
\r
7861 * and not move number 1, retrieve it from the parser
\r
7863 if (cm != MoveNumberOne)
\r
7864 LoadGameOneMove(cm);
\r
7866 /* load the remaining moves from the file */
\r
7867 while (LoadGameOneMove((ChessMove)0)) {
\r
7868 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7869 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7872 /* rewind to the start of the game */
\r
7873 currentMove = backwardMostMove;
\r
7875 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7877 if (oldGameMode == AnalyzeFile ||
\r
7878 oldGameMode == AnalyzeMode) {
\r
7879 AnalyzeFileEvent();
\r
7882 if (matchMode || appData.timeDelay == 0) {
\r
7884 gameMode = EditGame;
\r
7886 } else if (appData.timeDelay > 0) {
\r
7887 AutoPlayGameLoop();
\r
7890 if (appData.debugMode)
\r
7891 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
7895 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
7897 ReloadPosition(offset)
\r
7900 int positionNumber = lastLoadPositionNumber + offset;
\r
7901 if (lastLoadPositionFP == NULL) {
\r
7902 DisplayError("No position has been loaded yet", 0);
\r
7905 if (positionNumber <= 0) {
\r
7906 DisplayError("Can't back up any further", 0);
\r
7909 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
7910 lastLoadPositionTitle);
\r
7913 /* Load the nth position from the given file */
\r
7915 LoadPositionFromFile(filename, n, title)
\r
7921 char buf[MSG_SIZ];
\r
7923 if (strcmp(filename, "-") == 0) {
\r
7924 return LoadPosition(stdin, n, "stdin");
\r
7926 f = fopen(filename, "rb");
\r
7928 sprintf(buf, "Can't open \"%s\"", filename);
\r
7929 DisplayError(buf, errno);
\r
7932 return LoadPosition(f, n, title);
\r
7937 /* Load the nth position from the given open file, and close it */
\r
7939 LoadPosition(f, positionNumber, title)
\r
7941 int positionNumber;
\r
7944 char *p, line[MSG_SIZ];
\r
7945 Board initial_position;
\r
7946 int i, j, fenMode, pn;
\r
7948 if (gameMode == Training )
\r
7949 SetTrainingModeOff();
\r
7951 if (gameMode != BeginningOfGame) {
\r
7952 Reset(FALSE, TRUE);
\r
7954 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
7955 fclose(lastLoadPositionFP);
\r
7957 if (positionNumber == 0) positionNumber = 1;
\r
7958 lastLoadPositionFP = f;
\r
7959 lastLoadPositionNumber = positionNumber;
\r
7960 strcpy(lastLoadPositionTitle, title);
\r
7961 if (first.pr == NoProc) {
\r
7962 StartChessProgram(&first);
\r
7963 InitChessProgram(&first);
\r
7965 pn = positionNumber;
\r
7966 if (positionNumber < 0) {
\r
7967 /* Negative position number means to seek to that byte offset */
\r
7968 if (fseek(f, -positionNumber, 0) == -1) {
\r
7969 DisplayError("Can't seek on position file", 0);
\r
7974 if (fseek(f, 0, 0) == -1) {
\r
7975 if (f == lastLoadPositionFP ?
\r
7976 positionNumber == lastLoadPositionNumber + 1 :
\r
7977 positionNumber == 1) {
\r
7980 DisplayError("Can't seek on position file", 0);
\r
7985 /* See if this file is FEN or old-style xboard */
\r
7986 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7987 DisplayError("Position not found in file", 0);
\r
7990 switch (line[0]) {
\r
7991 case '#': case 'x':
\r
7995 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
7996 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
7997 case '1': case '2': case '3': case '4': case '5': case '6':
\r
7998 case '7': case '8': case '9':
\r
8000 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
8001 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
8002 case 'C': case 'W': case 'c': case 'w':
\r
8009 if (fenMode || line[0] == '#') pn--;
\r
8011 /* skip postions before number pn */
\r
8012 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
8013 Reset(TRUE, TRUE);
\r
8014 DisplayError("Position not found in file", 0);
\r
8017 if (fenMode || line[0] == '#') pn--;
\r
8022 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
8023 DisplayError("Bad FEN position in file", 0);
\r
8027 (void) fgets(line, MSG_SIZ, f);
\r
8028 (void) fgets(line, MSG_SIZ, f);
\r
8030 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
8031 (void) fgets(line, MSG_SIZ, f);
\r
8032 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
8035 initial_position[i][j++] = CharToPiece(*p);
\r
8039 blackPlaysFirst = FALSE;
\r
8041 (void) fgets(line, MSG_SIZ, f);
\r
8042 if (strncmp(line, "black", strlen("black"))==0)
\r
8043 blackPlaysFirst = TRUE;
\r
8046 startedFromSetupPosition = TRUE;
\r
8048 SendToProgram("force\n", &first);
\r
8049 CopyBoard(boards[0], initial_position);
\r
8050 /* [HGM] copy FEN attributes as well */
\r
8052 initialRulePlies = FENrulePlies;
\r
8053 epStatus[0] = FENepStatus;
\r
8054 for( i=0; i< nrCastlingRights; i++ )
\r
8055 castlingRights[0][i] = FENcastlingRights[i];
\r
8057 if (blackPlaysFirst) {
\r
8058 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8059 strcpy(moveList[0], "");
\r
8060 strcpy(parseList[0], "");
\r
8061 CopyBoard(boards[1], initial_position);
\r
8062 DisplayMessage("", "Black to play");
\r
8064 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8065 DisplayMessage("", "White to play");
\r
8067 SendBoard(&first, forwardMostMove);
\r
8069 if (positionNumber > 1) {
\r
8070 sprintf(line, "%s %d", title, positionNumber);
\r
8071 DisplayTitle(line);
\r
8073 DisplayTitle(title);
\r
8075 gameMode = EditGame;
\r
8078 timeRemaining[0][1] = whiteTimeRemaining;
\r
8079 timeRemaining[1][1] = blackTimeRemaining;
\r
8080 DrawPosition(FALSE, boards[currentMove]);
\r
8087 CopyPlayerNameIntoFileName(dest, src)
\r
8088 char **dest, *src;
\r
8090 while (*src != NULLCHAR && *src != ',') {
\r
8091 if (*src == ' ') {
\r
8095 *(*dest)++ = *src++;
\r
8100 char *DefaultFileName(ext)
\r
8103 static char def[MSG_SIZ];
\r
8106 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
8108 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
8110 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
8114 def[0] = NULLCHAR;
\r
8119 /* Save the current game to the given file */
\r
8121 SaveGameToFile(filename, append)
\r
8126 char buf[MSG_SIZ];
\r
8128 if (strcmp(filename, "-") == 0) {
\r
8129 return SaveGame(stdout, 0, NULL);
\r
8131 f = fopen(filename, append ? "a" : "w");
\r
8133 sprintf(buf, "Can't open \"%s\"", filename);
\r
8134 DisplayError(buf, errno);
\r
8137 return SaveGame(f, 0, NULL);
\r
8146 static char buf[MSG_SIZ];
\r
8149 p = strchr(str, ' ');
\r
8150 if (p == NULL) return str;
\r
8151 strncpy(buf, str, p - str);
\r
8152 buf[p - str] = NULLCHAR;
\r
8156 #define PGN_MAX_LINE 75
\r
8158 #define PGN_SIDE_WHITE 0
\r
8159 #define PGN_SIDE_BLACK 1
\r
8162 static int FindFirstMoveOutOfBook( int side )
\r
8166 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
8167 int index = backwardMostMove;
\r
8168 int has_book_hit = 0;
\r
8170 if( (index % 2) != side ) {
\r
8174 while( index < forwardMostMove ) {
\r
8175 /* Check to see if engine is in book */
\r
8176 int depth = pvInfoList[index].depth;
\r
8177 int score = pvInfoList[index].score;
\r
8180 if( depth <= 2 ) {
\r
8183 else if( score == 0 && depth == 63 ) {
\r
8184 in_book = 1; /* Zappa */
\r
8186 else if( score == 2 && depth == 99 ) {
\r
8187 in_book = 1; /* Abrok */
\r
8190 has_book_hit += in_book;
\r
8206 void GetOutOfBookInfo( char * buf )
\r
8210 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8212 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
8213 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
8217 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
8218 for( i=0; i<2; i++ ) {
\r
8222 if( i > 0 && oob[0] >= 0 ) {
\r
8223 strcat( buf, " " );
\r
8226 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
8227 sprintf( buf+strlen(buf), "%s%.2f",
\r
8228 pvInfoList[idx].score >= 0 ? "+" : "",
\r
8229 pvInfoList[idx].score / 100.0 );
\r
8235 /* Save game in PGN style and close the file */
\r
8240 int i, offset, linelen, newblock;
\r
8244 int movelen, numlen, blank;
\r
8245 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
8247 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8249 tm = time((time_t *) NULL);
\r
8251 PrintPGNTags(f, &gameInfo);
\r
8253 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8254 char *fen = PositionToFEN(backwardMostMove, 1);
\r
8255 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
8256 fprintf(f, "\n{--------------\n");
\r
8257 PrintPosition(f, backwardMostMove);
\r
8258 fprintf(f, "--------------}\n");
\r
8262 /* [AS] Out of book annotation */
\r
8263 if( appData.saveOutOfBookInfo ) {
\r
8266 GetOutOfBookInfo( buf );
\r
8268 if( buf[0] != '\0' ) {
\r
8269 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8276 i = backwardMostMove;
\r
8280 while (i < forwardMostMove) {
\r
8281 /* Print comments preceding this move */
\r
8282 if (commentList[i] != NULL) {
\r
8283 if (linelen > 0) fprintf(f, "\n");
\r
8284 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8289 /* Format move number */
\r
8290 if ((i % 2) == 0) {
\r
8291 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8294 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
8296 numtext[0] = NULLCHAR;
\r
8299 numlen = strlen(numtext);
\r
8302 /* Print move number */
\r
8303 blank = linelen > 0 && numlen > 0;
\r
8304 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
8313 fprintf(f, numtext);
\r
8314 linelen += numlen;
\r
8317 movetext = SavePart(parseList[i]);
\r
8319 /* [AS] Add PV info if present */
\r
8320 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
8321 /* [HGM] add time */
\r
8322 char buf[MSG_SIZ]; int seconds = 0;
\r
8324 if(i >= backwardMostMove) {
\r
8325 /* take the time that changed */
\r
8326 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
8328 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
8331 if (appData.debugMode) {
\r
8332 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
8333 timeRemaining[0][i+1], timeRemaining[0][i],
\r
8334 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
8338 if( seconds < 0 ) buf[0] = 0; else
\r
8339 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
8340 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
8342 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
8344 pvInfoList[i].score >= 0 ? "+" : "",
\r
8345 pvInfoList[i].score / 100.0,
\r
8346 pvInfoList[i].depth,
\r
8348 movetext = move_buffer;
\r
8351 movelen = strlen(movetext);
\r
8354 blank = linelen > 0 && movelen > 0;
\r
8355 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
8364 fprintf(f, movetext);
\r
8365 linelen += movelen;
\r
8370 /* Start a new line */
\r
8371 if (linelen > 0) fprintf(f, "\n");
\r
8373 /* Print comments after last move */
\r
8374 if (commentList[i] != NULL) {
\r
8375 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8378 /* Print result */
\r
8379 if (gameInfo.resultDetails != NULL &&
\r
8380 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8381 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
8382 PGNResult(gameInfo.result));
\r
8384 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8391 /* Save game in old style and close the file */
\r
8393 SaveGameOldStyle(f)
\r
8399 tm = time((time_t *) NULL);
\r
8401 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
8402 PrintOpponents(f);
\r
8404 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8405 fprintf(f, "\n[--------------\n");
\r
8406 PrintPosition(f, backwardMostMove);
\r
8407 fprintf(f, "--------------]\n");
\r
8412 i = backwardMostMove;
\r
8413 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8415 while (i < forwardMostMove) {
\r
8416 if (commentList[i] != NULL) {
\r
8417 fprintf(f, "[%s]\n", commentList[i]);
\r
8420 if ((i % 2) == 1) {
\r
8421 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
8424 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
8426 if (commentList[i] != NULL) {
\r
8430 if (i >= forwardMostMove) {
\r
8434 fprintf(f, "%s\n", parseList[i]);
\r
8439 if (commentList[i] != NULL) {
\r
8440 fprintf(f, "[%s]\n", commentList[i]);
\r
8443 /* This isn't really the old style, but it's close enough */
\r
8444 if (gameInfo.resultDetails != NULL &&
\r
8445 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8446 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
8447 gameInfo.resultDetails);
\r
8449 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8456 /* Save the current game to open file f and close the file */
\r
8458 SaveGame(f, dummy, dummy2)
\r
8463 if (gameMode == EditPosition) EditPositionDone();
\r
8464 if (appData.oldSaveStyle)
\r
8465 return SaveGameOldStyle(f);
\r
8467 return SaveGamePGN(f);
\r
8470 /* Save the current position to the given file */
\r
8472 SavePositionToFile(filename)
\r
8476 char buf[MSG_SIZ];
\r
8478 if (strcmp(filename, "-") == 0) {
\r
8479 return SavePosition(stdout, 0, NULL);
\r
8481 f = fopen(filename, "a");
\r
8483 sprintf(buf, "Can't open \"%s\"", filename);
\r
8484 DisplayError(buf, errno);
\r
8487 SavePosition(f, 0, NULL);
\r
8493 /* Save the current position to the given open file and close the file */
\r
8495 SavePosition(f, dummy, dummy2)
\r
8503 if (appData.oldSaveStyle) {
\r
8504 tm = time((time_t *) NULL);
\r
8506 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
8507 PrintOpponents(f);
\r
8508 fprintf(f, "[--------------\n");
\r
8509 PrintPosition(f, currentMove);
\r
8510 fprintf(f, "--------------]\n");
\r
8512 fen = PositionToFEN(currentMove, 1);
\r
8513 fprintf(f, "%s\n", fen);
\r
8521 ReloadCmailMsgEvent(unregister)
\r
8525 static char *inFilename = NULL;
\r
8526 static char *outFilename;
\r
8528 struct stat inbuf, outbuf;
\r
8531 /* Any registered moves are unregistered if unregister is set, */
\r
8532 /* i.e. invoked by the signal handler */
\r
8534 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8535 cmailMoveRegistered[i] = FALSE;
\r
8536 if (cmailCommentList[i] != NULL) {
\r
8537 free(cmailCommentList[i]);
\r
8538 cmailCommentList[i] = NULL;
\r
8541 nCmailMovesRegistered = 0;
\r
8544 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8545 cmailResult[i] = CMAIL_NOT_RESULT;
\r
8547 nCmailResults = 0;
\r
8549 if (inFilename == NULL) {
\r
8550 /* Because the filenames are static they only get malloced once */
\r
8551 /* and they never get freed */
\r
8552 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
8553 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
8555 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
8556 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
8559 status = stat(outFilename, &outbuf);
\r
8561 cmailMailedMove = FALSE;
\r
8563 status = stat(inFilename, &inbuf);
\r
8564 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
8567 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
8568 counts the games, notes how each one terminated, etc.
\r
8570 It would be nice to remove this kludge and instead gather all
\r
8571 the information while building the game list. (And to keep it
\r
8572 in the game list nodes instead of having a bunch of fixed-size
\r
8573 parallel arrays.) Note this will require getting each game's
\r
8574 termination from the PGN tags, as the game list builder does
\r
8575 not process the game moves. --mann
\r
8577 cmailMsgLoaded = TRUE;
\r
8578 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
8580 /* Load first game in the file or popup game menu */
\r
8581 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
8583 #endif /* !WIN32 */
\r
8591 char string[MSG_SIZ];
\r
8593 if ( cmailMailedMove
\r
8594 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
8595 return TRUE; /* Allow free viewing */
\r
8598 /* Unregister move to ensure that we don't leave RegisterMove */
\r
8599 /* with the move registered when the conditions for registering no */
\r
8601 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8602 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8603 nCmailMovesRegistered --;
\r
8605 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
8607 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
8608 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
8612 if (cmailOldMove == -1) {
\r
8613 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
8617 if (currentMove > cmailOldMove + 1) {
\r
8618 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
8622 if (currentMove < cmailOldMove) {
\r
8623 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
8627 if (forwardMostMove > currentMove) {
\r
8628 /* Silently truncate extra moves */
\r
8632 if ( (currentMove == cmailOldMove + 1)
\r
8633 || ( (currentMove == cmailOldMove)
\r
8634 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
8635 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
8636 if (gameInfo.result != GameUnfinished) {
\r
8637 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
8640 if (commentList[currentMove] != NULL) {
\r
8641 cmailCommentList[lastLoadGameNumber - 1]
\r
8642 = StrSave(commentList[currentMove]);
\r
8644 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
8646 if (appData.debugMode)
\r
8647 fprintf(debugFP, "Saving %s for game %d\n",
\r
8648 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8651 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
8653 f = fopen(string, "w");
\r
8654 if (appData.oldSaveStyle) {
\r
8655 SaveGameOldStyle(f); /* also closes the file */
\r
8657 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
8658 f = fopen(string, "w");
\r
8659 SavePosition(f, 0, NULL); /* also closes the file */
\r
8661 fprintf(f, "{--------------\n");
\r
8662 PrintPosition(f, currentMove);
\r
8663 fprintf(f, "--------------}\n\n");
\r
8665 SaveGame(f, 0, NULL); /* also closes the file*/
\r
8668 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
8669 nCmailMovesRegistered ++;
\r
8670 } else if (nCmailGames == 1) {
\r
8671 DisplayError("You have not made a move yet", 0);
\r
8682 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
8683 FILE *commandOutput;
\r
8684 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
8685 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
8691 if (! cmailMsgLoaded) {
\r
8692 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
8696 if (nCmailGames == nCmailResults) {
\r
8697 DisplayError("No unfinished games", 0);
\r
8701 #if CMAIL_PROHIBIT_REMAIL
\r
8702 if (cmailMailedMove) {
\r
8703 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
8704 DisplayError(msg, 0);
\r
8709 if (! (cmailMailedMove || RegisterMove())) return;
\r
8711 if ( cmailMailedMove
\r
8712 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
8713 sprintf(string, partCommandString,
\r
8714 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
8715 commandOutput = popen(string, "rb");
\r
8717 if (commandOutput == NULL) {
\r
8718 DisplayError("Failed to invoke cmail", 0);
\r
8720 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
8721 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
8723 if (nBuffers > 1) {
\r
8724 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
8725 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
8726 nBytes = MSG_SIZ - 1;
\r
8728 (void) memcpy(msg, buffer, nBytes);
\r
8730 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
8732 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
8733 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
8736 for (i = 0; i < nCmailGames; i ++) {
\r
8737 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
8742 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
8744 sprintf(buffer, "%s/%s.%s.archive",
\r
8746 appData.cmailGameName,
\r
8748 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
8749 cmailMsgLoaded = FALSE;
\r
8753 DisplayInformation(msg);
\r
8754 pclose(commandOutput);
\r
8757 if ((*cmailMsg) != '\0') {
\r
8758 DisplayInformation(cmailMsg);
\r
8763 #endif /* !WIN32 */
\r
8772 int prependComma = 0;
\r
8774 char string[MSG_SIZ]; /* Space for game-list */
\r
8777 if (!cmailMsgLoaded) return "";
\r
8779 if (cmailMailedMove) {
\r
8780 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
8782 /* Create a list of games left */
\r
8783 sprintf(string, "[");
\r
8784 for (i = 0; i < nCmailGames; i ++) {
\r
8785 if (! ( cmailMoveRegistered[i]
\r
8786 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
8787 if (prependComma) {
\r
8788 sprintf(number, ",%d", i + 1);
\r
8790 sprintf(number, "%d", i + 1);
\r
8794 strcat(string, number);
\r
8797 strcat(string, "]");
\r
8799 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
8800 switch (nCmailGames) {
\r
8803 "Still need to make move for game\n");
\r
8808 "Still need to make moves for both games\n");
\r
8813 "Still need to make moves for all %d games\n",
\r
8818 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
8821 "Still need to make a move for game %s\n",
\r
8826 if (nCmailResults == nCmailGames) {
\r
8827 sprintf(cmailMsg, "No unfinished games\n");
\r
8829 sprintf(cmailMsg, "Ready to send mail\n");
\r
8835 "Still need to make moves for games %s\n",
\r
8841 #endif /* WIN32 */
\r
8847 if (gameMode == Training)
\r
8848 SetTrainingModeOff();
\r
8850 Reset(TRUE, TRUE);
\r
8851 cmailMsgLoaded = FALSE;
\r
8852 if (appData.icsActive) {
\r
8853 SendToICS(ics_prefix);
\r
8854 SendToICS("refresh\n");
\r
8858 static int exiting = 0;
\r
8865 if (exiting > 2) {
\r
8866 /* Give up on clean exit */
\r
8869 if (exiting > 1) {
\r
8870 /* Keep trying for clean exit */
\r
8874 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
8876 if (telnetISR != NULL) {
\r
8877 RemoveInputSource(telnetISR);
\r
8879 if (icsPR != NoProc) {
\r
8880 DestroyChildProcess(icsPR, TRUE);
\r
8882 /* Save game if resource set and not already saved by GameEnds() */
\r
8883 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
\r
8884 if (*appData.saveGameFile != NULLCHAR) {
\r
8885 SaveGameToFile(appData.saveGameFile, TRUE);
\r
8886 } else if (appData.autoSaveGames) {
\r
8889 if (*appData.savePositionFile != NULLCHAR) {
\r
8890 SavePositionToFile(appData.savePositionFile);
\r
8893 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8895 /* Kill off chess programs */
\r
8896 if (first.pr != NoProc) {
\r
8897 ExitAnalyzeMode();
\r
8899 DoSleep( appData.delayBeforeQuit );
\r
8900 SendToProgram("quit\n", &first);
\r
8901 DoSleep( appData.delayAfterQuit );
\r
8902 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
8904 if (second.pr != NoProc) {
\r
8905 DoSleep( appData.delayBeforeQuit );
\r
8906 SendToProgram("quit\n", &second);
\r
8907 DoSleep( appData.delayAfterQuit );
\r
8908 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
8910 if (first.isr != NULL) {
\r
8911 RemoveInputSource(first.isr);
\r
8913 if (second.isr != NULL) {
\r
8914 RemoveInputSource(second.isr);
\r
8917 ShutDownFrontEnd();
\r
8924 if (appData.debugMode)
\r
8925 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
8929 if (gameMode == MachinePlaysWhite ||
\r
8930 gameMode == MachinePlaysBlack) {
\r
8933 DisplayBothClocks();
\r
8935 if (gameMode == PlayFromGameFile) {
\r
8936 if (appData.timeDelay >= 0)
\r
8937 AutoPlayGameLoop();
\r
8938 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
8939 Reset(FALSE, TRUE);
\r
8940 SendToICS(ics_prefix);
\r
8941 SendToICS("refresh\n");
\r
8942 } else if (currentMove < forwardMostMove) {
\r
8943 ForwardInner(forwardMostMove);
\r
8945 pauseExamInvalid = FALSE;
\r
8947 switch (gameMode) {
\r
8950 case IcsExamining:
\r
8951 pauseExamForwardMostMove = forwardMostMove;
\r
8952 pauseExamInvalid = FALSE;
\r
8953 /* fall through */
\r
8954 case IcsObserving:
\r
8955 case IcsPlayingWhite:
\r
8956 case IcsPlayingBlack:
\r
8960 case PlayFromGameFile:
\r
8961 (void) StopLoadGameTimer();
\r
8965 case BeginningOfGame:
\r
8966 if (appData.icsActive) return;
\r
8967 /* else fall through */
\r
8968 case MachinePlaysWhite:
\r
8969 case MachinePlaysBlack:
\r
8970 case TwoMachinesPlay:
\r
8971 if (forwardMostMove == 0)
\r
8972 return; /* don't pause if no one has moved */
\r
8973 if ((gameMode == MachinePlaysWhite &&
\r
8974 !WhiteOnMove(forwardMostMove)) ||
\r
8975 (gameMode == MachinePlaysBlack &&
\r
8976 WhiteOnMove(forwardMostMove))) {
\r
8987 EditCommentEvent()
\r
8989 char title[MSG_SIZ];
\r
8991 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
8992 strcpy(title, "Edit comment");
\r
8994 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
8995 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
8996 parseList[currentMove - 1]);
\r
8999 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
9006 char *tags = PGNTags(&gameInfo);
\r
9007 EditTagsPopUp(tags);
\r
9012 AnalyzeModeEvent()
\r
9014 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
9017 if (gameMode != AnalyzeFile) {
\r
9019 if (gameMode != EditGame) return;
\r
9020 ResurrectChessProgram();
\r
9021 SendToProgram("analyze\n", &first);
\r
9022 first.analyzing = TRUE;
\r
9023 /*first.maybeThinking = TRUE;*/
\r
9024 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9025 AnalysisPopUp("Analysis",
\r
9026 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9028 gameMode = AnalyzeMode;
\r
9033 StartAnalysisClock();
\r
9034 GetTimeMark(&lastNodeCountTime);
\r
9035 lastNodeCount = 0;
\r
9039 AnalyzeFileEvent()
\r
9041 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
9044 if (gameMode != AnalyzeMode) {
\r
9046 if (gameMode != EditGame) return;
\r
9047 ResurrectChessProgram();
\r
9048 SendToProgram("analyze\n", &first);
\r
9049 first.analyzing = TRUE;
\r
9050 /*first.maybeThinking = TRUE;*/
\r
9051 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9052 AnalysisPopUp("Analysis",
\r
9053 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9055 gameMode = AnalyzeFile;
\r
9060 StartAnalysisClock();
\r
9061 GetTimeMark(&lastNodeCountTime);
\r
9062 lastNodeCount = 0;
\r
9066 MachineWhiteEvent()
\r
9068 char buf[MSG_SIZ];
\r
9070 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
9074 if (gameMode == PlayFromGameFile ||
\r
9075 gameMode == TwoMachinesPlay ||
\r
9076 gameMode == Training ||
\r
9077 gameMode == AnalyzeMode ||
\r
9078 gameMode == EndOfGame)
\r
9081 if (gameMode == EditPosition)
\r
9082 EditPositionDone();
\r
9084 if (!WhiteOnMove(currentMove)) {
\r
9085 DisplayError("It is not White's turn", 0);
\r
9089 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9090 ExitAnalyzeMode();
\r
9092 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9093 gameMode == AnalyzeFile)
\r
9096 ResurrectChessProgram(); /* in case it isn't running */
\r
9097 gameMode = MachinePlaysWhite;
\r
9101 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9102 DisplayTitle(buf);
\r
9103 if (first.sendName) {
\r
9104 sprintf(buf, "name %s\n", gameInfo.black);
\r
9105 SendToProgram(buf, &first);
\r
9107 if (first.sendTime) {
\r
9108 if (first.useColors) {
\r
9109 SendToProgram("black\n", &first); /*gnu kludge*/
\r
9111 SendTimeRemaining(&first, TRUE);
\r
9113 if (first.useColors) {
\r
9114 SendToProgram("white\ngo\n", &first);
\r
9116 SendToProgram("go\n", &first);
\r
9118 SetMachineThinkingEnables();
\r
9119 first.maybeThinking = TRUE;
\r
9122 if (appData.autoFlipView && !flipView) {
\r
9123 flipView = !flipView;
\r
9124 DrawPosition(FALSE, NULL);
\r
9129 MachineBlackEvent()
\r
9131 char buf[MSG_SIZ];
\r
9133 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
9137 if (gameMode == PlayFromGameFile ||
\r
9138 gameMode == TwoMachinesPlay ||
\r
9139 gameMode == Training ||
\r
9140 gameMode == AnalyzeMode ||
\r
9141 gameMode == EndOfGame)
\r
9144 if (gameMode == EditPosition)
\r
9145 EditPositionDone();
\r
9147 if (WhiteOnMove(currentMove)) {
\r
9148 DisplayError("It is not Black's turn", 0);
\r
9152 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9153 ExitAnalyzeMode();
\r
9155 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9156 gameMode == AnalyzeFile)
\r
9159 ResurrectChessProgram(); /* in case it isn't running */
\r
9160 gameMode = MachinePlaysBlack;
\r
9164 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9165 DisplayTitle(buf);
\r
9166 if (first.sendName) {
\r
9167 sprintf(buf, "name %s\n", gameInfo.white);
\r
9168 SendToProgram(buf, &first);
\r
9170 if (first.sendTime) {
\r
9171 if (first.useColors) {
\r
9172 SendToProgram("white\n", &first); /*gnu kludge*/
\r
9174 SendTimeRemaining(&first, FALSE);
\r
9176 if (first.useColors) {
\r
9177 SendToProgram("black\ngo\n", &first);
\r
9179 SendToProgram("go\n", &first);
\r
9181 SetMachineThinkingEnables();
\r
9182 first.maybeThinking = TRUE;
\r
9185 if (appData.autoFlipView && flipView) {
\r
9186 flipView = !flipView;
\r
9187 DrawPosition(FALSE, NULL);
\r
9193 DisplayTwoMachinesTitle()
\r
9195 char buf[MSG_SIZ];
\r
9196 if (appData.matchGames > 0) {
\r
9197 if (first.twoMachinesColor[0] == 'w') {
\r
9198 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9199 gameInfo.white, gameInfo.black,
\r
9200 first.matchWins, second.matchWins,
\r
9201 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9203 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9204 gameInfo.white, gameInfo.black,
\r
9205 second.matchWins, first.matchWins,
\r
9206 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9209 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9211 DisplayTitle(buf);
\r
9215 TwoMachinesEvent P((void))
\r
9218 char buf[MSG_SIZ];
\r
9219 ChessProgramState *onmove;
\r
9221 if (appData.noChessProgram) return;
\r
9223 switch (gameMode) {
\r
9224 case TwoMachinesPlay:
\r
9226 case MachinePlaysWhite:
\r
9227 case MachinePlaysBlack:
\r
9228 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9229 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9232 /* fall through */
\r
9233 case BeginningOfGame:
\r
9234 case PlayFromGameFile:
\r
9237 if (gameMode != EditGame) return;
\r
9239 case EditPosition:
\r
9240 EditPositionDone();
\r
9244 ExitAnalyzeMode();
\r
9251 forwardMostMove = currentMove;
\r
9252 ResurrectChessProgram(); /* in case first program isn't running */
\r
9254 if (second.pr == NULL) {
\r
9255 StartChessProgram(&second);
\r
9256 if (second.protocolVersion == 1) {
\r
9257 TwoMachinesEventIfReady();
\r
9259 /* kludge: allow timeout for initial "feature" command */
\r
9261 DisplayMessage("", "Starting second chess program");
\r
9262 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9266 DisplayMessage("", "");
\r
9267 InitChessProgram(&second);
\r
9268 SendToProgram("force\n", &second);
\r
9269 if (startedFromSetupPosition) {
\r
9270 SendBoard(&second, backwardMostMove);
\r
9272 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9273 SendMoveToProgram(i, &second);
\r
9276 gameMode = TwoMachinesPlay;
\r
9280 DisplayTwoMachinesTitle();
\r
9282 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
9288 SendToProgram(first.computerString, &first);
\r
9289 if (first.sendName) {
\r
9290 sprintf(buf, "name %s\n", second.tidy);
\r
9291 SendToProgram(buf, &first);
\r
9293 SendToProgram(second.computerString, &second);
\r
9294 if (second.sendName) {
\r
9295 sprintf(buf, "name %s\n", first.tidy);
\r
9296 SendToProgram(buf, &second);
\r
9299 if (!first.sendTime || !second.sendTime) {
\r
9301 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9302 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9304 if (onmove->sendTime) {
\r
9305 if (onmove->useColors) {
\r
9306 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
9308 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
9310 if (onmove->useColors) {
\r
9311 SendToProgram(onmove->twoMachinesColor, onmove);
\r
9313 SendToProgram("go\n", onmove);
\r
9314 onmove->maybeThinking = TRUE;
\r
9315 SetMachineThinkingEnables();
\r
9323 if (gameMode == Training) {
\r
9324 SetTrainingModeOff();
\r
9325 gameMode = PlayFromGameFile;
\r
9326 DisplayMessage("", "Training mode off");
\r
9328 gameMode = Training;
\r
9329 animateTraining = appData.animate;
\r
9331 /* make sure we are not already at the end of the game */
\r
9332 if (currentMove < forwardMostMove) {
\r
9333 SetTrainingModeOn();
\r
9334 DisplayMessage("", "Training mode on");
\r
9336 gameMode = PlayFromGameFile;
\r
9337 DisplayError("Already at end of game", 0);
\r
9346 if (!appData.icsActive) return;
\r
9347 switch (gameMode) {
\r
9348 case IcsPlayingWhite:
\r
9349 case IcsPlayingBlack:
\r
9350 case IcsObserving:
\r
9352 case BeginningOfGame:
\r
9353 case IcsExamining:
\r
9359 case EditPosition:
\r
9360 EditPositionDone();
\r
9365 ExitAnalyzeMode();
\r
9373 gameMode = IcsIdle;
\r
9384 switch (gameMode) {
\r
9386 SetTrainingModeOff();
\r
9388 case MachinePlaysWhite:
\r
9389 case MachinePlaysBlack:
\r
9390 case BeginningOfGame:
\r
9391 SendToProgram("force\n", &first);
\r
9392 SetUserThinkingEnables();
\r
9394 case PlayFromGameFile:
\r
9395 (void) StopLoadGameTimer();
\r
9396 if (gameFileFP != NULL) {
\r
9397 gameFileFP = NULL;
\r
9400 case EditPosition:
\r
9401 EditPositionDone();
\r
9405 ExitAnalyzeMode();
\r
9406 SendToProgram("force\n", &first);
\r
9408 case TwoMachinesPlay:
\r
9409 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9410 ResurrectChessProgram();
\r
9411 SetUserThinkingEnables();
\r
9414 ResurrectChessProgram();
\r
9416 case IcsPlayingBlack:
\r
9417 case IcsPlayingWhite:
\r
9418 DisplayError("Warning: You are still playing a game", 0);
\r
9420 case IcsObserving:
\r
9421 DisplayError("Warning: You are still observing a game", 0);
\r
9423 case IcsExamining:
\r
9424 DisplayError("Warning: You are still examining a game", 0);
\r
9435 first.offeredDraw = second.offeredDraw = 0;
\r
9437 if (gameMode == PlayFromGameFile) {
\r
9438 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9439 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9443 if (gameMode == MachinePlaysWhite ||
\r
9444 gameMode == MachinePlaysBlack ||
\r
9445 gameMode == TwoMachinesPlay ||
\r
9446 gameMode == EndOfGame) {
\r
9447 i = forwardMostMove;
\r
9448 while (i > currentMove) {
\r
9449 SendToProgram("undo\n", &first);
\r
9452 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9453 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9454 DisplayBothClocks();
\r
9455 if (whiteFlag || blackFlag) {
\r
9456 whiteFlag = blackFlag = 0;
\r
9461 gameMode = EditGame;
\r
9468 EditPositionEvent()
\r
9470 if (gameMode == EditPosition) {
\r
9476 if (gameMode != EditGame) return;
\r
9478 gameMode = EditPosition;
\r
9481 if (currentMove > 0)
\r
9482 CopyBoard(boards[0], boards[currentMove]);
\r
9484 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
9486 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9487 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9494 if (first.analysisSupport && first.analyzing) {
\r
9495 SendToProgram("exit\n", &first);
\r
9496 first.analyzing = FALSE;
\r
9498 AnalysisPopDown();
\r
9499 thinkOutput[0] = NULLCHAR;
\r
9503 EditPositionDone()
\r
9505 startedFromSetupPosition = TRUE;
\r
9506 InitChessProgram(&first);
\r
9507 SendToProgram("force\n", &first);
\r
9508 if (blackPlaysFirst) {
\r
9509 strcpy(moveList[0], "");
\r
9510 strcpy(parseList[0], "");
\r
9511 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9512 CopyBoard(boards[1], boards[0]);
\r
9514 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9516 SendBoard(&first, forwardMostMove);
\r
9518 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9519 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9520 gameMode = EditGame;
\r
9522 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9523 ClearHighlights(); /* [AS] */
\r
9526 /* Pause for `ms' milliseconds */
\r
9527 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9537 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
9540 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9542 SendMultiLineToICS(buf)
\r
9545 char temp[MSG_SIZ+1], *p;
\r
9548 len = strlen(buf);
\r
9549 if (len > MSG_SIZ)
\r
9552 strncpy(temp, buf, len);
\r
9557 if (*p == '\n' || *p == '\r')
\r
9562 strcat(temp, "\n");
\r
9564 SendToPlayer(temp, strlen(temp));
\r
9568 SetWhiteToPlayEvent()
\r
9570 if (gameMode == EditPosition) {
\r
9571 blackPlaysFirst = FALSE;
\r
9572 DisplayBothClocks(); /* works because currentMove is 0 */
\r
9573 } else if (gameMode == IcsExamining) {
\r
9574 SendToICS(ics_prefix);
\r
9575 SendToICS("tomove white\n");
\r
9580 SetBlackToPlayEvent()
\r
9582 if (gameMode == EditPosition) {
\r
9583 blackPlaysFirst = TRUE;
\r
9584 currentMove = 1; /* kludge */
\r
9585 DisplayBothClocks();
\r
9587 } else if (gameMode == IcsExamining) {
\r
9588 SendToICS(ics_prefix);
\r
9589 SendToICS("tomove black\n");
\r
9594 EditPositionMenuEvent(selection, x, y)
\r
9595 ChessSquare selection;
\r
9598 char buf[MSG_SIZ];
\r
9599 ChessSquare piece = boards[0][y][x];
\r
9601 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
9603 switch (selection) {
\r
9605 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
9606 SendToICS(ics_prefix);
\r
9607 SendToICS("bsetup clear\n");
\r
9608 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
9609 SendToICS(ics_prefix);
\r
9610 SendToICS("clearboard\n");
\r
9612 for (x = 0; x < BOARD_WIDTH; x++) {
\r
9613 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
9614 if (gameMode == IcsExamining) {
\r
9615 if (boards[currentMove][y][x] != EmptySquare) {
\r
9616 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
9617 AAA + x, ONE + y);
\r
9621 boards[0][y][x] = EmptySquare;
\r
9626 if (gameMode == EditPosition) {
\r
9627 DrawPosition(FALSE, boards[0]);
\r
9632 SetWhiteToPlayEvent();
\r
9636 SetBlackToPlayEvent();
\r
9640 if (gameMode == IcsExamining) {
\r
9641 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
9644 boards[0][y][x] = EmptySquare;
\r
9645 DrawPosition(FALSE, boards[0]);
\r
9649 case PromotePiece:
\r
9650 if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||
\r
9651 piece >= (int)BlackPawn && piece < (int)BlackWazir ) {
\r
9652 selection = (ChessSquare) (PROMOTED piece);
\r
9653 } else if(piece == EmptySquare) selection = WhiteWazir;
\r
9654 else selection = (ChessSquare)((int)piece - 1);
\r
9655 goto defaultlabel;
\r
9658 if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||
\r
9659 piece >= (int)BlackUnicorn && piece < (int)BlackKing ) {
\r
9660 selection = (ChessSquare) (DEMOTED piece);
\r
9661 } else if( piece == WhiteKing || piece == BlackKing )
\r
9662 selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);
\r
9663 else if(piece == EmptySquare) selection = BlackWazir;
\r
9664 else selection = (ChessSquare)((int)piece + 1);
\r
9665 goto defaultlabel;
\r
9669 if(gameInfo.variant == VariantShatranj ||
\r
9670 gameInfo.variant == VariantXiangqi ||
\r
9671 gameInfo.variant == VariantCourier )
\r
9672 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
9673 goto defaultlabel;
\r
9677 if(gameInfo.variant == VariantXiangqi)
\r
9678 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
9679 if(gameInfo.variant == VariantKnightmate)
\r
9680 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
9683 if (gameMode == IcsExamining) {
\r
9684 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
9685 PieceToChar(selection), AAA + x, ONE + y);
\r
9688 boards[0][y][x] = selection;
\r
9689 DrawPosition(FALSE, boards[0]);
\r
9697 DropMenuEvent(selection, x, y)
\r
9698 ChessSquare selection;
\r
9701 ChessMove moveType;
\r
9703 switch (gameMode) {
\r
9704 case IcsPlayingWhite:
\r
9705 case MachinePlaysBlack:
\r
9706 if (!WhiteOnMove(currentMove)) {
\r
9707 DisplayMoveError("It is Black's turn");
\r
9710 moveType = WhiteDrop;
\r
9712 case IcsPlayingBlack:
\r
9713 case MachinePlaysWhite:
\r
9714 if (WhiteOnMove(currentMove)) {
\r
9715 DisplayMoveError("It is White's turn");
\r
9718 moveType = BlackDrop;
\r
9721 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
9727 if (moveType == BlackDrop && selection < BlackPawn) {
\r
9728 selection = (ChessSquare) ((int) selection
\r
9729 + (int) BlackPawn - (int) WhitePawn);
\r
9731 if (boards[currentMove][y][x] != EmptySquare) {
\r
9732 DisplayMoveError("That square is occupied");
\r
9736 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
9742 /* Accept a pending offer of any kind from opponent */
\r
9744 if (appData.icsActive) {
\r
9745 SendToICS(ics_prefix);
\r
9746 SendToICS("accept\n");
\r
9747 } else if (cmailMsgLoaded) {
\r
9748 if (currentMove == cmailOldMove &&
\r
9749 commentList[cmailOldMove] != NULL &&
\r
9750 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9751 "Black offers a draw" : "White offers a draw")) {
\r
9753 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9754 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9756 DisplayError("There is no pending offer on this move", 0);
\r
9757 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9760 /* Not used for offers from chess program */
\r
9767 /* Decline a pending offer of any kind from opponent */
\r
9769 if (appData.icsActive) {
\r
9770 SendToICS(ics_prefix);
\r
9771 SendToICS("decline\n");
\r
9772 } else if (cmailMsgLoaded) {
\r
9773 if (currentMove == cmailOldMove &&
\r
9774 commentList[cmailOldMove] != NULL &&
\r
9775 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9776 "Black offers a draw" : "White offers a draw")) {
\r
9778 AppendComment(cmailOldMove, "Draw declined");
\r
9779 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
9782 DisplayError("There is no pending offer on this move", 0);
\r
9785 /* Not used for offers from chess program */
\r
9792 /* Issue ICS rematch command */
\r
9793 if (appData.icsActive) {
\r
9794 SendToICS(ics_prefix);
\r
9795 SendToICS("rematch\n");
\r
9802 /* Call your opponent's flag (claim a win on time) */
\r
9803 if (appData.icsActive) {
\r
9804 SendToICS(ics_prefix);
\r
9805 SendToICS("flag\n");
\r
9807 switch (gameMode) {
\r
9810 case MachinePlaysWhite:
\r
9813 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9816 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
9818 DisplayError("Your opponent is not out of time", 0);
\r
9821 case MachinePlaysBlack:
\r
9824 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9827 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
9829 DisplayError("Your opponent is not out of time", 0);
\r
9839 /* Offer draw or accept pending draw offer from opponent */
\r
9841 if (appData.icsActive) {
\r
9842 /* Note: tournament rules require draw offers to be
\r
9843 made after you make your move but before you punch
\r
9844 your clock. Currently ICS doesn't let you do that;
\r
9845 instead, you immediately punch your clock after making
\r
9846 a move, but you can offer a draw at any time. */
\r
9848 SendToICS(ics_prefix);
\r
9849 SendToICS("draw\n");
\r
9850 } else if (cmailMsgLoaded) {
\r
9851 if (currentMove == cmailOldMove &&
\r
9852 commentList[cmailOldMove] != NULL &&
\r
9853 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9854 "Black offers a draw" : "White offers a draw")) {
\r
9855 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9856 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9857 } else if (currentMove == cmailOldMove + 1) {
\r
9858 char *offer = WhiteOnMove(cmailOldMove) ?
\r
9859 "White offers a draw" : "Black offers a draw";
\r
9860 AppendComment(currentMove, offer);
\r
9861 DisplayComment(currentMove - 1, offer);
\r
9862 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
9864 DisplayError("You must make your move before offering a draw", 0);
\r
9865 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9867 } else if (first.offeredDraw) {
\r
9868 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
9870 if (first.sendDrawOffers) {
\r
9871 SendToProgram("draw\n", &first);
\r
9872 userOfferedDraw = TRUE;
\r
9880 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
9882 if (appData.icsActive) {
\r
9883 SendToICS(ics_prefix);
\r
9884 SendToICS("adjourn\n");
\r
9886 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
9894 /* Offer Abort or accept pending Abort offer from opponent */
\r
9896 if (appData.icsActive) {
\r
9897 SendToICS(ics_prefix);
\r
9898 SendToICS("abort\n");
\r
9900 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
9907 /* Resign. You can do this even if it's not your turn. */
\r
9909 if (appData.icsActive) {
\r
9910 SendToICS(ics_prefix);
\r
9911 SendToICS("resign\n");
\r
9913 switch (gameMode) {
\r
9914 case MachinePlaysWhite:
\r
9915 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9917 case MachinePlaysBlack:
\r
9918 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9921 if (cmailMsgLoaded) {
\r
9923 if (WhiteOnMove(cmailOldMove)) {
\r
9924 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9926 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9928 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
9939 StopObservingEvent()
\r
9941 /* Stop observing current games */
\r
9942 SendToICS(ics_prefix);
\r
9943 SendToICS("unobserve\n");
\r
9947 StopExaminingEvent()
\r
9949 /* Stop observing current game */
\r
9950 SendToICS(ics_prefix);
\r
9951 SendToICS("unexamine\n");
\r
9955 ForwardInner(target)
\r
9960 if (appData.debugMode)
\r
9961 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
9962 target, currentMove, forwardMostMove);
\r
9964 if (gameMode == EditPosition)
\r
9967 if (gameMode == PlayFromGameFile && !pausing)
\r
9970 if (gameMode == IcsExamining && pausing)
\r
9971 limit = pauseExamForwardMostMove;
\r
9973 limit = forwardMostMove;
\r
9975 if (target > limit) target = limit;
\r
9977 if (target > 0 && moveList[target - 1][0]) {
\r
9978 int fromX, fromY, toX, toY;
\r
9979 toX = moveList[target - 1][2] - AAA;
\r
9980 toY = moveList[target - 1][3] - ONE;
\r
9981 if (moveList[target - 1][1] == '@') {
\r
9982 if (appData.highlightLastMove) {
\r
9983 SetHighlights(-1, -1, toX, toY);
\r
9986 fromX = moveList[target - 1][0] - AAA;
\r
9987 fromY = moveList[target - 1][1] - ONE;
\r
9988 if (target == currentMove + 1) {
\r
9989 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
9991 if (appData.highlightLastMove) {
\r
9992 SetHighlights(fromX, fromY, toX, toY);
\r
9996 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9997 gameMode == Training || gameMode == PlayFromGameFile ||
\r
9998 gameMode == AnalyzeFile) {
\r
9999 while (currentMove < target) {
\r
10000 SendMoveToProgram(currentMove++, &first);
\r
10003 currentMove = target;
\r
10006 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10007 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10008 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10010 DisplayBothClocks();
\r
10011 DisplayMove(currentMove - 1);
\r
10012 DrawPosition(FALSE, boards[currentMove]);
\r
10013 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10014 if (commentList[currentMove] && !matchMode && gameMode != Training) {
\r
10015 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10023 if (gameMode == IcsExamining && !pausing) {
\r
10024 SendToICS(ics_prefix);
\r
10025 SendToICS("forward\n");
\r
10027 ForwardInner(currentMove + 1);
\r
10034 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10035 /* to optimze, we temporarily turn off analysis mode while we feed
\r
10036 * the remaining moves to the engine. Otherwise we get analysis output
\r
10037 * after each move.
\r
10039 if (first.analysisSupport) {
\r
10040 SendToProgram("exit\nforce\n", &first);
\r
10041 first.analyzing = FALSE;
\r
10045 if (gameMode == IcsExamining && !pausing) {
\r
10046 SendToICS(ics_prefix);
\r
10047 SendToICS("forward 999999\n");
\r
10049 ForwardInner(forwardMostMove);
\r
10052 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10053 /* we have fed all the moves, so reactivate analysis mode */
\r
10054 SendToProgram("analyze\n", &first);
\r
10055 first.analyzing = TRUE;
\r
10056 /*first.maybeThinking = TRUE;*/
\r
10057 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10062 BackwardInner(target)
\r
10065 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
10067 if (appData.debugMode)
\r
10068 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
10069 target, currentMove, forwardMostMove);
\r
10071 if (gameMode == EditPosition) return;
\r
10072 if (currentMove <= backwardMostMove) {
\r
10073 ClearHighlights();
\r
10074 DrawPosition(full_redraw, boards[currentMove]);
\r
10077 if (gameMode == PlayFromGameFile && !pausing)
\r
10080 if (moveList[target][0]) {
\r
10081 int fromX, fromY, toX, toY;
\r
10082 toX = moveList[target][2] - AAA;
\r
10083 toY = moveList[target][3] - ONE;
\r
10084 if (moveList[target][1] == '@') {
\r
10085 if (appData.highlightLastMove) {
\r
10086 SetHighlights(-1, -1, toX, toY);
\r
10089 fromX = moveList[target][0] - AAA;
\r
10090 fromY = moveList[target][1] - ONE;
\r
10091 if (target == currentMove - 1) {
\r
10092 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
10094 if (appData.highlightLastMove) {
\r
10095 SetHighlights(fromX, fromY, toX, toY);
\r
10099 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
10100 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
10101 while (currentMove > target) {
\r
10102 SendToProgram("undo\n", &first);
\r
10106 currentMove = target;
\r
10109 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10110 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10111 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10113 DisplayBothClocks();
\r
10114 DisplayMove(currentMove - 1);
\r
10115 DrawPosition(full_redraw, boards[currentMove]);
\r
10116 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10117 if (commentList[currentMove] != NULL) {
\r
10118 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10125 if (gameMode == IcsExamining && !pausing) {
\r
10126 SendToICS(ics_prefix);
\r
10127 SendToICS("backward\n");
\r
10129 BackwardInner(currentMove - 1);
\r
10136 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10137 /* to optimze, we temporarily turn off analysis mode while we undo
\r
10138 * all the moves. Otherwise we get analysis output after each undo.
\r
10140 if (first.analysisSupport) {
\r
10141 SendToProgram("exit\nforce\n", &first);
\r
10142 first.analyzing = FALSE;
\r
10146 if (gameMode == IcsExamining && !pausing) {
\r
10147 SendToICS(ics_prefix);
\r
10148 SendToICS("backward 999999\n");
\r
10150 BackwardInner(backwardMostMove);
\r
10153 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10154 /* we have fed all the moves, so reactivate analysis mode */
\r
10155 SendToProgram("analyze\n", &first);
\r
10156 first.analyzing = TRUE;
\r
10157 /*first.maybeThinking = TRUE;*/
\r
10158 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10163 ToNrEvent(int to)
\r
10165 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
10166 if (to >= forwardMostMove) to = forwardMostMove;
\r
10167 if (to <= backwardMostMove) to = backwardMostMove;
\r
10168 if (to < currentMove) {
\r
10169 BackwardInner(to);
\r
10171 ForwardInner(to);
\r
10178 if (gameMode != IcsExamining) {
\r
10179 DisplayError("You are not examining a game", 0);
\r
10183 DisplayError("You can't revert while pausing", 0);
\r
10186 SendToICS(ics_prefix);
\r
10187 SendToICS("revert\n");
\r
10191 RetractMoveEvent()
\r
10193 switch (gameMode) {
\r
10194 case MachinePlaysWhite:
\r
10195 case MachinePlaysBlack:
\r
10196 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10197 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10200 if (forwardMostMove < 2) return;
\r
10201 currentMove = forwardMostMove = forwardMostMove - 2;
\r
10202 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10203 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10204 DisplayBothClocks();
\r
10205 DisplayMove(currentMove - 1);
\r
10206 ClearHighlights();/*!! could figure this out*/
\r
10207 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
10208 SendToProgram("remove\n", &first);
\r
10209 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
10212 case BeginningOfGame:
\r
10216 case IcsPlayingWhite:
\r
10217 case IcsPlayingBlack:
\r
10218 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
10219 SendToICS(ics_prefix);
\r
10220 SendToICS("takeback 2\n");
\r
10222 SendToICS(ics_prefix);
\r
10223 SendToICS("takeback 1\n");
\r
10232 ChessProgramState *cps;
\r
10234 switch (gameMode) {
\r
10235 case MachinePlaysWhite:
\r
10236 if (!WhiteOnMove(forwardMostMove)) {
\r
10237 DisplayError("It is your turn", 0);
\r
10242 case MachinePlaysBlack:
\r
10243 if (WhiteOnMove(forwardMostMove)) {
\r
10244 DisplayError("It is your turn", 0);
\r
10249 case TwoMachinesPlay:
\r
10250 if (WhiteOnMove(forwardMostMove) ==
\r
10251 (first.twoMachinesColor[0] == 'w')) {
\r
10257 case BeginningOfGame:
\r
10261 SendToProgram("?\n", cps);
\r
10265 TruncateGameEvent()
\r
10268 if (gameMode != EditGame) return;
\r
10275 if (forwardMostMove > currentMove) {
\r
10276 if (gameInfo.resultDetails != NULL) {
\r
10277 free(gameInfo.resultDetails);
\r
10278 gameInfo.resultDetails = NULL;
\r
10279 gameInfo.result = GameUnfinished;
\r
10281 forwardMostMove = currentMove;
\r
10282 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
10290 if (appData.noChessProgram) return;
\r
10291 switch (gameMode) {
\r
10292 case MachinePlaysWhite:
\r
10293 if (WhiteOnMove(forwardMostMove)) {
\r
10294 DisplayError("Wait until your turn", 0);
\r
10298 case BeginningOfGame:
\r
10299 case MachinePlaysBlack:
\r
10300 if (!WhiteOnMove(forwardMostMove)) {
\r
10301 DisplayError("Wait until your turn", 0);
\r
10306 DisplayError("No hint available", 0);
\r
10309 SendToProgram("hint\n", &first);
\r
10310 hintRequested = TRUE;
\r
10316 if (appData.noChessProgram) return;
\r
10317 switch (gameMode) {
\r
10318 case MachinePlaysWhite:
\r
10319 if (WhiteOnMove(forwardMostMove)) {
\r
10320 DisplayError("Wait until your turn", 0);
\r
10324 case BeginningOfGame:
\r
10325 case MachinePlaysBlack:
\r
10326 if (!WhiteOnMove(forwardMostMove)) {
\r
10327 DisplayError("Wait until your turn", 0);
\r
10331 case EditPosition:
\r
10332 EditPositionDone();
\r
10334 case TwoMachinesPlay:
\r
10339 SendToProgram("bk\n", &first);
\r
10340 bookOutput[0] = NULLCHAR;
\r
10341 bookRequested = TRUE;
\r
10347 char *tags = PGNTags(&gameInfo);
\r
10348 TagsPopUp(tags, CmailMsg());
\r
10352 /* end button procedures */
\r
10355 PrintPosition(fp, move)
\r
10361 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
10362 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
10363 char c = PieceToChar(boards[move][i][j]);
\r
10364 fputc(c == 'x' ? '.' : c, fp);
\r
10365 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
10368 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
10369 fprintf(fp, "white to play\n");
\r
10371 fprintf(fp, "black to play\n");
\r
10375 PrintOpponents(fp)
\r
10378 if (gameInfo.white != NULL) {
\r
10379 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
10381 fprintf(fp, "\n");
\r
10385 /* Find last component of program's own name, using some heuristics */
\r
10387 TidyProgramName(prog, host, buf)
\r
10388 char *prog, *host, buf[MSG_SIZ];
\r
10391 int local = (strcmp(host, "localhost") == 0);
\r
10392 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
10394 while (*p == ' ') p++;
\r
10397 if (*prog == '"' || *prog == '\'') {
\r
10398 q = strchr(prog + 1, *prog);
\r
10400 q = strchr(prog, ' ');
\r
10402 if (q == NULL) q = prog + strlen(prog);
\r
10404 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
10406 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
10407 memcpy(buf, p, q - p);
\r
10408 buf[q - p] = NULLCHAR;
\r
10410 strcat(buf, "@");
\r
10411 strcat(buf, host);
\r
10416 TimeControlTagValue()
\r
10418 char buf[MSG_SIZ];
\r
10419 if (!appData.clockMode) {
\r
10420 strcpy(buf, "-");
\r
10421 } else if (movesPerSession > 0) {
\r
10422 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
10423 } else if (timeIncrement == 0) {
\r
10424 sprintf(buf, "%ld", timeControl/1000);
\r
10426 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
10428 return StrSave(buf);
\r
10434 /* This routine is used only for certain modes */
\r
10435 VariantClass v = gameInfo.variant;
\r
10436 ClearGameInfo(&gameInfo);
\r
10437 gameInfo.variant = v;
\r
10439 switch (gameMode) {
\r
10440 case MachinePlaysWhite:
\r
10441 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10442 gameInfo.site = StrSave(HostName());
\r
10443 gameInfo.date = PGNDate();
\r
10444 gameInfo.round = StrSave("-");
\r
10445 gameInfo.white = StrSave(first.tidy);
\r
10446 gameInfo.black = StrSave(UserName());
\r
10447 gameInfo.timeControl = TimeControlTagValue();
\r
10450 case MachinePlaysBlack:
\r
10451 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10452 gameInfo.site = StrSave(HostName());
\r
10453 gameInfo.date = PGNDate();
\r
10454 gameInfo.round = StrSave("-");
\r
10455 gameInfo.white = StrSave(UserName());
\r
10456 gameInfo.black = StrSave(first.tidy);
\r
10457 gameInfo.timeControl = TimeControlTagValue();
\r
10460 case TwoMachinesPlay:
\r
10461 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10462 gameInfo.site = StrSave(HostName());
\r
10463 gameInfo.date = PGNDate();
\r
10464 if (matchGame > 0) {
\r
10465 char buf[MSG_SIZ];
\r
10466 sprintf(buf, "%d", matchGame);
\r
10467 gameInfo.round = StrSave(buf);
\r
10469 gameInfo.round = StrSave("-");
\r
10471 if (first.twoMachinesColor[0] == 'w') {
\r
10472 gameInfo.white = StrSave(first.tidy);
\r
10473 gameInfo.black = StrSave(second.tidy);
\r
10475 gameInfo.white = StrSave(second.tidy);
\r
10476 gameInfo.black = StrSave(first.tidy);
\r
10478 gameInfo.timeControl = TimeControlTagValue();
\r
10482 gameInfo.event = StrSave("Edited game");
\r
10483 gameInfo.site = StrSave(HostName());
\r
10484 gameInfo.date = PGNDate();
\r
10485 gameInfo.round = StrSave("-");
\r
10486 gameInfo.white = StrSave("-");
\r
10487 gameInfo.black = StrSave("-");
\r
10490 case EditPosition:
\r
10491 gameInfo.event = StrSave("Edited position");
\r
10492 gameInfo.site = StrSave(HostName());
\r
10493 gameInfo.date = PGNDate();
\r
10494 gameInfo.round = StrSave("-");
\r
10495 gameInfo.white = StrSave("-");
\r
10496 gameInfo.black = StrSave("-");
\r
10499 case IcsPlayingWhite:
\r
10500 case IcsPlayingBlack:
\r
10501 case IcsObserving:
\r
10502 case IcsExamining:
\r
10505 case PlayFromGameFile:
\r
10506 gameInfo.event = StrSave("Game from non-PGN file");
\r
10507 gameInfo.site = StrSave(HostName());
\r
10508 gameInfo.date = PGNDate();
\r
10509 gameInfo.round = StrSave("-");
\r
10510 gameInfo.white = StrSave("?");
\r
10511 gameInfo.black = StrSave("?");
\r
10520 ReplaceComment(index, text)
\r
10526 while (*text == '\n') text++;
\r
10527 len = strlen(text);
\r
10528 while (len > 0 && text[len - 1] == '\n') len--;
\r
10530 if (commentList[index] != NULL)
\r
10531 free(commentList[index]);
\r
10534 commentList[index] = NULL;
\r
10537 commentList[index] = (char *) malloc(len + 2);
\r
10538 strncpy(commentList[index], text, len);
\r
10539 commentList[index][len] = '\n';
\r
10540 commentList[index][len + 1] = NULLCHAR;
\r
10553 if (ch == '\r') continue;
\r
10555 } while (ch != '\0');
\r
10559 AppendComment(index, text)
\r
10566 GetInfoFromComment( index, text );
\r
10569 while (*text == '\n') text++;
\r
10570 len = strlen(text);
\r
10571 while (len > 0 && text[len - 1] == '\n') len--;
\r
10573 if (len == 0) return;
\r
10575 if (commentList[index] != NULL) {
\r
10576 old = commentList[index];
\r
10577 oldlen = strlen(old);
\r
10578 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
10579 strcpy(commentList[index], old);
\r
10581 strncpy(&commentList[index][oldlen], text, len);
\r
10582 commentList[index][oldlen + len] = '\n';
\r
10583 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
10585 commentList[index] = (char *) malloc(len + 2);
\r
10586 strncpy(commentList[index], text, len);
\r
10587 commentList[index][len] = '\n';
\r
10588 commentList[index][len + 1] = NULLCHAR;
\r
10592 static char * FindStr( char * text, char * sub_text )
\r
10594 char * result = strstr( text, sub_text );
\r
10596 if( result != NULL ) {
\r
10597 result += strlen( sub_text );
\r
10603 /* [AS] Try to extract PV info from PGN comment */
\r
10604 void GetInfoFromComment( int index, char * text )
\r
10606 if( text != NULL && index > 0 ) {
\r
10610 char * s_eval = FindStr( text, "[%eval " );
\r
10611 char * s_emt = FindStr( text, "[%emt " );
\r
10613 if( s_eval != NULL || s_emt != NULL ) {
\r
10617 if( s_eval != NULL ) {
\r
10618 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
10622 if( delim != ']' ) {
\r
10627 if( s_emt != NULL ) {
\r
10631 /* We expect something like: [+|-]nnn.nn/dd */
\r
10632 char * sep = strchr( text, '/' );
\r
10633 int score_lo = 0;
\r
10635 if( sep == NULL || sep < (text+4) ) {
\r
10639 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
10643 if( score_lo < 0 || score_lo >= 100 ) {
\r
10647 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
10650 if( depth <= 0 ) {
\r
10658 pvInfoList[index-1].depth = depth;
\r
10659 pvInfoList[index-1].score = score;
\r
10660 pvInfoList[index-1].time = time;
\r
10665 SendToProgram(message, cps)
\r
10667 ChessProgramState *cps;
\r
10669 int count, outCount, error;
\r
10670 char buf[MSG_SIZ];
\r
10672 if (cps->pr == NULL) return;
\r
10675 if (appData.debugMode) {
\r
10677 GetTimeMark(&now);
\r
10678 fprintf(debugFP, "%ld >%-6s: %s",
\r
10679 SubtractTimeMarks(&now, &programStartTime),
\r
10680 cps->which, message);
\r
10683 count = strlen(message);
\r
10684 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
10685 if (outCount < count && !exiting) {
\r
10686 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
10687 DisplayFatalError(buf, error, 1);
\r
10692 ReceiveFromProgram(isr, closure, message, count, error)
\r
10693 InputSourceRef isr;
\r
10694 VOIDSTAR closure;
\r
10700 char buf[MSG_SIZ];
\r
10701 ChessProgramState *cps = (ChessProgramState *)closure;
\r
10703 if (isr != cps->isr) return; /* Killed intentionally */
\r
10704 if (count <= 0) {
\r
10705 if (count == 0) {
\r
10707 "Error: %s chess program (%s) exited unexpectedly",
\r
10708 cps->which, cps->program);
\r
10709 RemoveInputSource(cps->isr);
\r
10710 DisplayFatalError(buf, 0, 1);
\r
10713 "Error reading from %s chess program (%s)",
\r
10714 cps->which, cps->program);
\r
10715 RemoveInputSource(cps->isr);
\r
10717 /* [AS] Program is misbehaving badly... kill it */
\r
10718 if( count == -2 ) {
\r
10719 DestroyChildProcess( cps->pr, 9 );
\r
10720 cps->pr = NoProc;
\r
10723 DisplayFatalError(buf, error, 1);
\r
10725 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10729 if ((end_str = strchr(message, '\r')) != NULL)
\r
10730 *end_str = NULLCHAR;
\r
10731 if ((end_str = strchr(message, '\n')) != NULL)
\r
10732 *end_str = NULLCHAR;
\r
10734 if (appData.debugMode) {
\r
10736 GetTimeMark(&now);
\r
10737 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
10738 SubtractTimeMarks(&now, &programStartTime),
\r
10739 cps->which, message);
\r
10741 HandleMachineMove(message, cps);
\r
10746 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
10747 ChessProgramState *cps;
\r
10748 int mps, inc, sd, st;
\r
10751 char buf[MSG_SIZ];
\r
10752 int seconds = (tc / 1000) % 60;
\r
10754 if( timeControl_2 > 0 ) {
\r
10755 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
10756 tc = timeControl_2;
\r
10761 /* Set exact time per move, normally using st command */
\r
10762 if (cps->stKludge) {
\r
10763 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
10764 seconds = st % 60;
\r
10765 if (seconds == 0) {
\r
10766 sprintf(buf, "level 1 %d\n", st/60);
\r
10768 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
10771 sprintf(buf, "st %d\n", st);
\r
10774 /* Set conventional or incremental time control, using level command */
\r
10775 if (seconds == 0) {
\r
10776 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
10777 Fixed in later versions, but still avoid :seconds
\r
10778 when seconds is 0. */
\r
10779 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
10781 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
10782 seconds, inc/1000);
\r
10785 SendToProgram(buf, cps);
\r
10787 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
10788 /* Orthogonally, limit search to given depth */
\r
10790 if (cps->sdKludge) {
\r
10791 sprintf(buf, "depth\n%d\n", sd);
\r
10793 sprintf(buf, "sd %d\n", sd);
\r
10795 SendToProgram(buf, cps);
\r
10800 SendTimeRemaining(cps, machineWhite)
\r
10801 ChessProgramState *cps;
\r
10802 int /*boolean*/ machineWhite;
\r
10804 char message[MSG_SIZ];
\r
10805 long time, otime;
\r
10807 /* Note: this routine must be called when the clocks are stopped
\r
10808 or when they have *just* been set or switched; otherwise
\r
10809 it will be off by the time since the current tick started.
\r
10811 if (machineWhite) {
\r
10812 time = whiteTimeRemaining / 10;
\r
10813 otime = blackTimeRemaining / 10;
\r
10815 time = blackTimeRemaining / 10;
\r
10816 otime = whiteTimeRemaining / 10;
\r
10818 if (time <= 0) time = 1;
\r
10819 if (otime <= 0) otime = 1;
\r
10821 sprintf(message, "time %ld\n", time);
\r
10822 SendToProgram(message, cps);
\r
10824 sprintf(message, "otim %ld\n", otime);
\r
10825 SendToProgram(message, cps);
\r
10829 BoolFeature(p, name, loc, cps)
\r
10833 ChessProgramState *cps;
\r
10835 char buf[MSG_SIZ];
\r
10836 int len = strlen(name);
\r
10838 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10840 sscanf(*p, "%d", &val);
\r
10841 *loc = (val != 0);
\r
10842 while (**p && **p != ' ') (*p)++;
\r
10843 sprintf(buf, "accepted %s\n", name);
\r
10844 SendToProgram(buf, cps);
\r
10851 IntFeature(p, name, loc, cps)
\r
10855 ChessProgramState *cps;
\r
10857 char buf[MSG_SIZ];
\r
10858 int len = strlen(name);
\r
10859 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10861 sscanf(*p, "%d", loc);
\r
10862 while (**p && **p != ' ') (*p)++;
\r
10863 sprintf(buf, "accepted %s\n", name);
\r
10864 SendToProgram(buf, cps);
\r
10871 StringFeature(p, name, loc, cps)
\r
10875 ChessProgramState *cps;
\r
10877 char buf[MSG_SIZ];
\r
10878 int len = strlen(name);
\r
10879 if (strncmp((*p), name, len) == 0
\r
10880 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
10882 sscanf(*p, "%[^\"]", loc);
\r
10883 while (**p && **p != '\"') (*p)++;
\r
10884 if (**p == '\"') (*p)++;
\r
10885 sprintf(buf, "accepted %s\n", name);
\r
10886 SendToProgram(buf, cps);
\r
10893 FeatureDone(cps, val)
\r
10894 ChessProgramState* cps;
\r
10897 DelayedEventCallback cb = GetDelayedEvent();
\r
10898 if ((cb == InitBackEnd3 && cps == &first) ||
\r
10899 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
10900 CancelDelayedEvent();
\r
10901 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
10903 cps->initDone = val;
\r
10906 /* Parse feature command from engine */
\r
10908 ParseFeatures(args, cps)
\r
10910 ChessProgramState *cps;
\r
10915 char buf[MSG_SIZ];
\r
10918 while (*p == ' ') p++;
\r
10919 if (*p == NULLCHAR) return;
\r
10921 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
10922 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
10923 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
10924 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
10925 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
10926 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
10927 /* Engine can disable reuse, but can't enable it if user said no */
\r
10928 if (!val) cps->reuse = FALSE;
\r
10931 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
10932 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
10933 if (gameMode == TwoMachinesPlay) {
\r
10934 DisplayTwoMachinesTitle();
\r
10936 DisplayTitle("");
\r
10940 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
10941 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
10942 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
10943 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
10944 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
10945 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
10946 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
10947 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
10948 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
10949 if (IntFeature(&p, "done", &val, cps)) {
\r
10950 FeatureDone(cps, val);
\r
10953 /* Added by Tord: */
\r
10954 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
10955 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
10956 /* End of additions by Tord */
\r
10958 /* unknown feature: complain and skip */
\r
10960 while (*q && *q != '=') q++;
\r
10961 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
10962 SendToProgram(buf, cps);
\r
10966 if (*p == '\"') {
\r
10968 while (*p && *p != '\"') p++;
\r
10969 if (*p == '\"') p++;
\r
10971 while (*p && *p != ' ') p++;
\r
10979 PeriodicUpdatesEvent(newState)
\r
10982 if (newState == appData.periodicUpdates)
\r
10985 appData.periodicUpdates=newState;
\r
10987 /* Display type changes, so update it now */
\r
10988 DisplayAnalysis();
\r
10990 /* Get the ball rolling again... */
\r
10992 AnalysisPeriodicEvent(1);
\r
10993 StartAnalysisClock();
\r
10998 PonderNextMoveEvent(newState)
\r
11001 if (newState == appData.ponderNextMove) return;
\r
11002 if (gameMode == EditPosition) EditPositionDone();
\r
11004 SendToProgram("hard\n", &first);
\r
11005 if (gameMode == TwoMachinesPlay) {
\r
11006 SendToProgram("hard\n", &second);
\r
11009 SendToProgram("easy\n", &first);
\r
11010 thinkOutput[0] = NULLCHAR;
\r
11011 if (gameMode == TwoMachinesPlay) {
\r
11012 SendToProgram("easy\n", &second);
\r
11015 appData.ponderNextMove = newState;
\r
11019 ShowThinkingEvent(newState)
\r
11022 if (newState == appData.showThinking) return;
\r
11023 if (gameMode == EditPosition) EditPositionDone();
\r
11025 SendToProgram("post\n", &first);
\r
11026 if (gameMode == TwoMachinesPlay) {
\r
11027 SendToProgram("post\n", &second);
\r
11030 SendToProgram("nopost\n", &first);
\r
11031 thinkOutput[0] = NULLCHAR;
\r
11032 if (gameMode == TwoMachinesPlay) {
\r
11033 SendToProgram("nopost\n", &second);
\r
11036 appData.showThinking = newState;
\r
11040 AskQuestionEvent(title, question, replyPrefix, which)
\r
11041 char *title; char *question; char *replyPrefix; char *which;
\r
11043 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
11044 if (pr == NoProc) return;
\r
11045 AskQuestion(title, question, replyPrefix, pr);
\r
11049 DisplayMove(moveNumber)
\r
11052 char message[MSG_SIZ];
\r
11053 char res[MSG_SIZ];
\r
11054 char cpThinkOutput[MSG_SIZ];
\r
11056 if (moveNumber == forwardMostMove - 1 ||
\r
11057 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11059 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
11061 if (strchr(cpThinkOutput, '\n')) {
\r
11062 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
11065 *cpThinkOutput = NULLCHAR;
\r
11068 /* [AS] Hide thinking from human user */
\r
11069 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
11070 *cpThinkOutput = NULLCHAR;
\r
11071 if( thinkOutput[0] != NULLCHAR ) {
\r
11074 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
11075 cpThinkOutput[i] = '.';
\r
11077 cpThinkOutput[i] = NULLCHAR;
\r
11078 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
11082 if (moveNumber == forwardMostMove - 1 &&
\r
11083 gameInfo.resultDetails != NULL) {
\r
11084 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
11085 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
11087 sprintf(res, " {%s} %s",
\r
11088 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
11091 res[0] = NULLCHAR;
\r
11094 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11095 DisplayMessage(res, cpThinkOutput);
\r
11097 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
11098 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11099 parseList[moveNumber], res);
\r
11100 DisplayMessage(message, cpThinkOutput);
\r
11105 DisplayAnalysisText(text)
\r
11108 char buf[MSG_SIZ];
\r
11110 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11111 sprintf(buf, "Analysis (%s)", first.tidy);
\r
11112 AnalysisPopUp(buf, text);
\r
11117 only_one_move(str)
\r
11120 while (*str && isspace(*str)) ++str;
\r
11121 while (*str && !isspace(*str)) ++str;
\r
11122 if (!*str) return 1;
\r
11123 while (*str && isspace(*str)) ++str;
\r
11124 if (!*str) return 1;
\r
11129 DisplayAnalysis()
\r
11131 char buf[MSG_SIZ];
\r
11132 char lst[MSG_SIZ / 2];
\r
11134 static char *xtra[] = { "", " (--)", " (++)" };
\r
11137 if (programStats.time == 0) {
\r
11138 programStats.time = 1;
\r
11141 if (programStats.got_only_move) {
\r
11142 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
11144 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
11146 nps = (((double)programStats.nodes) /
\r
11147 (((double)programStats.time)/100.0));
\r
11149 cs = programStats.time % 100;
\r
11150 s = programStats.time / 100;
\r
11151 h = (s / (60*60));
\r
11156 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
11157 if (programStats.move_name[0] != NULLCHAR) {
\r
11158 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11159 programStats.depth,
\r
11160 programStats.nr_moves-programStats.moves_left,
\r
11161 programStats.nr_moves, programStats.move_name,
\r
11162 ((float)programStats.score)/100.0, lst,
\r
11163 only_one_move(lst)?
\r
11164 xtra[programStats.got_fail] : "",
\r
11165 programStats.nodes, (int)nps, h, m, s, cs);
\r
11167 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11168 programStats.depth,
\r
11169 programStats.nr_moves-programStats.moves_left,
\r
11170 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
11172 only_one_move(lst)?
\r
11173 xtra[programStats.got_fail] : "",
\r
11174 programStats.nodes, (int)nps, h, m, s, cs);
\r
11177 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11178 programStats.depth,
\r
11179 ((float)programStats.score)/100.0,
\r
11181 only_one_move(lst)?
\r
11182 xtra[programStats.got_fail] : "",
\r
11183 programStats.nodes, (int)nps, h, m, s, cs);
\r
11186 DisplayAnalysisText(buf);
\r
11190 DisplayComment(moveNumber, text)
\r
11194 char title[MSG_SIZ];
\r
11196 if( appData.autoDisplayComment ) {
\r
11197 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11198 strcpy(title, "Comment");
\r
11200 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
11201 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11202 parseList[moveNumber]);
\r
11205 CommentPopUp(title, text);
\r
11209 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
11210 * might be busy thinking or pondering. It can be omitted if your
\r
11211 * gnuchess is configured to stop thinking immediately on any user
\r
11212 * input. However, that gnuchess feature depends on the FIONREAD
\r
11213 * ioctl, which does not work properly on some flavors of Unix.
\r
11217 ChessProgramState *cps;
\r
11220 if (!cps->useSigint) return;
\r
11221 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
11222 switch (gameMode) {
\r
11223 case MachinePlaysWhite:
\r
11224 case MachinePlaysBlack:
\r
11225 case TwoMachinesPlay:
\r
11226 case IcsPlayingWhite:
\r
11227 case IcsPlayingBlack:
\r
11228 case AnalyzeMode:
\r
11229 case AnalyzeFile:
\r
11230 /* Skip if we know it isn't thinking */
\r
11231 if (!cps->maybeThinking) return;
\r
11232 if (appData.debugMode)
\r
11233 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
11234 InterruptChildProcess(cps->pr);
\r
11235 cps->maybeThinking = FALSE;
\r
11240 #endif /*ATTENTION*/
\r
11246 if (whiteTimeRemaining <= 0) {
\r
11247 if (!whiteFlag) {
\r
11248 whiteFlag = TRUE;
\r
11249 if (appData.icsActive) {
\r
11250 if (appData.autoCallFlag &&
\r
11251 gameMode == IcsPlayingBlack && !blackFlag) {
\r
11252 SendToICS(ics_prefix);
\r
11253 SendToICS("flag\n");
\r
11257 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11259 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
11260 if (appData.autoCallFlag) {
\r
11261 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
11268 if (blackTimeRemaining <= 0) {
\r
11269 if (!blackFlag) {
\r
11270 blackFlag = TRUE;
\r
11271 if (appData.icsActive) {
\r
11272 if (appData.autoCallFlag &&
\r
11273 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
11274 SendToICS(ics_prefix);
\r
11275 SendToICS("flag\n");
\r
11279 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11281 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
11282 if (appData.autoCallFlag) {
\r
11283 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
11294 CheckTimeControl()
\r
11296 if (!appData.clockMode || appData.icsActive ||
\r
11297 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
11299 if (timeIncrement >= 0) {
\r
11300 if (WhiteOnMove(forwardMostMove)) {
\r
11301 blackTimeRemaining += timeIncrement;
\r
11303 whiteTimeRemaining += timeIncrement;
\r
11307 * add time to clocks when time control is achieved
\r
11309 if (movesPerSession) {
\r
11310 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
\r
11312 /* White made time control */
\r
11313 whiteTimeRemaining += GetTimeControlForWhite();
\r
11316 /* Black made time control */
\r
11317 blackTimeRemaining += GetTimeControlForBlack();
\r
11326 DisplayBothClocks()
\r
11328 int wom = gameMode == EditPosition ?
\r
11329 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
11330 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
11331 DisplayBlackClock(blackTimeRemaining, !wom);
\r
11335 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
11336 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
11337 to use other calls if you don't. Clocks will be less accurate if
\r
11338 you have neither ftime nor gettimeofday.
\r
11341 /* Get the current time as a TimeMark */
\r
11346 #if HAVE_GETTIMEOFDAY
\r
11348 struct timeval timeVal;
\r
11349 struct timezone timeZone;
\r
11351 gettimeofday(&timeVal, &timeZone);
\r
11352 tm->sec = (long) timeVal.tv_sec;
\r
11353 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
11355 #else /*!HAVE_GETTIMEOFDAY*/
\r
11358 #include <sys/timeb.h>
\r
11359 struct timeb timeB;
\r
11362 tm->sec = (long) timeB.time;
\r
11363 tm->ms = (int) timeB.millitm;
\r
11365 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
11366 tm->sec = (long) time(NULL);
\r
11372 /* Return the difference in milliseconds between two
\r
11373 time marks. We assume the difference will fit in a long!
\r
11376 SubtractTimeMarks(tm2, tm1)
\r
11377 TimeMark *tm2, *tm1;
\r
11379 return 1000L*(tm2->sec - tm1->sec) +
\r
11380 (long) (tm2->ms - tm1->ms);
\r
11385 * Code to manage the game clocks.
\r
11387 * In tournament play, black starts the clock and then white makes a move.
\r
11388 * We give the human user a slight advantage if he is playing white---the
\r
11389 * clocks don't run until he makes his first move, so it takes zero time.
\r
11390 * Also, we don't account for network lag, so we could get out of sync
\r
11391 * with GNU Chess's clock -- but then, referees are always right.
\r
11394 static TimeMark tickStartTM;
\r
11395 static long intendedTickLength;
\r
11398 NextTickLength(timeRemaining)
\r
11399 long timeRemaining;
\r
11401 long nominalTickLength, nextTickLength;
\r
11403 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
11404 nominalTickLength = 100L;
\r
11406 nominalTickLength = 1000L;
\r
11407 nextTickLength = timeRemaining % nominalTickLength;
\r
11408 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
11410 return nextTickLength;
\r
11413 /* Adjust clock one minute up or down */
\r
11415 AdjustClock(Boolean which, int dir)
\r
11417 if(which) blackTimeRemaining += 60000*dir;
\r
11418 else whiteTimeRemaining += 60000*dir;
\r
11419 DisplayBothClocks();
\r
11422 /* Stop clocks and reset to a fresh time control */
\r
11426 (void) StopClockTimer();
\r
11427 if (appData.icsActive) {
\r
11428 whiteTimeRemaining = blackTimeRemaining = 0;
\r
11430 whiteTimeRemaining = GetTimeControlForWhite();
\r
11431 blackTimeRemaining = GetTimeControlForBlack();
\r
11433 if (whiteFlag || blackFlag) {
\r
11434 DisplayTitle("");
\r
11435 whiteFlag = blackFlag = FALSE;
\r
11437 DisplayBothClocks();
\r
11440 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
11442 /* Decrement running clock by amount of time that has passed */
\r
11444 DecrementClocks()
\r
11446 long timeRemaining;
\r
11447 long lastTickLength, fudge;
\r
11450 if (!appData.clockMode) return;
\r
11451 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11453 GetTimeMark(&now);
\r
11455 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11457 /* Fudge if we woke up a little too soon */
\r
11458 fudge = intendedTickLength - lastTickLength;
\r
11459 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
11461 if (WhiteOnMove(forwardMostMove)) {
\r
11462 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
11463 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
11464 WhiteOnMove(currentMove));
\r
11466 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
11467 DisplayBlackClock(blackTimeRemaining - fudge,
\r
11468 !WhiteOnMove(currentMove));
\r
11471 if (CheckFlags()) return;
\r
11473 tickStartTM = now;
\r
11474 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
11475 StartClockTimer(intendedTickLength);
\r
11477 /* if the time remaining has fallen below the alarm threshold, sound the
\r
11478 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
11479 * with increment) the time remaining has increased to a level above the
\r
11480 * threshold, reset the alarm so it can sound again.
\r
11483 if (appData.icsActive && appData.icsAlarm) {
\r
11485 /* make sure we are dealing with the user's clock */
\r
11486 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
11487 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
11490 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
11491 alarmSounded = FALSE;
\r
11492 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
11493 PlayAlarmSound();
\r
11494 alarmSounded = TRUE;
\r
11500 /* A player has just moved, so stop the previously running
\r
11501 clock and (if in clock mode) start the other one.
\r
11502 We redisplay both clocks in case we're in ICS mode, because
\r
11503 ICS gives us an update to both clocks after every move.
\r
11504 Note that this routine is called *after* forwardMostMove
\r
11505 is updated, so the last fractional tick must be subtracted
\r
11506 from the color that is *not* on move now.
\r
11511 long lastTickLength;
\r
11513 int flagged = FALSE;
\r
11515 GetTimeMark(&now);
\r
11517 if (StopClockTimer() && appData.clockMode) {
\r
11518 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11519 if (WhiteOnMove(forwardMostMove)) {
\r
11520 blackTimeRemaining -= lastTickLength;
\r
11522 whiteTimeRemaining -= lastTickLength;
\r
11524 /* [HGM] save time for PGN file if engine did not give it */
\r
11525 if(pvInfoList[forwardMostMove-1].time == -1)
\r
11526 pvInfoList[forwardMostMove-1].time = lastTickLength/100;
\r
11527 flagged = CheckFlags();
\r
11529 CheckTimeControl();
\r
11531 if (flagged || !appData.clockMode) return;
\r
11533 switch (gameMode) {
\r
11534 case MachinePlaysBlack:
\r
11535 case MachinePlaysWhite:
\r
11536 case BeginningOfGame:
\r
11537 if (pausing) return;
\r
11541 case PlayFromGameFile:
\r
11542 case IcsExamining:
\r
11549 tickStartTM = now;
\r
11550 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11551 whiteTimeRemaining : blackTimeRemaining);
\r
11552 StartClockTimer(intendedTickLength);
\r
11556 /* Stop both clocks */
\r
11560 long lastTickLength;
\r
11563 if (!StopClockTimer()) return;
\r
11564 if (!appData.clockMode) return;
\r
11566 GetTimeMark(&now);
\r
11568 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11569 if (WhiteOnMove(forwardMostMove)) {
\r
11570 whiteTimeRemaining -= lastTickLength;
\r
11571 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
11573 blackTimeRemaining -= lastTickLength;
\r
11574 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
11579 /* Start clock of player on move. Time may have been reset, so
\r
11580 if clock is already running, stop and restart it. */
\r
11584 (void) StopClockTimer(); /* in case it was running already */
\r
11585 DisplayBothClocks();
\r
11586 if (CheckFlags()) return;
\r
11588 if (!appData.clockMode) return;
\r
11589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11591 GetTimeMark(&tickStartTM);
\r
11592 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
11593 whiteTimeRemaining : blackTimeRemaining);
\r
11594 StartClockTimer(intendedTickLength);
\r
11601 long second, minute, hour, day;
\r
11603 static char buf[32];
\r
11605 if (ms > 0 && ms <= 9900) {
\r
11606 /* convert milliseconds to tenths, rounding up */
\r
11607 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
11609 sprintf(buf, " %03.1f ", tenths/10.0);
\r
11613 /* convert milliseconds to seconds, rounding up */
\r
11614 /* use floating point to avoid strangeness of integer division
\r
11615 with negative dividends on many machines */
\r
11616 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
11618 if (second < 0) {
\r
11620 second = -second;
\r
11623 day = second / (60 * 60 * 24);
\r
11624 second = second % (60 * 60 * 24);
\r
11625 hour = second / (60 * 60);
\r
11626 second = second % (60 * 60);
\r
11627 minute = second / 60;
\r
11628 second = second % 60;
\r
11631 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
11632 sign, day, hour, minute, second);
\r
11633 else if (hour > 0)
\r
11634 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
11636 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
11643 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
11646 StrStr(string, match)
\r
11647 char *string, *match;
\r
11651 length = strlen(match);
\r
11653 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
11654 if (!strncmp(match, string, length))
\r
11661 StrCaseStr(string, match)
\r
11662 char *string, *match;
\r
11664 int i, j, length;
\r
11666 length = strlen(match);
\r
11668 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
11669 for (j = 0; j < length; j++) {
\r
11670 if (ToLower(match[j]) != ToLower(string[j]))
\r
11673 if (j == length) return string;
\r
11679 #ifndef _amigados
\r
11681 StrCaseCmp(s1, s2)
\r
11687 c1 = ToLower(*s1++);
\r
11688 c2 = ToLower(*s2++);
\r
11689 if (c1 > c2) return 1;
\r
11690 if (c1 < c2) return -1;
\r
11691 if (c1 == NULLCHAR) return 0;
\r
11700 return isupper(c) ? tolower(c) : c;
\r
11708 return islower(c) ? toupper(c) : c;
\r
11710 #endif /* !_amigados */
\r
11718 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
11725 StrSavePtr(s, savePtr)
\r
11726 char *s, **savePtr;
\r
11731 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
11732 strcpy(*savePtr, s);
\r
11734 return(*savePtr);
\r
11742 char buf[MSG_SIZ];
\r
11744 clock = time((time_t *)NULL);
\r
11745 tm = localtime(&clock);
\r
11746 sprintf(buf, "%04d.%02d.%02d",
\r
11747 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
11748 return StrSave(buf);
\r
11753 PositionToFEN(move, useFEN960)
\r
11757 int i, j, fromX, fromY, toX, toY;
\r
11762 ChessSquare piece;
\r
11764 whiteToPlay = (gameMode == EditPosition) ?
\r
11765 !blackPlaysFirst : (move % 2 == 0);
\r
11768 /* Piece placement data */
\r
11769 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11771 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11772 if (boards[move][i][j] == EmptySquare) {
\r
11774 } else { ChessSquare piece = boards[move][i][j];
\r
11775 if (emptycount > 0) {
\r
11776 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11777 *p++ = '0' + emptycount;
\r
11778 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11781 if(PieceToChar(piece) == '+') {
\r
11782 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
11784 piece = (ChessSquare)(DEMOTED piece);
\r
11786 *p++ = PieceToChar(piece);
\r
11787 if(p[-1] == '~') {
\r
11788 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
11789 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
11794 if (emptycount > 0) {
\r
11795 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11796 *p++ = '0' + emptycount;
\r
11797 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11804 /* Active color */
\r
11805 *p++ = whiteToPlay ? 'w' : 'b';
\r
11808 if(nrCastlingRights) {
\r
11809 /* HACK: we don't keep track of castling availability, so fake it! */
\r
11810 /* Tord! please fix with the aid of castlingRights[move][...] */
\r
11812 /* PUSH Fabien & Tord */
\r
11814 /* Declare all potential FRC castling rights (conservative) */
\r
11815 /* outermost rook on each side of the king */
\r
11817 if( gameInfo.variant == VariantFischeRandom ) {
\r
11822 /* White castling rights */
\r
11824 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11826 if (boards[move][0][fk] == WhiteKing) {
\r
11828 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11829 if (boards[move][0][fr] == WhiteRook) {
\r
11830 *p++ = useFEN960 ? 'A' + fr : 'K';
\r
11835 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11836 if (boards[move][0][fr] == WhiteRook) {
\r
11837 *p++ = useFEN960 ? 'A' + fr : 'Q';
\r
11844 /* Black castling rights */
\r
11846 for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {
\r
11848 if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {
\r
11850 for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */
\r
11851 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11852 *p++ = useFEN960 ? 'a' + fr : 'k';
\r
11857 for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */
\r
11858 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11859 *p++ = useFEN960 ? 'a' + fr : 'q';
\r
11866 if (q == p) *p++ = '-'; /* No castling rights */
\r
11872 #ifdef OLDCASTLINGCODE
\r
11873 if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {
\r
11874 if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';
\r
11875 if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';
\r
11877 if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {
\r
11878 if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';
\r
11879 if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';
\r
11882 /* [HGM] write true castling rights */
\r
11883 if( nrCastlingRights == 6 ) {
\r
11884 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
11885 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
11886 if(castlingRights[move][1] == BOARD_LEFT &&
\r
11887 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
11888 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
11889 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
11890 if(castlingRights[move][4] == BOARD_LEFT &&
\r
11891 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
11894 if (q == p) *p++ = '-';
\r
11898 /* POP Fabien & Tord */
\r
11901 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
11902 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
11903 /* En passant target square */
\r
11904 if (move > backwardMostMove) {
\r
11905 fromX = moveList[move - 1][0] - AAA;
\r
11906 fromY = moveList[move - 1][1] - ONE;
\r
11907 toX = moveList[move - 1][2] - AAA;
\r
11908 toY = moveList[move - 1][3] - ONE;
\r
11909 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
11910 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
11911 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
11913 /* 2-square pawn move just happened */
\r
11914 *p++ = toX + AAA;
\r
11915 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
11925 /* [HGM] print Crazyhouse or Shogi holdings */
\r
11926 if( gameInfo.holdingsWidth ) {
\r
11928 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
11929 piece = boards[move][i][BOARD_WIDTH-1];
\r
11930 if( piece != EmptySquare )
\r
11931 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
11932 *p++ = PieceToChar(piece);
\r
11934 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
11935 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
11936 if( piece != EmptySquare )
\r
11937 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
11938 *p++ = PieceToChar(piece);
\r
11941 if( q == p ) *p++ = '-';
\r
11945 /* [HGM] find reversible plies */
\r
11946 { int i = 0, j=move;
\r
11948 if (appData.debugMode) { int k;
\r
11949 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
11950 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
11951 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
11955 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
11956 if( j == backwardMostMove ) i += initialRulePlies;
\r
11957 sprintf(p, "%d ", i);
\r
11958 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
11960 /* Fullmove number */
\r
11961 sprintf(p, "%d", (move / 2) + 1);
\r
11963 return StrSave(buf);
\r
11967 ParseFEN(board, blackPlaysFirst, fen)
\r
11969 int *blackPlaysFirst;
\r
11975 ChessSquare piece;
\r
11979 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
11980 if(gameInfo.holdingsWidth) {
\r
11981 for(i=0; i<BOARD_HEIGHT; i++) {
\r
11982 board[i][0] = EmptySquare; /* black holdings */
\r
11983 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
11984 board[i][1] = (ChessSquare) 0; /* black counts */
\r
11985 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
11989 /* Piece placement data */
\r
11990 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11993 if (*p == '/' || *p == ' ') {
\r
11994 if (*p == '/') p++;
\r
11995 emptycount = gameInfo.boardWidth - j;
\r
11996 while (emptycount--)
\r
11997 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
11999 #if(BOARD_SIZE >= 10)
\r
12000 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
12001 p++; emptycount=10;
\r
12002 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12003 while (emptycount--)
\r
12004 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12006 } else if (isdigit(*p)) {
\r
12007 emptycount = *p++ - '0';
\r
12008 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
12009 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12010 while (emptycount--)
\r
12011 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12012 } else if (*p == '+' || isalpha(*p)) {
\r
12013 if (j >= gameInfo.boardWidth) return FALSE;
\r
12015 piece = CharToPiece(*++p);
\r
12016 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
12017 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
12018 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
12019 } else piece = CharToPiece(*p++);
\r
12021 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
12022 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
12023 piece = (ChessSquare) (PROMOTED piece);
\r
12024 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
12027 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
12033 while (*p == '/' || *p == ' ') p++;
\r
12035 /* Active color */
\r
12038 *blackPlaysFirst = FALSE;
\r
12041 *blackPlaysFirst = TRUE;
\r
12047 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
12048 /* return the extra info in global variiables */
\r
12050 /* set defaults in case FEN is incomplete */
\r
12051 FENepStatus = EP_UNKNOWN;
\r
12052 for(i=0; i<nrCastlingRights; i++ ) {
\r
12053 FENcastlingRights[i] = initialRights[i];
\r
12054 } /* assume possible unless obviously impossible */
\r
12055 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
12056 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
12057 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
12058 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
12059 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
12060 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
12061 FENrulePlies = 0;
\r
12063 while(*p==' ') p++;
\r
12064 if(nrCastlingRights) {
\r
12065 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
12066 /* castling indicator present, so default becomes no castlings */
\r
12067 for(i=0; i<nrCastlingRights; i++ ) {
\r
12068 FENcastlingRights[i] = -1;
\r
12071 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
12074 FENcastlingRights[0] = BOARD_RGHT-1;
\r
12075 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
12078 FENcastlingRights[1] = BOARD_LEFT;
\r
12079 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
12082 FENcastlingRights[3] = BOARD_RGHT-1;
\r
12083 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
12086 FENcastlingRights[4] = BOARD_LEFT;
\r
12087 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
12093 while(*p==' ') p++;
\r
12096 /* read e.p. field in games that know e.p. capture */
\r
12097 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
12098 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
12100 p++; FENepStatus = EP_NONE;
\r
12102 char c = *p++ - AAA;
\r
12104 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
12105 if(*p >= '0' && *p <='9') *p++;
\r
12110 /* [HGM] look for Crazyhouse holdings here */
\r
12111 while(*p==' ') p++;
\r
12112 if( gameInfo.holdingsWidth ) {
\r
12113 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
12114 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
12115 /* if we would allow FEN reading to set board size, we would */
\r
12116 /* have to add holdings and shift the board read so far here */
\r
12117 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
12119 if((int) piece >= (int) BlackPawn ) {
\r
12120 i = (int)piece - (int)BlackPawn;
\r
12121 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12122 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
12123 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
12125 i = (int)piece - (int)WhitePawn;
\r
12126 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12127 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
12128 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
12135 if(sscanf(p, "%d", &i) == 1) {
\r
12136 FENrulePlies = i; /* 50-move ply counter */
\r
12137 /* (The move number is still ignored) */
\r
12144 EditPositionPasteFEN(char *fen)
\r
12146 if (fen != NULL) {
\r
12147 Board initial_position;
\r
12149 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
12150 DisplayError("Bad FEN position in clipboard", 0);
\r
12153 int savedBlackPlaysFirst = blackPlaysFirst;
\r
12154 EditPositionEvent();
\r
12155 blackPlaysFirst = savedBlackPlaysFirst;
\r
12156 CopyBoard(boards[0], initial_position);
\r
12157 /* [HGM] copy FEN attributes as well */
\r
12159 initialRulePlies = FENrulePlies;
\r
12160 epStatus[0] = FENepStatus;
\r
12161 for( i=0; i<nrCastlingRights; i++ )
\r
12162 castlingRights[0][i] = FENcastlingRights[i];
\r
12164 EditPositionDone();
\r
12165 DisplayBothClocks();
\r
12166 DrawPosition(FALSE, boards[currentMove]);
\r