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 fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
429 { WhiteFairyRook, WhiteFairyKnight, WhiteFairyBishop, WhiteQueen,
\r
430 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
431 { BlackFairyRook, BlackFairyKnight, BlackFairyBishop, BlackQueen,
\r
432 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
435 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
436 { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn,
\r
437 WhiteKing, WhiteFairyBishop, WhiteKnight, WhiteRook },
\r
438 { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn,
\r
439 BlackKing, BlackFairyBishop, BlackKnight, BlackRook }
\r
442 #if (BOARD_SIZE>=9)
\r
443 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
444 { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteFairyPawn,
\r
445 WhiteKing, WhiteFairyPawn, WhiteFairyBishop, WhiteKnight, WhiteRook },
\r
446 { BlackRook, BlackKnight, BlackFairyBishop, BlackFairyPawn,
\r
447 BlackKing, BlackFairyPawn, BlackFairyBishop, BlackKnight, BlackRook }
\r
450 #define XiangqiPosition FIDEPosition
\r
453 #if (BOARD_SIZE>=10)
\r
454 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
455 { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen,
\r
456 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
457 { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen,
\r
458 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
462 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
463 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
464 WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook },
\r
465 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
466 BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook }
\r
469 #define GothicArray CapablancaArray
\r
472 #else // !(BOARD_SIZE>=10)
\r
473 #define CapablancaArray FIDEArray
\r
474 #define GothicArray FIDEArray
\r
475 #endif // !(BOARD_SIZE>=10)
\r
477 #if (BOARD_SIZE>=12)
\r
478 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
479 { WhiteRook, WhiteKnight, WhiteFairyBishop, WhiteBishop, WhiteFairyKing, WhiteKing,
\r
480 WhiteFairyPawn, WhiteFairyRook, WhiteBishop, WhiteFairyBishop, WhiteKnight, WhiteRook },
\r
481 { BlackRook, BlackKnight, BlackFairyBishop, BlackBishop, BlackFairyKing, BlackKing,
\r
482 BlackFairyPawn, BlackFairyRook, BlackBishop, BlackFairyBishop, BlackKnight, BlackRook }
\r
484 #else // !(BOARD_SIZE>=12)
\r
485 #define CourierArray CapablancaArray
\r
486 #endif // !(BOARD_SIZE>=12)
\r
490 Board initialPosition;
\r
493 /* Convert str to a rating. Checks for special cases of "----",
\r
495 "++++", etc. Also strips ()'s */
\r
497 string_to_rating(str)
\r
500 while(*str && !isdigit(*str)) ++str;
\r
502 return 0; /* One of the special "no rating" cases */
\r
508 ClearProgramStats()
\r
510 /* Init programStats */
\r
511 programStats.movelist[0] = 0;
\r
512 programStats.depth = 0;
\r
513 programStats.nr_moves = 0;
\r
514 programStats.moves_left = 0;
\r
515 programStats.nodes = 0;
\r
516 programStats.time = 100;
\r
517 programStats.score = 0;
\r
518 programStats.got_only_move = 0;
\r
519 programStats.got_fail = 0;
\r
520 programStats.line_is_book = 0;
\r
526 int matched, min, sec;
\r
528 GetTimeMark(&programStartTime);
\r
530 ClearProgramStats();
\r
531 programStats.ok_to_send = 1;
\r
532 programStats.seen_stat = 0;
\r
535 * Initialize game list
\r
537 ListNew(&gameList);
\r
541 * Internet chess server status
\r
543 if (appData.icsActive) {
\r
544 appData.matchMode = FALSE;
\r
545 appData.matchGames = 0;
\r
547 appData.noChessProgram = !appData.zippyPlay;
\r
549 appData.zippyPlay = FALSE;
\r
550 appData.zippyTalk = FALSE;
\r
551 appData.noChessProgram = TRUE;
\r
553 if (*appData.icsHelper != NULLCHAR) {
\r
554 appData.useTelnet = TRUE;
\r
555 appData.telnetProgram = appData.icsHelper;
\r
558 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
561 /* [AS] Initialize pv info list [HGM] and game state */
\r
565 for( i=0; i<MAX_MOVES; i++ ) {
\r
566 pvInfoList[i].depth = -1;
\r
567 epStatus[i]=EP_NONE;
\r
568 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
573 * Parse timeControl resource
\r
575 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
576 appData.movesPerSession)) {
\r
578 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
579 DisplayFatalError(buf, 0, 2);
\r
583 * Parse searchTime resource
\r
585 if (*appData.searchTime != NULLCHAR) {
\r
586 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
587 if (matched == 1) {
\r
588 searchTime = min * 60;
\r
589 } else if (matched == 2) {
\r
590 searchTime = min * 60 + sec;
\r
593 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
594 DisplayFatalError(buf, 0, 2);
\r
598 /* [AS] Adjudication threshold */
\r
599 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
601 first.which = "first";
\r
602 second.which = "second";
\r
603 first.maybeThinking = second.maybeThinking = FALSE;
\r
604 first.pr = second.pr = NoProc;
\r
605 first.isr = second.isr = NULL;
\r
606 first.sendTime = second.sendTime = 2;
\r
607 first.sendDrawOffers = 1;
\r
608 if (appData.firstPlaysBlack) {
\r
609 first.twoMachinesColor = "black\n";
\r
610 second.twoMachinesColor = "white\n";
\r
612 first.twoMachinesColor = "white\n";
\r
613 second.twoMachinesColor = "black\n";
\r
615 first.program = appData.firstChessProgram;
\r
616 second.program = appData.secondChessProgram;
\r
617 first.host = appData.firstHost;
\r
618 second.host = appData.secondHost;
\r
619 first.dir = appData.firstDirectory;
\r
620 second.dir = appData.secondDirectory;
\r
621 first.other = &second;
\r
622 second.other = &first;
\r
623 first.initString = appData.initString;
\r
624 second.initString = appData.secondInitString;
\r
625 first.computerString = appData.firstComputerString;
\r
626 second.computerString = appData.secondComputerString;
\r
627 first.useSigint = second.useSigint = TRUE;
\r
628 first.useSigterm = second.useSigterm = TRUE;
\r
629 first.reuse = appData.reuseFirst;
\r
630 second.reuse = appData.reuseSecond;
\r
631 first.useSetboard = second.useSetboard = FALSE;
\r
632 first.useSAN = second.useSAN = FALSE;
\r
633 first.usePing = second.usePing = FALSE;
\r
634 first.lastPing = second.lastPing = 0;
\r
635 first.lastPong = second.lastPong = 0;
\r
636 first.usePlayother = second.usePlayother = FALSE;
\r
637 first.useColors = second.useColors = TRUE;
\r
638 first.useUsermove = second.useUsermove = FALSE;
\r
639 first.sendICS = second.sendICS = FALSE;
\r
640 first.sendName = second.sendName = appData.icsActive;
\r
641 first.sdKludge = second.sdKludge = FALSE;
\r
642 first.stKludge = second.stKludge = FALSE;
\r
643 TidyProgramName(first.program, first.host, first.tidy);
\r
644 TidyProgramName(second.program, second.host, second.tidy);
\r
645 first.matchWins = second.matchWins = 0;
\r
646 strcpy(first.variants, appData.variant);
\r
647 strcpy(second.variants, appData.variant);
\r
648 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
649 first.analyzing = second.analyzing = FALSE;
\r
650 first.initDone = second.initDone = FALSE;
\r
652 /* New features added by Tord: */
\r
653 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
654 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
655 /* End of new features added by Tord. */
\r
657 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
658 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
659 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
660 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
661 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
662 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
664 if (appData.firstProtocolVersion > PROTOVER ||
\r
665 appData.firstProtocolVersion < 1) {
\r
667 sprintf(buf, "protocol version %d not supported",
\r
668 appData.firstProtocolVersion);
\r
669 DisplayFatalError(buf, 0, 2);
\r
671 first.protocolVersion = appData.firstProtocolVersion;
\r
674 if (appData.secondProtocolVersion > PROTOVER ||
\r
675 appData.secondProtocolVersion < 1) {
\r
677 sprintf(buf, "protocol version %d not supported",
\r
678 appData.secondProtocolVersion);
\r
679 DisplayFatalError(buf, 0, 2);
\r
681 second.protocolVersion = appData.secondProtocolVersion;
\r
684 if (appData.icsActive) {
\r
685 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
686 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
687 appData.clockMode = FALSE;
\r
688 first.sendTime = second.sendTime = 0;
\r
692 /* Override some settings from environment variables, for backward
\r
693 compatibility. Unfortunately it's not feasible to have the env
\r
694 vars just set defaults, at least in xboard. Ugh.
\r
696 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
701 if (appData.noChessProgram) {
\r
702 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
703 + strlen(PATCHLEVEL));
\r
704 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
708 while (*q != ' ' && *q != NULLCHAR) q++;
\r
710 while (p > first.program && *(p-1) != '/') p--;
\r
711 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
712 + strlen(PATCHLEVEL) + (q - p));
\r
713 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
714 strncat(programVersion, p, q - p);
\r
717 if (!appData.icsActive) {
\r
719 /* Check for variants that are supported only in ICS mode,
\r
720 or not at all. Some that are accepted here nevertheless
\r
721 have bugs; see comments below.
\r
723 VariantClass variant = StringToVariant(appData.variant);
\r
725 case VariantBughouse: /* need four players and two boards */
\r
726 case VariantKriegspiel: /* need to hide pieces and move details */
\r
727 /* case VariantFischeRandom: (Fabien: moved below) */
\r
728 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
729 DisplayFatalError(buf, 0, 2);
\r
732 case VariantUnknown:
\r
733 case VariantLoadable:
\r
745 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
746 DisplayFatalError(buf, 0, 2);
\r
749 case VariantNormal: /* definitely works! */
\r
750 case VariantWildCastle: /* pieces not automatically shuffled */
\r
751 case VariantNoCastle: /* pieces not automatically shuffled */
\r
752 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
\r
753 case VariantCrazyhouse: /* holdings not shown,
\r
754 offboard interposition not understood */
\r
755 case VariantLosers: /* should work except for win condition,
\r
756 and doesn't know captures are mandatory */
\r
757 case VariantSuicide: /* should work except for win condition,
\r
758 and doesn't know captures are mandatory */
\r
759 case VariantGiveaway: /* should work except for win condition,
\r
760 and doesn't know captures are mandatory */
\r
761 case VariantTwoKings: /* should work */
\r
762 case VariantAtomic: /* should work except for win condition */
\r
763 case Variant3Check: /* should work except for win condition */
\r
764 case VariantShatranj: /* might work if TestLegality is off */
\r
767 case VariantXiangqi:
\r
768 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
769 case VariantGothic:
\r
770 case VariantCapablanca:
\r
771 case VariantCourier:
\r
778 int NextIntegerFromString( char ** str, long * value )
\r
783 while( *s == ' ' || *s == '\t' ) {
\r
789 if( *s >= '0' && *s <= '9' ) {
\r
790 while( *s >= '0' && *s <= '9' ) {
\r
791 *value = *value * 10 + (*s - '0');
\r
803 int NextTimeControlFromString( char ** str, long * value )
\r
806 int result = NextIntegerFromString( str, &temp );
\r
808 if( result == 0 ) {
\r
809 *value = temp * 60; /* Minutes */
\r
810 if( **str == ':' ) {
\r
812 result = NextIntegerFromString( str, &temp );
\r
813 *value += temp; /* Seconds */
\r
820 int GetTimeControlForWhite()
\r
822 int result = timeControl;
\r
827 int GetTimeControlForBlack()
\r
829 int result = timeControl;
\r
831 if( timeControl_2 > 0 ) {
\r
832 result = timeControl_2;
\r
839 ParseTimeControl(tc, ti, mps)
\r
845 int matched, min, sec;
\r
847 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
848 if (matched == 1) {
\r
849 timeControl = min * 60 * 1000;
\r
850 } else if (matched == 2) {
\r
851 timeControl = (min * 60 + sec) * 1000;
\r
859 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
864 /* Parse second time control */
\r
867 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
875 timeControl_2 = tc2 * 1000;
\r
885 timeControl = tc1 * 1000;
\r
889 timeIncrement = ti * 1000; /* convert to ms */
\r
890 movesPerSession = 0;
\r
893 movesPerSession = mps;
\r
901 if (appData.debugMode) {
\r
902 fprintf(debugFP, "%s\n", programVersion);
\r
905 if (appData.matchGames > 0) {
\r
906 appData.matchMode = TRUE;
\r
907 } else if (appData.matchMode) {
\r
908 appData.matchGames = 1;
\r
910 Reset(TRUE, FALSE);
\r
911 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
914 /* kludge: allow timeout for initial "feature" commands */
\r
916 DisplayMessage("", "Starting chess program");
\r
917 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
922 InitBackEnd3 P((void))
\r
924 GameMode initialMode;
\r
928 InitChessProgram(&first);
\r
930 if (appData.icsActive) {
\r
933 if (*appData.icsCommPort != NULLCHAR) {
\r
934 sprintf(buf, "Could not open comm port %s",
\r
935 appData.icsCommPort);
\r
937 sprintf(buf, "Could not connect to host %s, port %s",
\r
938 appData.icsHost, appData.icsPort);
\r
940 DisplayFatalError(buf, err, 1);
\r
945 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
947 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
948 } else if (appData.noChessProgram) {
\r
954 if (*appData.cmailGameName != NULLCHAR) {
\r
956 OpenLoopback(&cmailPR);
\r
958 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
962 DisplayMessage("", "");
\r
963 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
964 initialMode = BeginningOfGame;
\r
965 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
966 initialMode = TwoMachinesPlay;
\r
967 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
968 initialMode = AnalyzeFile;
\r
969 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
970 initialMode = AnalyzeMode;
\r
971 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
972 initialMode = MachinePlaysWhite;
\r
973 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
974 initialMode = MachinePlaysBlack;
\r
975 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
976 initialMode = EditGame;
\r
977 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
978 initialMode = EditPosition;
\r
979 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
980 initialMode = Training;
\r
982 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
983 DisplayFatalError(buf, 0, 2);
\r
987 if (appData.matchMode) {
\r
988 /* Set up machine vs. machine match */
\r
989 if (appData.noChessProgram) {
\r
990 DisplayFatalError("Can't have a match with no chess programs",
\r
996 if (*appData.loadGameFile != NULLCHAR) {
\r
997 if (!LoadGameFromFile(appData.loadGameFile,
\r
998 appData.loadGameIndex,
\r
999 appData.loadGameFile, FALSE)) {
\r
1000 DisplayFatalError("Bad game file", 0, 1);
\r
1003 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1004 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1005 appData.loadPositionIndex,
\r
1006 appData.loadPositionFile)) {
\r
1007 DisplayFatalError("Bad position file", 0, 1);
\r
1011 TwoMachinesEvent();
\r
1012 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1013 /* Set up cmail mode */
\r
1014 ReloadCmailMsgEvent(TRUE);
\r
1016 /* Set up other modes */
\r
1017 if (initialMode == AnalyzeFile) {
\r
1018 if (*appData.loadGameFile == NULLCHAR) {
\r
1019 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1023 if (*appData.loadGameFile != NULLCHAR) {
\r
1024 (void) LoadGameFromFile(appData.loadGameFile,
\r
1025 appData.loadGameIndex,
\r
1026 appData.loadGameFile, TRUE);
\r
1027 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1028 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1029 appData.loadPositionIndex,
\r
1030 appData.loadPositionFile);
\r
1032 if (initialMode == AnalyzeMode) {
\r
1033 if (appData.noChessProgram) {
\r
1034 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1037 if (appData.icsActive) {
\r
1038 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1041 AnalyzeModeEvent();
\r
1042 } else if (initialMode == AnalyzeFile) {
\r
1043 ShowThinkingEvent(TRUE);
\r
1044 AnalyzeFileEvent();
\r
1045 AnalysisPeriodicEvent(1);
\r
1046 } else if (initialMode == MachinePlaysWhite) {
\r
1047 if (appData.noChessProgram) {
\r
1048 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1052 if (appData.icsActive) {
\r
1053 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1057 MachineWhiteEvent();
\r
1058 } else if (initialMode == MachinePlaysBlack) {
\r
1059 if (appData.noChessProgram) {
\r
1060 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1064 if (appData.icsActive) {
\r
1065 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1069 MachineBlackEvent();
\r
1070 } else if (initialMode == TwoMachinesPlay) {
\r
1071 if (appData.noChessProgram) {
\r
1072 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1076 if (appData.icsActive) {
\r
1077 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1081 TwoMachinesEvent();
\r
1082 } else if (initialMode == EditGame) {
\r
1084 } else if (initialMode == EditPosition) {
\r
1085 EditPositionEvent();
\r
1086 } else if (initialMode == Training) {
\r
1087 if (*appData.loadGameFile == NULLCHAR) {
\r
1088 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1097 * Establish will establish a contact to a remote host.port.
\r
1098 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1099 * used to talk to the host.
\r
1100 * Returns 0 if okay, error code if not.
\r
1105 char buf[MSG_SIZ];
\r
1107 if (*appData.icsCommPort != NULLCHAR) {
\r
1108 /* Talk to the host through a serial comm port */
\r
1109 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1111 } else if (*appData.gateway != NULLCHAR) {
\r
1112 if (*appData.remoteShell == NULLCHAR) {
\r
1113 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1114 sprintf(buf, "%s %s %s",
\r
1115 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1116 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1119 /* Use the rsh program to run telnet program on a gateway host */
\r
1120 if (*appData.remoteUser == NULLCHAR) {
\r
1121 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1122 appData.gateway, appData.telnetProgram,
\r
1123 appData.icsHost, appData.icsPort);
\r
1125 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1126 appData.remoteShell, appData.gateway,
\r
1127 appData.remoteUser, appData.telnetProgram,
\r
1128 appData.icsHost, appData.icsPort);
\r
1130 return StartChildProcess(buf, "", &icsPR);
\r
1133 } else if (appData.useTelnet) {
\r
1134 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1137 /* TCP socket interface differs somewhat between
\r
1138 Unix and NT; handle details in the front end.
\r
1140 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1145 show_bytes(fp, buf, count)
\r
1151 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1152 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1161 /* Returns an errno value */
\r
1163 OutputMaybeTelnet(pr, message, count, outError)
\r
1169 char buf[8192], *p, *q, *buflim;
\r
1170 int left, newcount, outcount;
\r
1172 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1173 *appData.gateway != NULLCHAR) {
\r
1174 if (appData.debugMode) {
\r
1175 fprintf(debugFP, ">ICS: ");
\r
1176 show_bytes(debugFP, message, count);
\r
1177 fprintf(debugFP, "\n");
\r
1179 return OutputToProcess(pr, message, count, outError);
\r
1182 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1188 if (q >= buflim) {
\r
1189 if (appData.debugMode) {
\r
1190 fprintf(debugFP, ">ICS: ");
\r
1191 show_bytes(debugFP, buf, newcount);
\r
1192 fprintf(debugFP, "\n");
\r
1194 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1195 if (outcount < newcount) return -1; /* to be sure */
\r
1202 } else if (((unsigned char) *p) == TN_IAC) {
\r
1203 *q++ = (char) TN_IAC;
\r
1210 if (appData.debugMode) {
\r
1211 fprintf(debugFP, ">ICS: ");
\r
1212 show_bytes(debugFP, buf, newcount);
\r
1213 fprintf(debugFP, "\n");
\r
1215 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1216 if (outcount < newcount) return -1; /* to be sure */
\r
1221 read_from_player(isr, closure, message, count, error)
\r
1222 InputSourceRef isr;
\r
1228 int outError, outCount;
\r
1229 static int gotEof = 0;
\r
1231 /* Pass data read from player on to ICS */
\r
1234 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1235 if (outCount < count) {
\r
1236 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1238 } else if (count < 0) {
\r
1239 RemoveInputSource(isr);
\r
1240 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1241 } else if (gotEof++ > 0) {
\r
1242 RemoveInputSource(isr);
\r
1243 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1251 int count, outCount, outError;
\r
1253 if (icsPR == NULL) return;
\r
1255 count = strlen(s);
\r
1256 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1257 if (outCount < count) {
\r
1258 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1262 /* This is used for sending logon scripts to the ICS. Sending
\r
1263 without a delay causes problems when using timestamp on ICC
\r
1264 (at least on my machine). */
\r
1266 SendToICSDelayed(s,msdelay)
\r
1270 int count, outCount, outError;
\r
1272 if (icsPR == NULL) return;
\r
1274 count = strlen(s);
\r
1275 if (appData.debugMode) {
\r
1276 fprintf(debugFP, ">ICS: ");
\r
1277 show_bytes(debugFP, s, count);
\r
1278 fprintf(debugFP, "\n");
\r
1280 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1282 if (outCount < count) {
\r
1283 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1288 /* Remove all highlighting escape sequences in s
\r
1289 Also deletes any suffix starting with '('
\r
1292 StripHighlightAndTitle(s)
\r
1295 static char retbuf[MSG_SIZ];
\r
1298 while (*s != NULLCHAR) {
\r
1299 while (*s == '\033') {
\r
1300 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1301 if (*s != NULLCHAR) s++;
\r
1303 while (*s != NULLCHAR && *s != '\033') {
\r
1304 if (*s == '(' || *s == '[') {
\r
1315 /* Remove all highlighting escape sequences in s */
\r
1320 static char retbuf[MSG_SIZ];
\r
1323 while (*s != NULLCHAR) {
\r
1324 while (*s == '\033') {
\r
1325 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1326 if (*s != NULLCHAR) s++;
\r
1328 while (*s != NULLCHAR && *s != '\033') {
\r
1336 char *variantNames[] = VARIANT_NAMES;
\r
1341 return variantNames[v];
\r
1345 /* Identify a variant from the strings the chess servers use or the
\r
1346 PGN Variant tag names we use. */
\r
1348 StringToVariant(e)
\r
1353 VariantClass v = VariantNormal;
\r
1354 int i, found = FALSE;
\r
1355 char buf[MSG_SIZ];
\r
1359 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1360 if (StrCaseStr(e, variantNames[i])) {
\r
1361 v = (VariantClass) i;
\r
1368 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1369 || StrCaseStr(e, "wild/fr")) {
\r
1370 v = VariantFischeRandom;
\r
1371 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1372 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1374 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1375 if (isdigit(*p)) {
\r
1381 case 0: /* FICS only, actually */
\r
1383 /* Castling legal even if K starts on d-file */
\r
1384 v = VariantWildCastle;
\r
1389 /* Castling illegal even if K & R happen to start in
\r
1390 normal positions. */
\r
1391 v = VariantNoCastle;
\r
1404 /* Castling legal iff K & R start in normal positions */
\r
1405 v = VariantNormal;
\r
1410 /* Special wilds for position setup; unclear what to do here */
\r
1411 v = VariantLoadable;
\r
1414 /* Bizarre ICC game */
\r
1415 v = VariantTwoKings;
\r
1418 v = VariantKriegspiel;
\r
1421 v = VariantLosers;
\r
1424 v = VariantFischeRandom;
\r
1427 v = VariantCrazyhouse;
\r
1430 v = VariantBughouse;
\r
1433 v = Variant3Check;
\r
1436 /* Not quite the same as FICS suicide! */
\r
1437 v = VariantGiveaway;
\r
1440 v = VariantAtomic;
\r
1443 v = VariantShatranj;
\r
1446 /* Temporary names for future ICC types. The name *will* change in
\r
1447 the next xboard/WinBoard release after ICC defines it. */
\r
1463 v = VariantXiangqi;
\r
1470 v = VariantCourier;
\r
1477 v = VariantGothic;
\r
1484 v = VariantCapablanca;
\r
1498 /* Found "wild" or "w" in the string but no number;
\r
1499 must assume it's normal chess. */
\r
1500 v = VariantNormal;
\r
1503 sprintf(buf, "Unknown wild type %d", wnum);
\r
1504 DisplayError(buf, 0);
\r
1505 v = VariantUnknown;
\r
1510 if (appData.debugMode) {
\r
1511 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1512 e, wnum, VariantName(v));
\r
1517 static int leftover_start = 0, leftover_len = 0;
\r
1518 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1520 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1521 advance *index beyond it, and set leftover_start to the new value of
\r
1522 *index; else return FALSE. If pattern contains the character '*', it
\r
1523 matches any sequence of characters not containing '\r', '\n', or the
\r
1524 character following the '*' (if any), and the matched sequence(s) are
\r
1525 copied into star_match.
\r
1528 looking_at(buf, index, pattern)
\r
1533 char *bufp = &buf[*index], *patternp = pattern;
\r
1534 int star_count = 0;
\r
1535 char *matchp = star_match[0];
\r
1538 if (*patternp == NULLCHAR) {
\r
1539 *index = leftover_start = bufp - buf;
\r
1540 *matchp = NULLCHAR;
\r
1543 if (*bufp == NULLCHAR) return FALSE;
\r
1544 if (*patternp == '*') {
\r
1545 if (*bufp == *(patternp + 1)) {
\r
1546 *matchp = NULLCHAR;
\r
1547 matchp = star_match[++star_count];
\r
1551 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1553 if (*patternp == NULLCHAR)
\r
1558 *matchp++ = *bufp++;
\r
1562 if (*patternp != *bufp) return FALSE;
\r
1569 SendToPlayer(data, length)
\r
1573 int error, outCount;
\r
1574 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1575 if (outCount < length) {
\r
1576 DisplayFatalError("Error writing to display", error, 1);
\r
1581 PackHolding(packed, holding)
\r
1585 char *p = holding;
\r
1587 int runlength = 0;
\r
1593 switch (runlength) {
\r
1604 sprintf(q, "%d", runlength);
\r
1616 /* Telnet protocol requests from the front end */
\r
1618 TelnetRequest(ddww, option)
\r
1619 unsigned char ddww, option;
\r
1621 unsigned char msg[3];
\r
1622 int outCount, outError;
\r
1624 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1626 if (appData.debugMode) {
\r
1627 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1643 sprintf(buf1, "%d", ddww);
\r
1648 optionStr = "ECHO";
\r
1652 sprintf(buf2, "%d", option);
\r
1655 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1660 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1661 if (outCount < 3) {
\r
1662 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1669 if (!appData.icsActive) return;
\r
1670 TelnetRequest(TN_DO, TN_ECHO);
\r
1676 if (!appData.icsActive) return;
\r
1677 TelnetRequest(TN_DONT, TN_ECHO);
\r
1680 static int loggedOn = FALSE;
\r
1682 /*-- Game start info cache: --*/
\r
1684 char gs_kind[MSG_SIZ];
\r
1685 static char player1Name[128] = "";
\r
1686 static char player2Name[128] = "";
\r
1687 static int player1Rating = -1;
\r
1688 static int player2Rating = -1;
\r
1689 /*----------------------------*/
\r
1691 ColorClass curColor = ColorNormal;
\r
1694 read_from_ics(isr, closure, data, count, error)
\r
1695 InputSourceRef isr;
\r
1701 #define BUF_SIZE 8192
\r
1702 #define STARTED_NONE 0
\r
1703 #define STARTED_MOVES 1
\r
1704 #define STARTED_BOARD 2
\r
1705 #define STARTED_OBSERVE 3
\r
1706 #define STARTED_HOLDINGS 4
\r
1707 #define STARTED_CHATTER 5
\r
1708 #define STARTED_COMMENT 6
\r
1709 #define STARTED_MOVES_NOHIDE 7
\r
1711 static int started = STARTED_NONE;
\r
1712 static char parse[20000];
\r
1713 static int parse_pos = 0;
\r
1714 static char buf[BUF_SIZE + 1];
\r
1715 static int firstTime = TRUE, intfSet = FALSE;
\r
1716 static ColorClass prevColor = ColorNormal;
\r
1717 static int savingComment = FALSE;
\r
1726 if (appData.debugMode) {
\r
1728 fprintf(debugFP, "<ICS: ");
\r
1729 show_bytes(debugFP, data, count);
\r
1730 fprintf(debugFP, "\n");
\r
1736 /* If last read ended with a partial line that we couldn't parse,
\r
1737 prepend it to the new read and try again. */
\r
1738 if (leftover_len > 0) {
\r
1739 for (i=0; i<leftover_len; i++)
\r
1740 buf[i] = buf[leftover_start + i];
\r
1743 /* Copy in new characters, removing nulls and \r's */
\r
1744 buf_len = leftover_len;
\r
1745 for (i = 0; i < count; i++) {
\r
1746 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1747 buf[buf_len++] = data[i];
\r
1750 buf[buf_len] = NULLCHAR;
\r
1751 next_out = leftover_len;
\r
1752 leftover_start = 0;
\r
1755 while (i < buf_len) {
\r
1756 /* Deal with part of the TELNET option negotiation
\r
1757 protocol. We refuse to do anything beyond the
\r
1758 defaults, except that we allow the WILL ECHO option,
\r
1759 which ICS uses to turn off password echoing when we are
\r
1760 directly connected to it. We reject this option
\r
1761 if localLineEditing mode is on (always on in xboard)
\r
1762 and we are talking to port 23, which might be a real
\r
1763 telnet server that will try to keep WILL ECHO on permanently.
\r
1765 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1766 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1767 unsigned char option;
\r
1769 switch ((unsigned char) buf[++i]) {
\r
1771 if (appData.debugMode)
\r
1772 fprintf(debugFP, "\n<WILL ");
\r
1773 switch (option = (unsigned char) buf[++i]) {
\r
1775 if (appData.debugMode)
\r
1776 fprintf(debugFP, "ECHO ");
\r
1777 /* Reply only if this is a change, according
\r
1778 to the protocol rules. */
\r
1779 if (remoteEchoOption) break;
\r
1780 if (appData.localLineEditing &&
\r
1781 atoi(appData.icsPort) == TN_PORT) {
\r
1782 TelnetRequest(TN_DONT, TN_ECHO);
\r
1785 TelnetRequest(TN_DO, TN_ECHO);
\r
1786 remoteEchoOption = TRUE;
\r
1790 if (appData.debugMode)
\r
1791 fprintf(debugFP, "%d ", option);
\r
1792 /* Whatever this is, we don't want it. */
\r
1793 TelnetRequest(TN_DONT, option);
\r
1798 if (appData.debugMode)
\r
1799 fprintf(debugFP, "\n<WONT ");
\r
1800 switch (option = (unsigned char) buf[++i]) {
\r
1802 if (appData.debugMode)
\r
1803 fprintf(debugFP, "ECHO ");
\r
1804 /* Reply only if this is a change, according
\r
1805 to the protocol rules. */
\r
1806 if (!remoteEchoOption) break;
\r
1808 TelnetRequest(TN_DONT, TN_ECHO);
\r
1809 remoteEchoOption = FALSE;
\r
1812 if (appData.debugMode)
\r
1813 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1814 /* Whatever this is, it must already be turned
\r
1815 off, because we never agree to turn on
\r
1816 anything non-default, so according to the
\r
1817 protocol rules, we don't reply. */
\r
1822 if (appData.debugMode)
\r
1823 fprintf(debugFP, "\n<DO ");
\r
1824 switch (option = (unsigned char) buf[++i]) {
\r
1826 /* Whatever this is, we refuse to do it. */
\r
1827 if (appData.debugMode)
\r
1828 fprintf(debugFP, "%d ", option);
\r
1829 TelnetRequest(TN_WONT, option);
\r
1834 if (appData.debugMode)
\r
1835 fprintf(debugFP, "\n<DONT ");
\r
1836 switch (option = (unsigned char) buf[++i]) {
\r
1838 if (appData.debugMode)
\r
1839 fprintf(debugFP, "%d ", option);
\r
1840 /* Whatever this is, we are already not doing
\r
1841 it, because we never agree to do anything
\r
1842 non-default, so according to the protocol
\r
1843 rules, we don't reply. */
\r
1848 if (appData.debugMode)
\r
1849 fprintf(debugFP, "\n<IAC ");
\r
1850 /* Doubled IAC; pass it through */
\r
1854 if (appData.debugMode)
\r
1855 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1856 /* Drop all other telnet commands on the floor */
\r
1859 if (oldi > next_out)
\r
1860 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1861 if (++i > next_out)
\r
1866 /* OK, this at least will *usually* work */
\r
1867 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1871 if (loggedOn && !intfSet) {
\r
1872 if (ics_type == ICS_ICC) {
\r
1874 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1877 } else if (ics_type == ICS_CHESSNET) {
\r
1878 sprintf(str, "/style 12\n");
\r
1880 strcpy(str, "alias $ @\n$set interface ");
\r
1881 strcat(str, programVersion);
\r
1882 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1884 strcat(str, "$iset nohighlight 1\n");
\r
1886 strcat(str, "$iset lock 1\n$style 12\n");
\r
1892 if (started == STARTED_COMMENT) {
\r
1893 /* Accumulate characters in comment */
\r
1894 parse[parse_pos++] = buf[i];
\r
1895 if (buf[i] == '\n') {
\r
1896 parse[parse_pos] = NULLCHAR;
\r
1897 AppendComment(forwardMostMove, StripHighlight(parse));
\r
1898 started = STARTED_NONE;
\r
1900 /* Don't match patterns against characters in chatter */
\r
1905 if (started == STARTED_CHATTER) {
\r
1906 if (buf[i] != '\n') {
\r
1907 /* Don't match patterns against characters in chatter */
\r
1911 started = STARTED_NONE;
\r
1914 /* Kludge to deal with rcmd protocol */
\r
1915 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
1916 DisplayFatalError(&buf[1], 0, 1);
\r
1919 firstTime = FALSE;
\r
1922 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
1923 ics_type = ICS_ICC;
\r
1925 if (appData.debugMode)
\r
1926 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1929 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
1930 ics_type = ICS_FICS;
\r
1932 if (appData.debugMode)
\r
1933 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1936 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
1937 ics_type = ICS_CHESSNET;
\r
1939 if (appData.debugMode)
\r
1940 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1945 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
1946 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
1947 looking_at(buf, &i, "will be \"*\""))) {
\r
1948 strcpy(ics_handle, star_match[0]);
\r
1952 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
1953 char buf[MSG_SIZ];
\r
1954 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
1955 DisplayIcsInteractionTitle(buf);
\r
1956 have_set_title = TRUE;
\r
1959 /* skip finger notes */
\r
1960 if (started == STARTED_NONE &&
\r
1961 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
1962 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
1963 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
1964 started = STARTED_CHATTER;
\r
1969 /* skip formula vars */
\r
1970 if (started == STARTED_NONE &&
\r
1971 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
1972 started = STARTED_CHATTER;
\r
1978 if (appData.zippyTalk || appData.zippyPlay) {
\r
1980 if (ZippyControl(buf, &i) ||
\r
1981 ZippyConverse(buf, &i) ||
\r
1982 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
1988 if (/* Don't color "message" or "messages" output */
\r
1989 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
1990 looking_at(buf, &i, "*. * at *:*: ") ||
\r
1991 looking_at(buf, &i, "--* (*:*): ") ||
\r
1992 /* Regular tells and says */
\r
1993 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
1994 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
1995 looking_at(buf, &i, "* says: ") ||
\r
1996 /* Message notifications (same color as tells) */
\r
1997 looking_at(buf, &i, "* has left a message ") ||
\r
1998 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
1999 /* Whispers and kibitzes */
\r
2000 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2001 looking_at(buf, &i, "* kibitzes: ") ||
\r
2002 /* Channel tells */
\r
2003 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2005 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2006 /* Avoid "tells you:" spoofs in channels */
\r
2009 if (star_match[0][0] == NULLCHAR ||
\r
2010 strchr(star_match[0], ' ') ||
\r
2011 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2012 /* Reject bogus matches */
\r
2015 if (appData.colorize) {
\r
2016 if (oldi > next_out) {
\r
2017 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2022 Colorize(ColorTell, FALSE);
\r
2023 curColor = ColorTell;
\r
2026 Colorize(ColorKibitz, FALSE);
\r
2027 curColor = ColorKibitz;
\r
2030 p = strrchr(star_match[1], '(');
\r
2032 p = star_match[1];
\r
2036 if (atoi(p) == 1) {
\r
2037 Colorize(ColorChannel1, FALSE);
\r
2038 curColor = ColorChannel1;
\r
2040 Colorize(ColorChannel, FALSE);
\r
2041 curColor = ColorChannel;
\r
2045 curColor = ColorNormal;
\r
2049 if (started == STARTED_NONE && appData.autoComment &&
\r
2050 (gameMode == IcsObserving ||
\r
2051 gameMode == IcsPlayingWhite ||
\r
2052 gameMode == IcsPlayingBlack)) {
\r
2053 parse_pos = i - oldi;
\r
2054 memcpy(parse, &buf[oldi], parse_pos);
\r
2055 parse[parse_pos] = NULLCHAR;
\r
2056 started = STARTED_COMMENT;
\r
2057 savingComment = TRUE;
\r
2059 started = STARTED_CHATTER;
\r
2060 savingComment = FALSE;
\r
2067 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2068 looking_at(buf, &i, "* c-shouts: ")) {
\r
2069 if (appData.colorize) {
\r
2070 if (oldi > next_out) {
\r
2071 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2074 Colorize(ColorSShout, FALSE);
\r
2075 curColor = ColorSShout;
\r
2078 started = STARTED_CHATTER;
\r
2082 if (looking_at(buf, &i, "--->")) {
\r
2087 if (looking_at(buf, &i, "* shouts: ") ||
\r
2088 looking_at(buf, &i, "--> ")) {
\r
2089 if (appData.colorize) {
\r
2090 if (oldi > next_out) {
\r
2091 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2094 Colorize(ColorShout, FALSE);
\r
2095 curColor = ColorShout;
\r
2098 started = STARTED_CHATTER;
\r
2102 if (looking_at( buf, &i, "Challenge:")) {
\r
2103 if (appData.colorize) {
\r
2104 if (oldi > next_out) {
\r
2105 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2108 Colorize(ColorChallenge, FALSE);
\r
2109 curColor = ColorChallenge;
\r
2115 if (looking_at(buf, &i, "* offers you") ||
\r
2116 looking_at(buf, &i, "* offers to be") ||
\r
2117 looking_at(buf, &i, "* would like to") ||
\r
2118 looking_at(buf, &i, "* requests to") ||
\r
2119 looking_at(buf, &i, "Your opponent offers") ||
\r
2120 looking_at(buf, &i, "Your opponent requests")) {
\r
2122 if (appData.colorize) {
\r
2123 if (oldi > next_out) {
\r
2124 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2127 Colorize(ColorRequest, FALSE);
\r
2128 curColor = ColorRequest;
\r
2133 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2134 if (appData.colorize) {
\r
2135 if (oldi > next_out) {
\r
2136 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2139 Colorize(ColorSeek, FALSE);
\r
2140 curColor = ColorSeek;
\r
2146 if (looking_at(buf, &i, "\\ ")) {
\r
2147 if (prevColor != ColorNormal) {
\r
2148 if (oldi > next_out) {
\r
2149 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2152 Colorize(prevColor, TRUE);
\r
2153 curColor = prevColor;
\r
2155 if (savingComment) {
\r
2156 parse_pos = i - oldi;
\r
2157 memcpy(parse, &buf[oldi], parse_pos);
\r
2158 parse[parse_pos] = NULLCHAR;
\r
2159 started = STARTED_COMMENT;
\r
2161 started = STARTED_CHATTER;
\r
2166 if (looking_at(buf, &i, "Black Strength :") ||
\r
2167 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2168 looking_at(buf, &i, "<10>") ||
\r
2169 looking_at(buf, &i, "#@#")) {
\r
2170 /* Wrong board style */
\r
2172 SendToICS(ics_prefix);
\r
2173 SendToICS("set style 12\n");
\r
2174 SendToICS(ics_prefix);
\r
2175 SendToICS("refresh\n");
\r
2179 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2181 have_sent_ICS_logon = 1;
\r
2185 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2186 (looking_at(buf, &i, "\n<12> ") ||
\r
2187 looking_at(buf, &i, "<12> "))) {
\r
2189 if (oldi > next_out) {
\r
2190 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2193 started = STARTED_BOARD;
\r
2198 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2199 looking_at(buf, &i, "<b1> ")) {
\r
2200 if (oldi > next_out) {
\r
2201 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2204 started = STARTED_HOLDINGS;
\r
2209 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2211 /* Header for a move list -- first line */
\r
2213 switch (ics_getting_history) {
\r
2215 switch (gameMode) {
\r
2217 case BeginningOfGame:
\r
2218 /* User typed "moves" or "oldmoves" while we
\r
2219 were idle. Pretend we asked for these
\r
2220 moves and soak them up so user can step
\r
2221 through them and/or save them.
\r
2223 Reset(FALSE, TRUE);
\r
2224 gameMode = IcsObserving;
\r
2227 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2229 case EditGame: /*?*/
\r
2230 case EditPosition: /*?*/
\r
2231 /* Should above feature work in these modes too? */
\r
2232 /* For now it doesn't */
\r
2233 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2236 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2241 /* Is this the right one? */
\r
2242 if (gameInfo.white && gameInfo.black &&
\r
2243 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2244 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2246 ics_getting_history = H_GOT_REQ_HEADER;
\r
2249 case H_GOT_REQ_HEADER:
\r
2250 case H_GOT_UNREQ_HEADER:
\r
2251 case H_GOT_UNWANTED_HEADER:
\r
2252 case H_GETTING_MOVES:
\r
2253 /* Should not happen */
\r
2254 DisplayError("Error gathering move list: two headers", 0);
\r
2255 ics_getting_history = H_FALSE;
\r
2259 /* Save player ratings into gameInfo if needed */
\r
2260 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2261 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2262 (gameInfo.whiteRating == -1 ||
\r
2263 gameInfo.blackRating == -1)) {
\r
2265 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2266 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2267 if (appData.debugMode)
\r
2268 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2269 gameInfo.whiteRating, gameInfo.blackRating);
\r
2274 if (looking_at(buf, &i,
\r
2275 "* * match, initial time: * minute*, increment: * second")) {
\r
2276 /* Header for a move list -- second line */
\r
2277 /* Initial board will follow if this is a wild game */
\r
2279 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2280 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2281 gameInfo.event = StrSave(str);
\r
2282 gameInfo.variant = StringToVariant(gameInfo.event);
\r
2286 if (looking_at(buf, &i, "Move ")) {
\r
2287 /* Beginning of a move list */
\r
2288 switch (ics_getting_history) {
\r
2290 /* Normally should not happen */
\r
2291 /* Maybe user hit reset while we were parsing */
\r
2294 /* Happens if we are ignoring a move list that is not
\r
2295 * the one we just requested. Common if the user
\r
2296 * tries to observe two games without turning off
\r
2299 case H_GETTING_MOVES:
\r
2300 /* Should not happen */
\r
2301 DisplayError("Error gathering move list: nested", 0);
\r
2302 ics_getting_history = H_FALSE;
\r
2304 case H_GOT_REQ_HEADER:
\r
2305 ics_getting_history = H_GETTING_MOVES;
\r
2306 started = STARTED_MOVES;
\r
2308 if (oldi > next_out) {
\r
2309 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2312 case H_GOT_UNREQ_HEADER:
\r
2313 ics_getting_history = H_GETTING_MOVES;
\r
2314 started = STARTED_MOVES_NOHIDE;
\r
2317 case H_GOT_UNWANTED_HEADER:
\r
2318 ics_getting_history = H_FALSE;
\r
2324 if (looking_at(buf, &i, "% ") ||
\r
2325 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2326 && looking_at(buf, &i, "}*"))) {
\r
2327 savingComment = FALSE;
\r
2328 switch (started) {
\r
2329 case STARTED_MOVES:
\r
2330 case STARTED_MOVES_NOHIDE:
\r
2331 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2332 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2333 ParseGameHistory(parse);
\r
2335 if (appData.zippyPlay && first.initDone) {
\r
2336 FeedMovesToProgram(&first, forwardMostMove);
\r
2337 if (gameMode == IcsPlayingWhite) {
\r
2338 if (WhiteOnMove(forwardMostMove)) {
\r
2339 if (first.sendTime) {
\r
2340 if (first.useColors) {
\r
2341 SendToProgram("black\n", &first);
\r
2343 SendTimeRemaining(&first, TRUE);
\r
2345 if (first.useColors) {
\r
2346 SendToProgram("white\ngo\n", &first);
\r
2348 SendToProgram("go\n", &first);
\r
2350 first.maybeThinking = TRUE;
\r
2352 if (first.usePlayother) {
\r
2353 if (first.sendTime) {
\r
2354 SendTimeRemaining(&first, TRUE);
\r
2356 SendToProgram("playother\n", &first);
\r
2357 firstMove = FALSE;
\r
2362 } else if (gameMode == IcsPlayingBlack) {
\r
2363 if (!WhiteOnMove(forwardMostMove)) {
\r
2364 if (first.sendTime) {
\r
2365 if (first.useColors) {
\r
2366 SendToProgram("white\n", &first);
\r
2368 SendTimeRemaining(&first, FALSE);
\r
2370 if (first.useColors) {
\r
2371 SendToProgram("black\ngo\n", &first);
\r
2373 SendToProgram("go\n", &first);
\r
2375 first.maybeThinking = TRUE;
\r
2377 if (first.usePlayother) {
\r
2378 if (first.sendTime) {
\r
2379 SendTimeRemaining(&first, FALSE);
\r
2381 SendToProgram("playother\n", &first);
\r
2382 firstMove = FALSE;
\r
2390 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2391 /* Moves came from oldmoves or moves command
\r
2392 while we weren't doing anything else.
\r
2394 currentMove = forwardMostMove;
\r
2395 ClearHighlights();/*!!could figure this out*/
\r
2396 flipView = appData.flipView;
\r
2397 DrawPosition(FALSE, boards[currentMove]);
\r
2398 DisplayBothClocks();
\r
2399 sprintf(str, "%s vs. %s",
\r
2400 gameInfo.white, gameInfo.black);
\r
2401 DisplayTitle(str);
\r
2402 gameMode = IcsIdle;
\r
2404 /* Moves were history of an active game */
\r
2405 if (gameInfo.resultDetails != NULL) {
\r
2406 free(gameInfo.resultDetails);
\r
2407 gameInfo.resultDetails = NULL;
\r
2410 HistorySet(parseList, backwardMostMove,
\r
2411 forwardMostMove, currentMove-1);
\r
2412 DisplayMove(currentMove - 1);
\r
2413 if (started == STARTED_MOVES) next_out = i;
\r
2414 started = STARTED_NONE;
\r
2415 ics_getting_history = H_FALSE;
\r
2418 case STARTED_OBSERVE:
\r
2419 started = STARTED_NONE;
\r
2420 SendToICS(ics_prefix);
\r
2421 SendToICS("refresh\n");
\r
2430 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2431 started == STARTED_HOLDINGS ||
\r
2432 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2433 /* Accumulate characters in move list or board */
\r
2434 parse[parse_pos++] = buf[i];
\r
2437 /* Start of game messages. Mostly we detect start of game
\r
2438 when the first board image arrives. On some versions
\r
2439 of the ICS, though, we need to do a "refresh" after starting
\r
2440 to observe in order to get the current board right away. */
\r
2441 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2442 started = STARTED_OBSERVE;
\r
2446 /* Handle auto-observe */
\r
2447 if (appData.autoObserve &&
\r
2448 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2449 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2451 /* Choose the player that was highlighted, if any. */
\r
2452 if (star_match[0][0] == '\033' ||
\r
2453 star_match[1][0] != '\033') {
\r
2454 player = star_match[0];
\r
2456 player = star_match[2];
\r
2458 sprintf(str, "%sobserve %s\n",
\r
2459 ics_prefix, StripHighlightAndTitle(player));
\r
2462 /* Save ratings from notify string */
\r
2463 strcpy(player1Name, star_match[0]);
\r
2464 player1Rating = string_to_rating(star_match[1]);
\r
2465 strcpy(player2Name, star_match[2]);
\r
2466 player2Rating = string_to_rating(star_match[3]);
\r
2468 if (appData.debugMode)
\r
2470 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2471 player1Name, player1Rating,
\r
2472 player2Name, player2Rating);
\r
2477 /* Deal with automatic examine mode after a game,
\r
2478 and with IcsObserving -> IcsExamining transition */
\r
2479 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2480 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2482 int gamenum = atoi(star_match[0]);
\r
2483 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2484 gamenum == ics_gamenum) {
\r
2485 /* We were already playing or observing this game;
\r
2486 no need to refetch history */
\r
2487 gameMode = IcsExamining;
\r
2489 pauseExamForwardMostMove = forwardMostMove;
\r
2490 } else if (currentMove < forwardMostMove) {
\r
2491 ForwardInner(forwardMostMove);
\r
2494 /* I don't think this case really can happen */
\r
2495 SendToICS(ics_prefix);
\r
2496 SendToICS("refresh\n");
\r
2501 /* Error messages */
\r
2502 if (ics_user_moved) {
\r
2503 if (looking_at(buf, &i, "Illegal move") ||
\r
2504 looking_at(buf, &i, "Not a legal move") ||
\r
2505 looking_at(buf, &i, "Your king is in check") ||
\r
2506 looking_at(buf, &i, "It isn't your turn") ||
\r
2507 looking_at(buf, &i, "It is not your move")) {
\r
2508 /* Illegal move */
\r
2509 ics_user_moved = 0;
\r
2510 if (forwardMostMove > backwardMostMove) {
\r
2511 currentMove = --forwardMostMove;
\r
2512 DisplayMove(currentMove - 1); /* before DMError */
\r
2513 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2514 DrawPosition(FALSE, boards[currentMove]);
\r
2516 DisplayBothClocks();
\r
2522 if (looking_at(buf, &i, "still have time") ||
\r
2523 looking_at(buf, &i, "not out of time") ||
\r
2524 looking_at(buf, &i, "either player is out of time") ||
\r
2525 looking_at(buf, &i, "has timeseal; checking")) {
\r
2526 /* We must have called his flag a little too soon */
\r
2527 whiteFlag = blackFlag = FALSE;
\r
2531 if (looking_at(buf, &i, "added * seconds to") ||
\r
2532 looking_at(buf, &i, "seconds were added to")) {
\r
2533 /* Update the clocks */
\r
2534 SendToICS(ics_prefix);
\r
2535 SendToICS("refresh\n");
\r
2539 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2540 ics_clock_paused = TRUE;
\r
2545 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2546 ics_clock_paused = FALSE;
\r
2551 /* Grab player ratings from the Creating: message.
\r
2552 Note we have to check for the special case when
\r
2553 the ICS inserts things like [white] or [black]. */
\r
2554 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2555 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2557 0 player 1 name (not necessarily white)
\r
2559 2 empty, white, or black (IGNORED)
\r
2560 3 player 2 name (not necessarily black)
\r
2563 The names/ratings are sorted out when the game
\r
2564 actually starts (below).
\r
2566 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2567 player1Rating = string_to_rating(star_match[1]);
\r
2568 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2569 player2Rating = string_to_rating(star_match[4]);
\r
2571 if (appData.debugMode)
\r
2573 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2574 player1Name, player1Rating,
\r
2575 player2Name, player2Rating);
\r
2580 /* Improved generic start/end-of-game messages */
\r
2581 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2582 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2583 /* If tkind == 0: */
\r
2584 /* star_match[0] is the game number */
\r
2585 /* [1] is the white player's name */
\r
2586 /* [2] is the black player's name */
\r
2587 /* For end-of-game: */
\r
2588 /* [3] is the reason for the game end */
\r
2589 /* [4] is a PGN end game-token, preceded by " " */
\r
2590 /* For start-of-game: */
\r
2591 /* [3] begins with "Creating" or "Continuing" */
\r
2592 /* [4] is " *" or empty (don't care). */
\r
2593 int gamenum = atoi(star_match[0]);
\r
2594 char *whitename, *blackname, *why, *endtoken;
\r
2595 ChessMove endtype = (ChessMove) 0;
\r
2598 whitename = star_match[1];
\r
2599 blackname = star_match[2];
\r
2600 why = star_match[3];
\r
2601 endtoken = star_match[4];
\r
2603 whitename = star_match[1];
\r
2604 blackname = star_match[3];
\r
2605 why = star_match[5];
\r
2606 endtoken = star_match[6];
\r
2609 /* Game start messages */
\r
2610 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2611 strncmp(why, "Continuing ", 11) == 0) {
\r
2612 gs_gamenum = gamenum;
\r
2613 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2615 if (appData.zippyPlay) {
\r
2616 ZippyGameStart(whitename, blackname);
\r
2622 /* Game end messages */
\r
2623 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2624 ics_gamenum != gamenum) {
\r
2627 while (endtoken[0] == ' ') endtoken++;
\r
2628 switch (endtoken[0]) {
\r
2631 endtype = GameUnfinished;
\r
2634 endtype = BlackWins;
\r
2637 if (endtoken[1] == '/')
\r
2638 endtype = GameIsDrawn;
\r
2640 endtype = WhiteWins;
\r
2643 GameEnds(endtype, why, GE_ICS);
\r
2645 if (appData.zippyPlay && first.initDone) {
\r
2646 ZippyGameEnd(endtype, why);
\r
2647 if (first.pr == NULL) {
\r
2648 /* Start the next process early so that we'll
\r
2649 be ready for the next challenge */
\r
2650 StartChessProgram(&first);
\r
2652 /* Send "new" early, in case this command takes
\r
2653 a long time to finish, so that we'll be ready
\r
2654 for the next challenge. */
\r
2655 Reset(TRUE, TRUE);
\r
2661 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2662 looking_at(buf, &i, "no longer observing game *") ||
\r
2663 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2664 if (gameMode == IcsObserving &&
\r
2665 atoi(star_match[0]) == ics_gamenum)
\r
2668 gameMode = IcsIdle;
\r
2670 ics_user_moved = FALSE;
\r
2675 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2676 if (gameMode == IcsExamining &&
\r
2677 atoi(star_match[0]) == ics_gamenum)
\r
2679 gameMode = IcsIdle;
\r
2681 ics_user_moved = FALSE;
\r
2686 /* Advance leftover_start past any newlines we find,
\r
2687 so only partial lines can get reparsed */
\r
2688 if (looking_at(buf, &i, "\n")) {
\r
2689 prevColor = curColor;
\r
2690 if (curColor != ColorNormal) {
\r
2691 if (oldi > next_out) {
\r
2692 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2695 Colorize(ColorNormal, FALSE);
\r
2696 curColor = ColorNormal;
\r
2698 if (started == STARTED_BOARD) {
\r
2699 started = STARTED_NONE;
\r
2700 parse[parse_pos] = NULLCHAR;
\r
2701 ParseBoard12(parse);
\r
2702 ics_user_moved = 0;
\r
2704 /* Send premove here */
\r
2705 if (appData.premove) {
\r
2706 char str[MSG_SIZ];
\r
2707 if (currentMove == 0 &&
\r
2708 gameMode == IcsPlayingWhite &&
\r
2709 appData.premoveWhite) {
\r
2710 sprintf(str, "%s%s\n", ics_prefix,
\r
2711 appData.premoveWhiteText);
\r
2712 if (appData.debugMode)
\r
2713 fprintf(debugFP, "Sending premove:\n");
\r
2715 } else if (currentMove == 1 &&
\r
2716 gameMode == IcsPlayingBlack &&
\r
2717 appData.premoveBlack) {
\r
2718 sprintf(str, "%s%s\n", ics_prefix,
\r
2719 appData.premoveBlackText);
\r
2720 if (appData.debugMode)
\r
2721 fprintf(debugFP, "Sending premove:\n");
\r
2723 } else if (gotPremove) {
\r
2725 ClearPremoveHighlights();
\r
2726 if (appData.debugMode)
\r
2727 fprintf(debugFP, "Sending premove:\n");
\r
2728 UserMoveEvent(premoveFromX, premoveFromY,
\r
2729 premoveToX, premoveToY,
\r
2730 premovePromoChar);
\r
2734 /* Usually suppress following prompt */
\r
2735 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2736 if (looking_at(buf, &i, "*% ")) {
\r
2737 savingComment = FALSE;
\r
2741 } else if (started == STARTED_HOLDINGS) {
\r
2743 char new_piece[MSG_SIZ];
\r
2744 started = STARTED_NONE;
\r
2745 parse[parse_pos] = NULLCHAR;
\r
2746 if (appData.debugMode)
\r
2747 fprintf(debugFP, "Parsing holdings: %s\n", parse);
\r
2748 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2749 gamenum == ics_gamenum) {
\r
2750 if (gameInfo.variant == VariantNormal) {
\r
2751 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
\r
2752 /* Get a move list just to see the header, which
\r
2753 will tell us whether this is really bug or zh */
\r
2754 if (ics_getting_history == H_FALSE) {
\r
2755 ics_getting_history = H_REQUESTED;
\r
2756 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2760 new_piece[0] = NULLCHAR;
\r
2761 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2762 &gamenum, white_holding, black_holding,
\r
2764 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2765 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2767 if (appData.zippyPlay && first.initDone) {
\r
2768 ZippyHoldings(white_holding, black_holding,
\r
2772 if (tinyLayout || smallLayout) {
\r
2773 char wh[16], bh[16];
\r
2774 PackHolding(wh, white_holding);
\r
2775 PackHolding(bh, black_holding);
\r
2776 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2777 gameInfo.white, gameInfo.black);
\r
2779 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2780 gameInfo.white, white_holding,
\r
2781 gameInfo.black, black_holding);
\r
2783 DrawPosition(FALSE, NULL);
\r
2784 DisplayTitle(str);
\r
2786 /* Suppress following prompt */
\r
2787 if (looking_at(buf, &i, "*% ")) {
\r
2788 savingComment = FALSE;
\r
2795 i++; /* skip unparsed character and loop back */
\r
2798 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2799 started != STARTED_HOLDINGS && i > next_out) {
\r
2800 SendToPlayer(&buf[next_out], i - next_out);
\r
2804 leftover_len = buf_len - leftover_start;
\r
2805 /* if buffer ends with something we couldn't parse,
\r
2806 reparse it after appending the next read */
\r
2808 } else if (count == 0) {
\r
2809 RemoveInputSource(isr);
\r
2810 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2812 DisplayFatalError("Error reading from ICS", error, 1);
\r
2817 /* Board style 12 looks like this:
\r
2819 <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
2821 * The "<12> " is stripped before it gets to this routine. The two
\r
2822 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2823 * some chess servers may not have them, or may have only the first.
\r
2824 * Additional trailing fields may be added in the future.
\r
2827 #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
2829 #define RELATION_OBSERVING_PLAYED 0
\r
2830 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2831 #define RELATION_PLAYING_MYMOVE 1
\r
2832 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2833 #define RELATION_EXAMINING 2
\r
2834 #define RELATION_ISOLATED_BOARD -3
\r
2835 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2838 ParseBoard12(string)
\r
2841 GameMode newGameMode;
\r
2842 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2843 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2844 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2845 char to_play, board_chars[72];
\r
2846 char move_str[500], str[500], elapsed_time[500];
\r
2847 char black[32], white[32];
\r
2849 int prevMove = currentMove;
\r
2851 ChessMove moveType;
\r
2852 int fromX, fromY, toX, toY;
\r
2855 fromX = fromY = toX = toY = -1;
\r
2859 if (appData.debugMode)
\r
2860 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2862 move_str[0] = NULLCHAR;
\r
2863 elapsed_time[0] = NULLCHAR;
\r
2864 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2865 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2866 &gamenum, white, black, &relation, &basetime, &increment,
\r
2867 &white_stren, &black_stren, &white_time, &black_time,
\r
2868 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2872 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2873 DisplayError(str, 0);
\r
2877 /* Convert the move number to internal form */
\r
2878 moveNum = (moveNum - 1) * 2;
\r
2879 if (to_play == 'B') moveNum++;
\r
2880 if (moveNum >= MAX_MOVES) {
\r
2881 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
2886 switch (relation) {
\r
2887 case RELATION_OBSERVING_PLAYED:
\r
2888 case RELATION_OBSERVING_STATIC:
\r
2889 if (gamenum == -1) {
\r
2890 /* Old ICC buglet */
\r
2891 relation = RELATION_OBSERVING_STATIC;
\r
2893 newGameMode = IcsObserving;
\r
2895 case RELATION_PLAYING_MYMOVE:
\r
2896 case RELATION_PLAYING_NOTMYMOVE:
\r
2898 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
2899 IcsPlayingWhite : IcsPlayingBlack;
\r
2901 case RELATION_EXAMINING:
\r
2902 newGameMode = IcsExamining;
\r
2904 case RELATION_ISOLATED_BOARD:
\r
2906 /* Just display this board. If user was doing something else,
\r
2907 we will forget about it until the next board comes. */
\r
2908 newGameMode = IcsIdle;
\r
2910 case RELATION_STARTING_POSITION:
\r
2911 newGameMode = gameMode;
\r
2915 /* Modify behavior for initial board display on move listing
\r
2918 switch (ics_getting_history) {
\r
2922 case H_GOT_REQ_HEADER:
\r
2923 case H_GOT_UNREQ_HEADER:
\r
2924 /* This is the initial position of the current game */
\r
2925 gamenum = ics_gamenum;
\r
2926 moveNum = 0; /* old ICS bug workaround */
\r
2927 if (to_play == 'B') {
\r
2928 startedFromSetupPosition = TRUE;
\r
2929 blackPlaysFirst = TRUE;
\r
2931 if (forwardMostMove == 0) forwardMostMove = 1;
\r
2932 if (backwardMostMove == 0) backwardMostMove = 1;
\r
2933 if (currentMove == 0) currentMove = 1;
\r
2935 newGameMode = gameMode;
\r
2936 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
2938 case H_GOT_UNWANTED_HEADER:
\r
2939 /* This is an initial board that we don't want */
\r
2941 case H_GETTING_MOVES:
\r
2942 /* Should not happen */
\r
2943 DisplayError("Error gathering move list: extra board", 0);
\r
2944 ics_getting_history = H_FALSE;
\r
2948 /* Take action if this is the first board of a new game, or of a
\r
2949 different game than is currently being displayed. */
\r
2950 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
2951 relation == RELATION_ISOLATED_BOARD) {
\r
2953 /* Forget the old game and get the history (if any) of the new one */
\r
2954 if (gameMode != BeginningOfGame) {
\r
2955 Reset(FALSE, TRUE);
\r
2958 if (appData.autoRaiseBoard) BoardToTop();
\r
2960 if (gamenum == -1) {
\r
2961 newGameMode = IcsIdle;
\r
2962 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
2963 appData.getMoveList) {
\r
2964 /* Need to get game history */
\r
2965 ics_getting_history = H_REQUESTED;
\r
2966 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2970 /* Initially flip the board to have black on the bottom if playing
\r
2971 black or if the ICS flip flag is set, but let the user change
\r
2972 it with the Flip View button. */
\r
2973 flipView = appData.autoFlipView ?
\r
2974 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
2977 /* Done with values from previous mode; copy in new ones */
\r
2978 gameMode = newGameMode;
\r
2980 ics_gamenum = gamenum;
\r
2981 if (gamenum == gs_gamenum) {
\r
2982 int klen = strlen(gs_kind);
\r
2983 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
2984 sprintf(str, "ICS %s", gs_kind);
\r
2985 gameInfo.event = StrSave(str);
\r
2987 gameInfo.event = StrSave("ICS game");
\r
2989 gameInfo.site = StrSave(appData.icsHost);
\r
2990 gameInfo.date = PGNDate();
\r
2991 gameInfo.round = StrSave("-");
\r
2992 gameInfo.white = StrSave(white);
\r
2993 gameInfo.black = StrSave(black);
\r
2994 timeControl = basetime * 60 * 1000;
\r
2995 timeControl_2 = 0;
\r
2996 timeIncrement = increment * 1000;
\r
2997 movesPerSession = 0;
\r
2998 gameInfo.timeControl = TimeControlTagValue();
\r
2999 gameInfo.variant = StringToVariant(gameInfo.event);
\r
3000 gameInfo.outOfBook = NULL;
\r
3002 /* Do we have the ratings? */
\r
3003 if (strcmp(player1Name, white) == 0 &&
\r
3004 strcmp(player2Name, black) == 0) {
\r
3005 if (appData.debugMode)
\r
3006 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3007 player1Rating, player2Rating);
\r
3008 gameInfo.whiteRating = player1Rating;
\r
3009 gameInfo.blackRating = player2Rating;
\r
3010 } else if (strcmp(player2Name, white) == 0 &&
\r
3011 strcmp(player1Name, black) == 0) {
\r
3012 if (appData.debugMode)
\r
3013 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3014 player2Rating, player1Rating);
\r
3015 gameInfo.whiteRating = player2Rating;
\r
3016 gameInfo.blackRating = player1Rating;
\r
3018 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3020 /* Silence shouts if requested */
\r
3021 if (appData.quietPlay &&
\r
3022 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3023 SendToICS(ics_prefix);
\r
3024 SendToICS("set shout 0\n");
\r
3028 /* Deal with midgame name changes */
\r
3030 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3031 if (gameInfo.white) free(gameInfo.white);
\r
3032 gameInfo.white = StrSave(white);
\r
3034 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3035 if (gameInfo.black) free(gameInfo.black);
\r
3036 gameInfo.black = StrSave(black);
\r
3040 /* Throw away game result if anything actually changes in examine mode */
\r
3041 if (gameMode == IcsExamining && !newGame) {
\r
3042 gameInfo.result = GameUnfinished;
\r
3043 if (gameInfo.resultDetails != NULL) {
\r
3044 free(gameInfo.resultDetails);
\r
3045 gameInfo.resultDetails = NULL;
\r
3049 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3050 in if they are in a different variation than we are. */
\r
3051 if (pauseExamInvalid) return;
\r
3052 if (pausing && gameMode == IcsExamining) {
\r
3053 if (moveNum <= pauseExamForwardMostMove) {
\r
3054 pauseExamInvalid = TRUE;
\r
3055 forwardMostMove = pauseExamForwardMostMove;
\r
3060 /* Parse the board */
\r
3061 for (k = 0; k < 8; k++)
\r
3062 for (j = 0; j < 8; j++)
\r
3063 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3064 CopyBoard(boards[moveNum], board);
\r
3065 if (moveNum == 0) {
\r
3066 startedFromSetupPosition =
\r
3067 !CompareBoards(board, initialPosition);
\r
3070 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3071 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3072 /* This was an initial position from a move list, not
\r
3073 the current position */
\r
3077 /* Update currentMove and known move number limits */
\r
3078 newMove = newGame || moveNum > forwardMostMove;
\r
3080 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3081 if (gameMode == IcsExamining && moveNum == 0) {
\r
3082 /* Workaround for ICS limitation: we are not told the wild
\r
3083 type when starting to examine a game. But if we ask for
\r
3084 the move list, the move list header will tell us */
\r
3085 ics_getting_history = H_REQUESTED;
\r
3086 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3089 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3090 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3091 forwardMostMove = moveNum;
\r
3092 if (!pausing || currentMove > forwardMostMove)
\r
3093 currentMove = forwardMostMove;
\r
3095 /* New part of history that is not contiguous with old part */
\r
3096 if (pausing && gameMode == IcsExamining) {
\r
3097 pauseExamInvalid = TRUE;
\r
3098 forwardMostMove = pauseExamForwardMostMove;
\r
3101 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3102 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3103 ics_getting_history = H_REQUESTED;
\r
3104 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3109 /* Update the clocks */
\r
3110 if (strchr(elapsed_time, '.')) {
\r
3111 /* Time is in ms */
\r
3112 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3113 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3115 /* Time is in seconds */
\r
3116 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3117 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3122 if (appData.zippyPlay && newGame &&
\r
3123 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3124 gameMode != IcsExamining)
\r
3125 ZippyFirstBoard(moveNum, basetime, increment);
\r
3128 /* Put the move on the move list, first converting
\r
3129 to canonical algebraic form. */
\r
3130 if (moveNum > 0) {
\r
3131 if (moveNum <= backwardMostMove) {
\r
3132 /* We don't know what the board looked like before
\r
3133 this move. Punt. */
\r
3134 strcpy(parseList[moveNum - 1], move_str);
\r
3135 strcat(parseList[moveNum - 1], " ");
\r
3136 strcat(parseList[moveNum - 1], elapsed_time);
\r
3137 moveList[moveNum - 1][0] = NULLCHAR;
\r
3138 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3139 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3140 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3141 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3142 fromY, fromX, toY, toX, promoChar,
\r
3143 parseList[moveNum-1]);
\r
3144 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3145 castlingRights[moveNum]) ) {
\r
3147 case MT_STALEMATE:
\r
3151 strcat(parseList[moveNum - 1], "+");
\r
3153 case MT_CHECKMATE:
\r
3154 strcat(parseList[moveNum - 1], "#");
\r
3157 strcat(parseList[moveNum - 1], " ");
\r
3158 strcat(parseList[moveNum - 1], elapsed_time);
\r
3159 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3160 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3161 strcat(moveList[moveNum - 1], "\n");
\r
3162 } else if (strcmp(move_str, "none") == 0) {
\r
3163 /* Again, we don't know what the board looked like;
\r
3164 this is really the start of the game. */
\r
3165 parseList[moveNum - 1][0] = NULLCHAR;
\r
3166 moveList[moveNum - 1][0] = NULLCHAR;
\r
3167 backwardMostMove = moveNum;
\r
3168 startedFromSetupPosition = TRUE;
\r
3169 fromX = fromY = toX = toY = -1;
\r
3171 /* Move from ICS was illegal!? Punt. */
\r
3173 if (appData.testLegality && appData.debugMode) {
\r
3174 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3175 DisplayError(str, 0);
\r
3178 strcpy(parseList[moveNum - 1], move_str);
\r
3179 strcat(parseList[moveNum - 1], " ");
\r
3180 strcat(parseList[moveNum - 1], elapsed_time);
\r
3181 moveList[moveNum - 1][0] = NULLCHAR;
\r
3182 fromX = fromY = toX = toY = -1;
\r
3186 /* Send move to chess program (BEFORE animating it). */
\r
3187 if (appData.zippyPlay && !newGame && newMove &&
\r
3188 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3190 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3191 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3192 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3193 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3195 DisplayError(str, 0);
\r
3197 if (first.sendTime) {
\r
3198 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3200 SendMoveToProgram(moveNum - 1, &first);
\r
3202 firstMove = FALSE;
\r
3203 if (first.useColors) {
\r
3204 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3206 "black\ngo\n", &first);
\r
3208 SendToProgram("go\n", &first);
\r
3210 first.maybeThinking = TRUE;
\r
3213 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3214 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3215 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3216 DisplayError(str, 0);
\r
3218 SendMoveToProgram(moveNum - 1, &first);
\r
3225 if (moveNum > 0 && !gotPremove) {
\r
3226 /* If move comes from a remote source, animate it. If it
\r
3227 isn't remote, it will have already been animated. */
\r
3228 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3229 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3231 if (!pausing && appData.highlightLastMove) {
\r
3232 SetHighlights(fromX, fromY, toX, toY);
\r
3236 /* Start the clocks */
\r
3237 whiteFlag = blackFlag = FALSE;
\r
3238 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3239 if (ticking == 0) {
\r
3240 ics_clock_paused = TRUE;
\r
3242 } else if (ticking == 1) {
\r
3243 ics_clock_paused = FALSE;
\r
3245 if (gameMode == IcsIdle ||
\r
3246 relation == RELATION_OBSERVING_STATIC ||
\r
3247 relation == RELATION_EXAMINING ||
\r
3249 DisplayBothClocks();
\r
3253 /* Display opponents and material strengths */
\r
3254 if (gameInfo.variant != VariantBughouse &&
\r
3255 gameInfo.variant != VariantCrazyhouse) {
\r
3256 if (tinyLayout || smallLayout) {
\r
3257 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3258 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3259 basetime, increment);
\r
3261 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3262 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3263 basetime, increment);
\r
3265 DisplayTitle(str);
\r
3269 /* Display the board */
\r
3272 if (appData.premove)
\r
3273 if (!gotPremove ||
\r
3274 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3275 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3276 ClearPremoveHighlights();
\r
3278 DrawPosition(FALSE, boards[currentMove]);
\r
3279 DisplayMove(moveNum - 1);
\r
3280 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3284 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3288 GetMoveListEvent()
\r
3290 char buf[MSG_SIZ];
\r
3291 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3292 ics_getting_history = H_REQUESTED;
\r
3293 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3299 AnalysisPeriodicEvent(force)
\r
3302 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3303 && !force) || !appData.periodicUpdates)
\r
3306 /* Send . command to Crafty to collect stats */
\r
3307 SendToProgram(".\n", &first);
\r
3309 /* Don't send another until we get a response (this makes
\r
3310 us stop sending to old Crafty's which don't understand
\r
3311 the "." command (sending illegal cmds resets node count & time,
\r
3312 which looks bad)) */
\r
3313 programStats.ok_to_send = 0;
\r
3317 SendMoveToProgram(moveNum, cps)
\r
3319 ChessProgramState *cps;
\r
3321 char buf[MSG_SIZ];
\r
3322 if (cps->useUsermove) {
\r
3323 SendToProgram("usermove ", cps);
\r
3325 if (cps->useSAN) {
\r
3327 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3328 int len = space - parseList[moveNum];
\r
3329 memcpy(buf, parseList[moveNum], len);
\r
3330 buf[len++] = '\n';
\r
3331 buf[len] = NULLCHAR;
\r
3333 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3335 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3336 if(BOARD_HEIGHT>8) {
\r
3338 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3340 SendToProgram(buf, cps);
\r
3342 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3343 * the engine. It would be nice to have a better way to identify castle
\r
3345 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3346 int fromX = moveList[moveNum][0] - 'a';
\r
3347 int fromY = moveList[moveNum][1] - ONE;
\r
3348 int toX = moveList[moveNum][2] - 'a';
\r
3349 int toY = moveList[moveNum][3] - ONE;
\r
3350 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3351 && boards[currentMove][toY][toX] == WhiteRook)
\r
3352 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3353 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3354 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3355 else SendToProgram("O-O-O\n", cps);
\r
3357 else SendToProgram(moveList[moveNum], cps);
\r
3359 else SendToProgram(moveList[moveNum], cps);
\r
3360 /* End of additions by Tord */
\r
3365 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3366 ChessMove moveType;
\r
3367 int fromX, fromY, toX, toY;
\r
3369 char user_move[MSG_SIZ];
\r
3371 switch (moveType) {
\r
3373 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3374 (int)moveType, fromX, fromY, toX, toY);
\r
3375 DisplayError(user_move + strlen("say "), 0);
\r
3377 case WhiteKingSideCastle:
\r
3378 case BlackKingSideCastle:
\r
3379 case WhiteQueenSideCastleWild:
\r
3380 case BlackQueenSideCastleWild:
\r
3382 case WhiteHSideCastleFR:
\r
3383 case BlackHSideCastleFR:
\r
3385 sprintf(user_move, "o-o\n");
\r
3387 case WhiteQueenSideCastle:
\r
3388 case BlackQueenSideCastle:
\r
3389 case WhiteKingSideCastleWild:
\r
3390 case BlackKingSideCastleWild:
\r
3392 case WhiteASideCastleFR:
\r
3393 case BlackASideCastleFR:
\r
3395 sprintf(user_move, "o-o-o\n");
\r
3397 case WhitePromotionQueen:
\r
3398 case BlackPromotionQueen:
\r
3399 case WhitePromotionRook:
\r
3400 case BlackPromotionRook:
\r
3401 case WhitePromotionBishop:
\r
3402 case BlackPromotionBishop:
\r
3403 case WhitePromotionKnight:
\r
3404 case BlackPromotionKnight:
\r
3405 case WhitePromotionKing:
\r
3406 case BlackPromotionKing:
\r
3408 case WhitePromotionChancellor:
\r
3409 case BlackPromotionChancellor:
\r
3410 case WhitePromotionArchbishop:
\r
3411 case BlackPromotionArchbishop:
\r
3413 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3414 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY,
\r
3415 PieceToChar(PromoPiece(moveType)));
\r
3419 sprintf(user_move, "%c@%c%c\n",
\r
3420 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3421 'a' + toX, ONE + toY);
\r
3424 case WhiteCapturesEnPassant:
\r
3425 case BlackCapturesEnPassant:
\r
3426 case IllegalMove: /* could be a variant we don't quite understand */
\r
3427 sprintf(user_move, "%c%c%c%c\n",
\r
3428 'a' + fromX, ONE + fromY, 'a' + toX, ONE + toY);
\r
3431 SendToICS(user_move);
\r
3435 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3436 int rf, ff, rt, ft;
\r
3440 if (rf == DROP_RANK) {
\r
3441 sprintf(move, "%c@%c%c\n",
\r
3442 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, ONE + rt);
\r
3444 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3445 sprintf(move, "%c%c%c%c\n",
\r
3446 'a' + ff, ONE + rf, 'a' + ft, ONE + rt);
\r
3448 sprintf(move, "%c%c%c%c%c\n",
\r
3449 'a' + ff, ONE + rf, 'a' + ft, ONE + rt, promoChar);
\r
3455 ProcessICSInitScript(f)
\r
3458 char buf[MSG_SIZ];
\r
3460 while (fgets(buf, MSG_SIZ, f)) {
\r
3461 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3468 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3470 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3473 ChessMove *moveType;
\r
3474 int *fromX, *fromY, *toX, *toY;
\r
3477 *moveType = yylexstr(moveNum, move);
\r
3479 switch (*moveType) {
\r
3481 case WhitePromotionChancellor:
\r
3482 case BlackPromotionChancellor:
\r
3483 case WhitePromotionArchbishop:
\r
3484 case BlackPromotionArchbishop:
\r
3486 case WhitePromotionQueen:
\r
3487 case BlackPromotionQueen:
\r
3488 case WhitePromotionRook:
\r
3489 case BlackPromotionRook:
\r
3490 case WhitePromotionBishop:
\r
3491 case BlackPromotionBishop:
\r
3492 case WhitePromotionKnight:
\r
3493 case BlackPromotionKnight:
\r
3494 case WhitePromotionKing:
\r
3495 case BlackPromotionKing:
\r
3497 case WhiteCapturesEnPassant:
\r
3498 case BlackCapturesEnPassant:
\r
3499 case WhiteKingSideCastle:
\r
3500 case WhiteQueenSideCastle:
\r
3501 case BlackKingSideCastle:
\r
3502 case BlackQueenSideCastle:
\r
3503 case WhiteKingSideCastleWild:
\r
3504 case WhiteQueenSideCastleWild:
\r
3505 case BlackKingSideCastleWild:
\r
3506 case BlackQueenSideCastleWild:
\r
3507 /* Code added by Tord: */
\r
3508 case WhiteHSideCastleFR:
\r
3509 case WhiteASideCastleFR:
\r
3510 case BlackHSideCastleFR:
\r
3511 case BlackASideCastleFR:
\r
3512 /* End of code added by Tord */
\r
3513 case IllegalMove: /* bug or odd chess variant */
\r
3514 *fromX = currentMoveString[0] - 'a';
\r
3515 *fromY = currentMoveString[1] - ONE;
\r
3516 *toX = currentMoveString[2] - 'a';
\r
3517 *toY = currentMoveString[3] - ONE;
\r
3518 *promoChar = currentMoveString[4];
\r
3519 if (*fromX < 0 || *fromX >= BOARD_WIDTH || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3520 *toX < 0 || *toX >= BOARD_WIDTH || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3521 *fromX = *fromY = *toX = *toY = 0;
\r
3524 if (appData.testLegality) {
\r
3525 return (*moveType != IllegalMove);
\r
3527 return !(fromX == fromY && toX == toY);
\r
3532 *fromX = *moveType == WhiteDrop ?
\r
3533 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3534 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3535 *fromY = DROP_RANK;
\r
3536 *toX = currentMoveString[2] - 'a';
\r
3537 *toY = currentMoveString[3] - ONE;
\r
3538 *promoChar = NULLCHAR;
\r
3541 case AmbiguousMove:
\r
3542 case ImpossibleMove:
\r
3543 case (ChessMove) 0: /* end of file */
\r
3553 *fromX = *fromY = *toX = *toY = 0;
\r
3554 *promoChar = NULLCHAR;
\r
3559 /* [AS] FRC game initialization */
\r
3560 static int FindEmptySquare( Board board, int n )
\r
3565 while( board[0][i] != EmptySquare ) i++;
\r
3575 static void ShuffleFRC( Board board )
\r
3581 for( i=0; i<8; i++ ) {
\r
3582 board[0][i] = EmptySquare;
\r
3585 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3586 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
3587 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
3588 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
3589 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
3590 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3591 board[0][FindEmptySquare(board, 0)] = WhiteKing;
\r
3592 board[0][FindEmptySquare(board, 0)] = WhiteRook;
\r
3594 for( i=0; i<BOARD_WIDTH; i++ ) {
\r
3595 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3599 static unsigned char FRC_KnightTable[10] = {
\r
3600 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
3603 static void SetupFRC( Board board, int pos_index )
\r
3606 unsigned char knights;
\r
3608 /* Bring the position index into a safe range (just in case...) */
\r
3609 if( pos_index < 0 ) pos_index = 0;
\r
3613 /* Clear the board */
\r
3614 for( i=0; i<8; i++ ) {
\r
3615 board[0][i] = EmptySquare;
\r
3618 /* Place bishops and queen */
\r
3619 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
3622 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
3625 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
3628 /* Place knigths */
\r
3629 knights = FRC_KnightTable[ pos_index ];
\r
3631 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
3632 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
3634 /* Place rooks and king */
\r
3635 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3636 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
\r
3637 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
\r
3639 /* Mirror piece placement for black */
\r
3640 for( i=0; i<BOARD_WIDTH; i++ ) {
\r
3641 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
3646 InitPosition(redraw)
\r
3649 ChessSquare (* pieces)[BOARD_SIZE];
\r
3652 currentMove = forwardMostMove = backwardMostMove = 0;
\r
3654 /* [AS] Initialize pv info list [HGM] and game status */
\r
3656 for( i=0; i<MAX_MOVES; i++ ) {
\r
3657 pvInfoList[i].depth = 0;
\r
3658 epStatus[i]=EP_NONE;
\r
3659 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
3662 /* [HGM] Build normal castling rights */
\r
3663 for( j=0; j<BOARD_SIZE; j++ ) initialRights[j] = -1;
\r
3664 nrCastlingRights = 6;
\r
3665 castlingRights[0][0] = initialRights[0] = BOARD_WIDTH-1;
\r
3666 castlingRights[0][1] = initialRights[1] = 0;
\r
3667 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
3668 castlingRights[0][3] = initialRights[3] = BOARD_WIDTH-1;
\r
3669 castlingRights[0][4] = initialRights[4] = 0;
\r
3670 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
3672 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
3673 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
3675 initialRulePlies = 0; /* 50-move counter start */
\r
3679 /* [HGM] logic here is completely changed. In stead of full positions */
\r
3680 /* the initialized data only consist of the two backranks. The switch */
\r
3681 /* selects which one we will use, which is than copied to the Board */
\r
3682 /* initialPosition, which for the rest is initialized by Pawns and */
\r
3683 /* empty squares. This initial position is then copied to boards[0], */
\r
3684 /* possibly after shuffling, so that it remains available. */
\r
3686 switch (gameInfo.variant) {
\r
3688 pieces = BOARD_WIDTH <= 8 ? FIDEArray :
\r
3689 BOARD_WIDTH <= 10 ? CapablancaArray : CourierArray;
\r
3691 case VariantShatranj:
\r
3692 pieces = ShatranjArray;
\r
3693 CharToPiece('E'); /* associate PGN/FEN letter with internal piece type */
\r
3695 nrCastlingRights = 0;
\r
3696 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3698 case VariantTwoKings:
\r
3699 pieces = twoKingsArray;
\r
3700 nrCastlingRights = 8; /* add rights for second King */
\r
3701 castlingRights[0][6] = initialRights[2] = 5;
\r
3702 castlingRights[0][7] = initialRights[5] = 5;
\r
3703 castlingRank[6] = 0;
\r
3704 castlingRank[6] = BOARD_HEIGHT-1;
\r
3705 startedFromSetupPosition = TRUE;
\r
3708 case VariantCapablanca:
\r
3709 pieces = CapablancaArray;
\r
3711 case VariantGothic:
\r
3712 pieces = GothicArray;
\r
3714 case VariantXiangqi:
\r
3715 pieces = XiangqiArray;
\r
3717 case VariantCourier:
\r
3718 pieces = CourierArray;
\r
3719 nrCastlingRights = 0;
\r
3720 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3722 case VariantFairy:
\r
3723 pieces = fairyArray;
\r
3724 startedFromSetupPosition = TRUE;
\r
3727 case VariantWildCastle:
\r
3728 pieces = FIDEArray;
\r
3729 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
3731 case VariantNoCastle:
\r
3732 pieces = FIDEArray;
\r
3733 nrCastlingRights = 0;
\r
3734 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
3735 /* !!?unconstrained back-rank shuffle */
\r
3739 for( j=0; j<BOARD_WIDTH; j++ ) {
\r
3740 for( i=0; i<BOARD_HEIGHT; i++ )
\r
3741 initialPosition[i][j] = EmptySquare;
\r
3742 initialPosition[0][j] = pieces[0][j];
\r
3743 if(gameInfo.variant == VariantXiangqi) {
\r
3745 initialPosition[3][j] =
\r
3746 initialPosition[BOARD_HEIGHT-4][j] = EmptySquare;
\r
3747 if(j==1 || j>=BOARD_WIDTH-2) {
\r
3748 initialPosition[2][j] = WhiteFairyMarshall;
\r
3749 initialPosition[BOARD_HEIGHT-3][j] = BlackFairyMarshall;
\r
3752 initialPosition[3][j] = WhitePawn;
\r
3753 initialPosition[BOARD_HEIGHT-4][j] = BlackPawn;
\r
3756 initialPosition[1][j] = WhitePawn;
\r
3757 initialPosition[BOARD_HEIGHT-2][j] = BlackPawn;
\r
3759 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j];
\r
3762 if(gameInfo.variant == VariantFischeRandom) {
\r
3763 if( appData.defaultFrcPosition < 0 ) {
\r
3764 ShuffleFRC( initialPosition );
\r
3767 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
3771 CopyBoard(boards[0], initialPosition);
\r
3774 DrawPosition(TRUE, boards[currentMove]);
\r
3778 SendBoard(cps, moveNum)
\r
3779 ChessProgramState *cps;
\r
3782 char message[MSG_SIZ];
\r
3784 if (cps->useSetboard) {
\r
3785 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
3786 sprintf(message, "setboard %s\n", fen);
\r
3787 SendToProgram(message, cps);
\r
3793 /* Kludge to set black to move, avoiding the troublesome and now
\r
3794 * deprecated "black" command.
\r
3796 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
3798 SendToProgram("edit\n", cps);
\r
3799 SendToProgram("#\n", cps);
\r
3800 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
3801 bp = &boards[moveNum][i][0];
\r
3802 for (j = 0; j < BOARD_WIDTH; j++, bp++) {
\r
3803 if ((int) *bp < (int) BlackPawn) {
\r
3804 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
3805 'a' + j, ONE + i);
\r
3806 SendToProgram(message, cps);
\r
3811 SendToProgram("c\n", cps);
\r
3812 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
3813 bp = &boards[moveNum][i][0];
\r
3814 for (j = 0; j < BOARD_WIDTH; j++, bp++) {
\r
3815 if (((int) *bp != (int) EmptySquare)
\r
3816 && ((int) *bp >= (int) BlackPawn)) {
\r
3817 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
3818 'a' + j, ONE + i);
\r
3819 SendToProgram(message, cps);
\r
3824 SendToProgram(".\n", cps);
\r
3829 IsPromotion(fromX, fromY, toX, toY)
\r
3830 int fromX, fromY, toX, toY;
\r
3832 return gameMode != EditPosition && gameInfo.variant != VariantXiangqi &&
\r
3833 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
\r
3834 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == BOARD_HEIGHT-1) ||
\r
3835 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
\r
3840 PieceForSquare (x, y)
\r
3844 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
3847 return boards[currentMove][y][x];
\r
3851 OKToStartUserMove(x, y)
\r
3854 ChessSquare from_piece;
\r
3857 if (matchMode) return FALSE;
\r
3858 if (gameMode == EditPosition) return TRUE;
\r
3860 if (x >= 0 && y >= 0)
\r
3861 from_piece = boards[currentMove][y][x];
\r
3863 from_piece = EmptySquare;
\r
3865 if (from_piece == EmptySquare) return FALSE;
\r
3867 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
3868 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
3870 switch (gameMode) {
\r
3871 case PlayFromGameFile:
\r
3873 case TwoMachinesPlay:
\r
3877 case IcsObserving:
\r
3881 case MachinePlaysWhite:
\r
3882 case IcsPlayingBlack:
\r
3883 if (appData.zippyPlay) return FALSE;
\r
3884 if (white_piece) {
\r
3885 DisplayMoveError("You are playing Black");
\r
3890 case MachinePlaysBlack:
\r
3891 case IcsPlayingWhite:
\r
3892 if (appData.zippyPlay) return FALSE;
\r
3893 if (!white_piece) {
\r
3894 DisplayMoveError("You are playing White");
\r
3900 if (!white_piece && WhiteOnMove(currentMove)) {
\r
3901 DisplayMoveError("It is White's turn");
\r
3904 if (white_piece && !WhiteOnMove(currentMove)) {
\r
3905 DisplayMoveError("It is Black's turn");
\r
3908 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
3909 /* Editing correspondence game history */
\r
3910 /* Could disallow this or prompt for confirmation */
\r
3911 cmailOldMove = -1;
\r
3913 if (currentMove < forwardMostMove) {
\r
3914 /* Discarding moves */
\r
3915 /* Could prompt for confirmation here,
\r
3916 but I don't think that's such a good idea */
\r
3917 forwardMostMove = currentMove;
\r
3921 case BeginningOfGame:
\r
3922 if (appData.icsActive) return FALSE;
\r
3923 if (!appData.noChessProgram) {
\r
3924 if (!white_piece) {
\r
3925 DisplayMoveError("You are playing White");
\r
3932 if (!white_piece && WhiteOnMove(currentMove)) {
\r
3933 DisplayMoveError("It is White's turn");
\r
3936 if (white_piece && !WhiteOnMove(currentMove)) {
\r
3937 DisplayMoveError("It is Black's turn");
\r
3943 case IcsExamining:
\r
3946 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
3947 && gameMode != AnalyzeFile && gameMode != Training) {
\r
3948 DisplayMoveError("Displayed position is not current");
\r
3954 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
3955 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
3956 int lastLoadGameUseList = FALSE;
\r
3957 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
3958 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
3962 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
3963 int fromX, fromY, toX, toY;
\r
3966 ChessMove moveType;
\r
3968 if (fromX < 0 || fromY < 0) return;
\r
3969 if ((fromX == toX) && (fromY == toY)) {
\r
3973 /* Check if the user is playing in turn. This is complicated because we
\r
3974 let the user "pick up" a piece before it is his turn. So the piece he
\r
3975 tried to pick up may have been captured by the time he puts it down!
\r
3976 Therefore we use the color the user is supposed to be playing in this
\r
3977 test, not the color of the piece that is currently on the starting
\r
3978 square---except in EditGame mode, where the user is playing both
\r
3979 sides; fortunately there the capture race can't happen. (It can
\r
3980 now happen in IcsExamining mode, but that's just too bad. The user
\r
3981 will get a somewhat confusing message in that case.)
\r
3984 switch (gameMode) {
\r
3985 case PlayFromGameFile:
\r
3987 case TwoMachinesPlay:
\r
3989 case IcsObserving:
\r
3991 /* We switched into a game mode where moves are not accepted,
\r
3992 perhaps while the mouse button was down. */
\r
3995 case MachinePlaysWhite:
\r
3996 /* User is moving for Black */
\r
3997 if (WhiteOnMove(currentMove)) {
\r
3998 DisplayMoveError("It is White's turn");
\r
4003 case MachinePlaysBlack:
\r
4004 /* User is moving for White */
\r
4005 if (!WhiteOnMove(currentMove)) {
\r
4006 DisplayMoveError("It is Black's turn");
\r
4012 case IcsExamining:
\r
4013 case BeginningOfGame:
\r
4016 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4017 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4018 /* User is moving for Black */
\r
4019 if (WhiteOnMove(currentMove)) {
\r
4020 DisplayMoveError("It is White's turn");
\r
4024 /* User is moving for White */
\r
4025 if (!WhiteOnMove(currentMove)) {
\r
4026 DisplayMoveError("It is Black's turn");
\r
4032 case IcsPlayingBlack:
\r
4033 /* User is moving for Black */
\r
4034 if (WhiteOnMove(currentMove)) {
\r
4035 if (!appData.premove) {
\r
4036 DisplayMoveError("It is White's turn");
\r
4037 } else if (toX >= 0 && toY >= 0) {
\r
4040 premoveFromX = fromX;
\r
4041 premoveFromY = fromY;
\r
4042 premovePromoChar = promoChar;
\r
4044 if (appData.debugMode)
\r
4045 fprintf(debugFP, "Got premove: fromX %d,"
\r
4046 "fromY %d, toX %d, toY %d\n",
\r
4047 fromX, fromY, toX, toY);
\r
4053 case IcsPlayingWhite:
\r
4054 /* User is moving for White */
\r
4055 if (!WhiteOnMove(currentMove)) {
\r
4056 if (!appData.premove) {
\r
4057 DisplayMoveError("It is Black's turn");
\r
4058 } else if (toX >= 0 && toY >= 0) {
\r
4061 premoveFromX = fromX;
\r
4062 premoveFromY = fromY;
\r
4063 premovePromoChar = promoChar;
\r
4065 if (appData.debugMode)
\r
4066 fprintf(debugFP, "Got premove: fromX %d,"
\r
4067 "fromY %d, toX %d, toY %d\n",
\r
4068 fromX, fromY, toX, toY);
\r
4077 case EditPosition:
\r
4078 if (toX == -2 || toY == -2) {
\r
4079 boards[0][fromY][fromX] = EmptySquare;
\r
4080 DrawPosition(FALSE, boards[currentMove]);
\r
4081 } else if (toX >= 0 && toY >= 0) {
\r
4082 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4083 boards[0][fromY][fromX] = EmptySquare;
\r
4084 DrawPosition(FALSE, boards[currentMove]);
\r
4089 if (toX < 0 || toY < 0) return;
\r
4090 userOfferedDraw = FALSE;
\r
4092 if (appData.testLegality) {
\r
4093 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4094 EP_UNKNOWN, castlingRights[currentMove],
\r
4095 fromY, fromX, toY, toX, promoChar);
\r
4096 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4097 DisplayMoveError("Illegal move");
\r
4101 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4104 if (gameMode == Training) {
\r
4105 /* compare the move played on the board to the next move in the
\r
4106 * game. If they match, display the move and the opponent's response.
\r
4107 * If they don't match, display an error message.
\r
4111 CopyBoard(testBoard, boards[currentMove]);
\r
4112 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4114 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4115 ForwardInner(currentMove+1);
\r
4117 /* Autoplay the opponent's response.
\r
4118 * if appData.animate was TRUE when Training mode was entered,
\r
4119 * the response will be animated.
\r
4121 saveAnimate = appData.animate;
\r
4122 appData.animate = animateTraining;
\r
4123 ForwardInner(currentMove+1);
\r
4124 appData.animate = saveAnimate;
\r
4126 /* check for the end of the game */
\r
4127 if (currentMove >= forwardMostMove) {
\r
4128 gameMode = PlayFromGameFile;
\r
4130 SetTrainingModeOff();
\r
4131 DisplayInformation("End of game");
\r
4134 DisplayError("Incorrect move", 0);
\r
4139 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4142 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4144 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4145 ChessMove moveType;
\r
4146 int fromX, fromY, toX, toY;
\r
4147 /*char*/int promoChar;
\r
4149 /* Ok, now we know that the move is good, so we can kill
\r
4150 the previous line in Analysis Mode */
\r
4151 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4152 forwardMostMove = currentMove;
\r
4155 /* If we need the chess program but it's dead, restart it */
\r
4156 ResurrectChessProgram();
\r
4158 /* A user move restarts a paused game*/
\r
4162 thinkOutput[0] = NULLCHAR;
\r
4164 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4166 if (gameMode == BeginningOfGame) {
\r
4167 if (appData.noChessProgram) {
\r
4168 gameMode = EditGame;
\r
4171 char buf[MSG_SIZ];
\r
4172 gameMode = MachinePlaysBlack;
\r
4174 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4175 DisplayTitle(buf);
\r
4176 if (first.sendName) {
\r
4177 sprintf(buf, "name %s\n", gameInfo.white);
\r
4178 SendToProgram(buf, &first);
\r
4184 /* Relay move to ICS or chess engine */
\r
4185 if (appData.icsActive) {
\r
4186 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4187 gameMode == IcsExamining) {
\r
4188 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4189 ics_user_moved = 1;
\r
4192 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4193 gameMode == MachinePlaysWhite ||
\r
4194 gameMode == MachinePlaysBlack)) {
\r
4195 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4197 SendMoveToProgram(forwardMostMove-1, &first);
\r
4198 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4199 first.maybeThinking = TRUE;
\r
4201 if (currentMove == cmailOldMove + 1) {
\r
4202 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4206 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4208 switch (gameMode) {
\r
4210 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4211 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4215 case MT_CHECKMATE:
\r
4216 if (WhiteOnMove(currentMove)) {
\r
4217 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4219 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4222 case MT_STALEMATE:
\r
4223 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4228 case MachinePlaysBlack:
\r
4229 case MachinePlaysWhite:
\r
4230 /* disable certain menu options while machine is thinking */
\r
4231 SetMachineThinkingEnables();
\r
4239 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4241 char * hint = lastHint;
\r
4242 FrontEndProgramStats stats;
\r
4244 stats.which = cps == &first ? 0 : 1;
\r
4245 stats.depth = cpstats->depth;
\r
4246 stats.nodes = cpstats->nodes;
\r
4247 stats.score = cpstats->score;
\r
4248 stats.time = cpstats->time;
\r
4249 stats.pv = cpstats->movelist;
\r
4250 stats.hint = lastHint;
\r
4251 stats.an_move_index = 0;
\r
4252 stats.an_move_count = 0;
\r
4254 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4255 stats.hint = cpstats->move_name;
\r
4256 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4257 stats.an_move_count = cpstats->nr_moves;
\r
4260 SetProgramStats( &stats );
\r
4264 HandleMachineMove(message, cps)
\r
4266 ChessProgramState *cps;
\r
4268 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4269 char realname[MSG_SIZ];
\r
4270 int fromX, fromY, toX, toY;
\r
4271 ChessMove moveType;
\r
4277 * Kludge to ignore BEL characters
\r
4279 while (*message == '\007') message++;
\r
4282 * Look for book output
\r
4284 if (cps == &first && bookRequested) {
\r
4285 if (message[0] == '\t' || message[0] == ' ') {
\r
4286 /* Part of the book output is here; append it */
\r
4287 strcat(bookOutput, message);
\r
4288 strcat(bookOutput, " \n");
\r
4290 } else if (bookOutput[0] != NULLCHAR) {
\r
4291 /* All of book output has arrived; display it */
\r
4292 char *p = bookOutput;
\r
4293 while (*p != NULLCHAR) {
\r
4294 if (*p == '\t') *p = ' ';
\r
4297 DisplayInformation(bookOutput);
\r
4298 bookRequested = FALSE;
\r
4299 /* Fall through to parse the current output */
\r
4304 * Look for machine move.
\r
4306 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4307 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4309 /* This method is only useful on engines that support ping */
\r
4310 if (cps->lastPing != cps->lastPong) {
\r
4311 if (gameMode == BeginningOfGame) {
\r
4312 /* Extra move from before last new; ignore */
\r
4313 if (appData.debugMode) {
\r
4314 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4317 if (appData.debugMode) {
\r
4318 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4319 cps->which, gameMode);
\r
4322 SendToProgram("undo\n", cps);
\r
4327 switch (gameMode) {
\r
4328 case BeginningOfGame:
\r
4329 /* Extra move from before last reset; ignore */
\r
4330 if (appData.debugMode) {
\r
4331 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
4338 /* Extra move after we tried to stop. The mode test is
\r
4339 not a reliable way of detecting this problem, but it's
\r
4340 the best we can do on engines that don't support ping.
\r
4342 if (appData.debugMode) {
\r
4343 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
4344 cps->which, gameMode);
\r
4346 SendToProgram("undo\n", cps);
\r
4349 case MachinePlaysWhite:
\r
4350 case IcsPlayingWhite:
\r
4351 machineWhite = TRUE;
\r
4354 case MachinePlaysBlack:
\r
4355 case IcsPlayingBlack:
\r
4356 machineWhite = FALSE;
\r
4359 case TwoMachinesPlay:
\r
4360 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
4363 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
4364 if (appData.debugMode) {
\r
4366 "Ignoring move out of turn by %s, gameMode %d"
\r
4367 ", forwardMost %d\n",
\r
4368 cps->which, gameMode, forwardMostMove);
\r
4373 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
4374 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4375 /* Machine move could not be parsed; ignore it. */
\r
4376 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
4377 machineMove, cps->which);
\r
4378 DisplayError(buf1, 0);
\r
4379 if (gameMode == TwoMachinesPlay) {
\r
4380 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
4381 "Forfeit due to illegal move", GE_XBOARD);
\r
4386 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
4387 /* So we have to redo legality test with true e.p. status here, */
\r
4388 /* to make sure an illegal e.p. capture does not slip through, */
\r
4389 /* to cause a forfeit on a justified illegal-move complaint */
\r
4390 /* of the opponent. */
\r
4391 if(gameMode==TwoMachinesPlay && appData.testLegality &&
\r
4392 LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
4393 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
4394 fromY, fromX, toY, toX, promoChar) == IllegalMove)
\r
4395 { static char buf[MSG_SIZ];
\r
4396 if (appData.debugMode) {
\r
4398 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
4399 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
4400 fprintf(debugFP, "castling rights\n");
\r
4402 sprintf(buf, "Xboard: Forfeit due to illegal move %s (%c%c%c%c)%c",
\r
4403 machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0);
\r
4404 GameEnds(machineWhite ? BlackWins : WhiteWins, buf, GE_XBOARD);
\r
4406 hintRequested = FALSE;
\r
4407 lastHint[0] = NULLCHAR;
\r
4408 bookRequested = FALSE;
\r
4409 /* Program may be pondering now */
\r
4410 cps->maybeThinking = TRUE;
\r
4411 if (cps->sendTime == 2) cps->sendTime = 1;
\r
4412 if (cps->offeredDraw) cps->offeredDraw--;
\r
4415 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
4417 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4418 ics_user_moved = 1;
\r
4421 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
4422 strcpy(machineMove, currentMoveString);
\r
4423 strcat(machineMove, "\n");
\r
4424 strcpy(moveList[forwardMostMove], machineMove);
\r
4426 /* [AS] Save move info and clear stats for next move */
\r
4427 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
4428 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
4429 pvInfoList[ forwardMostMove ].time = -1;
\r
4430 ClearProgramStats();
\r
4431 thinkOutput[0] = NULLCHAR;
\r
4432 hiddenThinkOutputState = 0;
\r
4434 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
4436 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
4437 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
4440 while( count < adjudicateLossPlies ) {
\r
4441 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
4444 score = -score; /* Flip score for winning side */
\r
4447 if( score > adjudicateLossThreshold ) {
\r
4454 if( count >= adjudicateLossPlies ) {
\r
4455 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4457 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
4458 "Xboard adjudication",
\r
4465 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
4467 if( gameMode == TwoMachinesPlay ) {
\r
4468 int count = 0, epFile = epStatus[forwardMostMove];
\r
4470 if(appData.testLegality) // don't wait for engine to announce game end if we can judge ourselves
\r
4471 switch (MateTest(boards[forwardMostMove],
\r
4472 PosFlags(forwardMostMove), epFile,
\r
4473 castlingRights[forwardMostMove]) ) {
\r
4478 case MT_STALEMATE:
\r
4479 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4480 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
4483 case MT_CHECKMATE:
\r
4484 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4485 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
4486 "Xboard adjudication: Checkmate",
\r
4491 if( appData.testLegality )
\r
4492 { /* [HGM] Some more adjudications for obstinate engines */
\r
4493 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
4495 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
4496 static int moveCount;
\r
4498 /* First absolutely insufficient mating material. Count what is on board. */
\r
4499 for(i=0; i<BOARD_HEIGHT; i++) for(j=0; j<BOARD_WIDTH; j++)
\r
4500 { ChessSquare p = boards[forwardMostMove][i][j];
\r
4504 { /* count B,N,R and other of each side */
\r
4521 case EmptySquare:
\r
4526 PawnAdvance += m; NrPawns++;
\r
4528 NrPieces += (p != EmptySquare);
\r
4531 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )
\r
4532 { /* KBK, KNK or KK */
\r
4534 /* always flag draws, for judging claims */
\r
4535 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
4537 if(adjudicateLossThreshold != 0) {
\r
4538 /* but only adjudicate them if adjudication enabled */
\r
4539 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4540 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
4545 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
4546 if(NrPieces == 4 &&
\r
4547 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
4548 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
4549 || NrWN==2 || NrBN==2 /* KNNK */
\r
4550 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
4552 if(--moveCount < 0 && adjudicateLossThreshold != 0)
\r
4553 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
4554 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4555 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
4558 } else moveCount = 6;
\r
4560 if (appData.debugMode) { int i;
\r
4561 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
4562 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
4563 appData.drawRepeats);
\r
4564 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
4565 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
4568 /* Check for rep-draws */
\r
4570 for(k = forwardMostMove-2;
\r
4571 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
4572 epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
4575 if (appData.debugMode) {
\r
4576 fprintf(debugFP, " loop\n");
\r
4578 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
4579 if (appData.debugMode) {
\r
4580 fprintf(debugFP, "match\n");
\r
4582 /* compare castling rights */
\r
4583 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
4584 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
4585 rights++; /* King lost rights, while rook still had them */
\r
4586 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
4587 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
4588 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
4589 rights++; /* but at least one rook lost them */
\r
4591 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
4592 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
4594 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
4595 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
4596 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
4599 if (appData.debugMode) {
\r
4600 for(i=0; i<nrCastlingRights; i++)
\r
4601 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
4604 if (appData.debugMode) {
\r
4605 fprintf(debugFP, " %d %d\n", rights, k);
\r
4607 if( rights == 0 && ++count > appData.drawRepeats-2
\r
4608 && adjudicateLossThreshold != 0) {
\r
4609 /* adjudicate after user-specified nr of repeats */
\r
4610 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4611 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
4614 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
4615 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
4619 /* Now we test for 50-move draws. Determine ply count */
\r
4620 count = forwardMostMove;
\r
4621 /* look for last irreversble move */
\r
4622 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
4624 /* if we hit starting position, add initial plies */
\r
4625 if( count == backwardMostMove )
\r
4626 count -= initialRulePlies;
\r
4627 count = forwardMostMove - count;
\r
4629 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
4630 /* this is used to judge if draw claims are legal */
\r
4631 if(adjudicateLossThreshold != 0 && count >= 2*appData.ruleMoves) {
\r
4632 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4633 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
4641 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
4642 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4644 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
4649 if (gameMode == TwoMachinesPlay) {
\r
4650 if (cps->other->sendTime) {
\r
4651 SendTimeRemaining(cps->other,
\r
4652 cps->other->twoMachinesColor[0] == 'w');
\r
4654 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
4656 firstMove = FALSE;
\r
4657 if (cps->other->useColors) {
\r
4658 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
4660 SendToProgram("go\n", cps->other);
\r
4662 cps->other->maybeThinking = TRUE;
\r
4665 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4667 if (!pausing && appData.ringBellAfterMoves) {
\r
4672 * Reenable menu items that were disabled while
\r
4673 * machine was thinking
\r
4675 if (gameMode != TwoMachinesPlay)
\r
4676 SetUserThinkingEnables();
\r
4681 /* Set special modes for chess engines. Later something general
\r
4682 * could be added here; for now there is just one kludge feature,
\r
4683 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
4684 * when "xboard" is given as an interactive command.
\r
4686 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
4687 cps->useSigint = FALSE;
\r
4688 cps->useSigterm = FALSE;
\r
4692 * Look for communication commands
\r
4694 if (!strncmp(message, "telluser ", 9)) {
\r
4695 DisplayNote(message + 9);
\r
4698 if (!strncmp(message, "tellusererror ", 14)) {
\r
4699 DisplayError(message + 14, 0);
\r
4702 if (!strncmp(message, "tellopponent ", 13)) {
\r
4703 if (appData.icsActive) {
\r
4705 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
4709 DisplayNote(message + 13);
\r
4713 if (!strncmp(message, "tellothers ", 11)) {
\r
4714 if (appData.icsActive) {
\r
4716 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
4722 if (!strncmp(message, "tellall ", 8)) {
\r
4723 if (appData.icsActive) {
\r
4725 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
4729 DisplayNote(message + 8);
\r
4733 if (strncmp(message, "warning", 7) == 0) {
\r
4734 /* Undocumented feature, use tellusererror in new code */
\r
4735 DisplayError(message, 0);
\r
4738 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
4739 strcpy(realname, cps->tidy);
\r
4740 strcat(realname, " query");
\r
4741 AskQuestion(realname, buf2, buf1, cps->pr);
\r
4744 /* Commands from the engine directly to ICS. We don't allow these to be
\r
4745 * sent until we are logged on. Crafty kibitzes have been known to
\r
4746 * interfere with the login process.
\r
4749 if (!strncmp(message, "tellics ", 8)) {
\r
4750 SendToICS(message + 8);
\r
4754 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
4755 SendToICS(ics_prefix);
\r
4756 SendToICS(message + 15);
\r
4760 /* The following are for backward compatibility only */
\r
4761 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
4762 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
4763 SendToICS(ics_prefix);
\r
4764 SendToICS(message);
\r
4769 if (strncmp(message, "feature ", 8) == 0) {
\r
4770 ParseFeatures(message+8, cps);
\r
4772 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
4776 * If the move is illegal, cancel it and redraw the board.
\r
4777 * Also deal with other error cases. Matching is rather loose
\r
4778 * here to accommodate engines written before the spec.
\r
4780 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
4781 strncmp(message, "Error", 5) == 0) {
\r
4782 if (StrStr(message, "name") ||
\r
4783 StrStr(message, "rating") || StrStr(message, "?") ||
\r
4784 StrStr(message, "result") || StrStr(message, "board") ||
\r
4785 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
4786 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
4787 StrStr(message, "random") || StrStr(message, "depth") ||
\r
4788 StrStr(message, "accepted")) {
\r
4791 if (StrStr(message, "protover")) {
\r
4792 /* Program is responding to input, so it's apparently done
\r
4793 initializing, and this error message indicates it is
\r
4794 protocol version 1. So we don't need to wait any longer
\r
4795 for it to initialize and send feature commands. */
\r
4796 FeatureDone(cps, 1);
\r
4797 cps->protocolVersion = 1;
\r
4800 cps->maybeThinking = FALSE;
\r
4802 if (StrStr(message, "draw")) {
\r
4803 /* Program doesn't have "draw" command */
\r
4804 cps->sendDrawOffers = 0;
\r
4807 if (cps->sendTime != 1 &&
\r
4808 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
4809 /* Program apparently doesn't have "time" or "otim" command */
\r
4810 cps->sendTime = 0;
\r
4813 if (StrStr(message, "analyze")) {
\r
4814 cps->analysisSupport = FALSE;
\r
4815 cps->analyzing = FALSE;
\r
4816 Reset(FALSE, TRUE);
\r
4817 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
4818 DisplayError(buf2, 0);
\r
4821 if (StrStr(message, "(no matching move)st")) {
\r
4822 /* Special kludge for GNU Chess 4 only */
\r
4823 cps->stKludge = TRUE;
\r
4824 SendTimeControl(cps, movesPerSession, timeControl,
\r
4825 timeIncrement, appData.searchDepth,
\r
4829 if (StrStr(message, "(no matching move)sd")) {
\r
4830 /* Special kludge for GNU Chess 4 only */
\r
4831 cps->sdKludge = TRUE;
\r
4832 SendTimeControl(cps, movesPerSession, timeControl,
\r
4833 timeIncrement, appData.searchDepth,
\r
4837 if (!StrStr(message, "llegal")) {
\r
4840 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
4841 gameMode == IcsIdle) return;
\r
4842 if (forwardMostMove <= backwardMostMove) return;
\r
4844 /* Following removed: it caused a bug where a real illegal move
\r
4845 message in analyze mored would be ignored. */
\r
4846 if (cps == &first && programStats.ok_to_send == 0) {
\r
4847 /* Bogus message from Crafty responding to "." This filtering
\r
4848 can miss some of the bad messages, but fortunately the bug
\r
4849 is fixed in current Crafty versions, so it doesn't matter. */
\r
4853 if (pausing) PauseEvent();
\r
4854 if (gameMode == PlayFromGameFile) {
\r
4855 /* Stop reading this game file */
\r
4856 gameMode = EditGame;
\r
4859 currentMove = --forwardMostMove;
\r
4860 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
4862 DisplayBothClocks();
\r
4863 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
4864 parseList[currentMove], cps->which);
\r
4865 DisplayMoveError(buf1);
\r
4866 DrawPosition(FALSE, boards[currentMove]);
\r
4868 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
4869 /* only passes fully legal moves */
\r
4870 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
4871 static char buf[MSG_SIZ];
\r
4872 sprintf(buf, "False illegal-move claim on %s (%c%c%c%c)%c",
\r
4873 machineMove, fromX+'a', fromY+ONE, toX+'a', toY+ONE, 0);
\r
4874 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
4879 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
4880 /* Program has a broken "time" command that
\r
4881 outputs a string not ending in newline.
\r
4883 cps->sendTime = 0;
\r
4887 * If chess program startup fails, exit with an error message.
\r
4888 * Attempts to recover here are futile.
\r
4890 if ((StrStr(message, "unknown host") != NULL)
\r
4891 || (StrStr(message, "No remote directory") != NULL)
\r
4892 || (StrStr(message, "not found") != NULL)
\r
4893 || (StrStr(message, "No such file") != NULL)
\r
4894 || (StrStr(message, "can't alloc") != NULL)
\r
4895 || (StrStr(message, "Permission denied") != NULL)) {
\r
4897 cps->maybeThinking = FALSE;
\r
4898 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
4899 cps->which, cps->program, cps->host, message);
\r
4900 RemoveInputSource(cps->isr);
\r
4901 DisplayFatalError(buf1, 0, 1);
\r
4906 * Look for hint output
\r
4908 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
4909 if (cps == &first && hintRequested) {
\r
4910 hintRequested = FALSE;
\r
4911 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
4912 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
4913 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
4914 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
4915 fromY, fromX, toY, toX, promoChar, buf1);
\r
4916 sprintf(buf2, "Hint: %s", buf1);
\r
4917 DisplayInformation(buf2);
\r
4919 /* Hint move could not be parsed!? */
\r
4921 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
4922 buf1, cps->which);
\r
4923 DisplayError(buf2, 0);
\r
4926 strcpy(lastHint, buf1);
\r
4932 * Ignore other messages if game is not in progress
\r
4934 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
4935 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
4938 * look for win, lose, draw, or draw offer
\r
4940 if (strncmp(message, "1-0", 3) == 0) {
\r
4941 char *p, *q, *r = "";
\r
4942 p = strchr(message, '{');
\r
4944 q = strchr(p, '}');
\r
4950 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
4952 } else if (strncmp(message, "0-1", 3) == 0) {
\r
4953 char *p, *q, *r = "";
\r
4954 p = strchr(message, '{');
\r
4956 q = strchr(p, '}');
\r
4962 /* Kludge for Arasan 4.1 bug */
\r
4963 if (strcmp(r, "Black resigns") == 0) {
\r
4964 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
4967 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
4969 } else if (strncmp(message, "1/2", 3) == 0) {
\r
4970 char *p, *q, *r = "";
\r
4971 p = strchr(message, '{');
\r
4973 q = strchr(p, '}');
\r
4980 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
4983 } else if (strncmp(message, "White resign", 12) == 0) {
\r
4984 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
4986 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
4987 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
4989 } else if (strncmp(message, "White", 5) == 0 &&
\r
4990 message[5] != '(' &&
\r
4991 StrStr(message, "Black") == NULL) {
\r
4992 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
4994 } else if (strncmp(message, "Black", 5) == 0 &&
\r
4995 message[5] != '(') {
\r
4996 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
4998 } else if (strcmp(message, "resign") == 0 ||
\r
4999 strcmp(message, "computer resigns") == 0) {
\r
5000 switch (gameMode) {
\r
5001 case MachinePlaysBlack:
\r
5002 case IcsPlayingBlack:
\r
5003 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5005 case MachinePlaysWhite:
\r
5006 case IcsPlayingWhite:
\r
5007 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5009 case TwoMachinesPlay:
\r
5010 if (cps->twoMachinesColor[0] == 'w')
\r
5011 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5013 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5016 /* can't happen */
\r
5020 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5021 switch (gameMode) {
\r
5022 case MachinePlaysBlack:
\r
5023 case IcsPlayingBlack:
\r
5024 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5026 case MachinePlaysWhite:
\r
5027 case IcsPlayingWhite:
\r
5028 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5030 case TwoMachinesPlay:
\r
5031 if (cps->twoMachinesColor[0] == 'w')
\r
5032 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5034 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5037 /* can't happen */
\r
5041 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5042 switch (gameMode) {
\r
5043 case MachinePlaysBlack:
\r
5044 case IcsPlayingBlack:
\r
5045 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5047 case MachinePlaysWhite:
\r
5048 case IcsPlayingWhite:
\r
5049 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5051 case TwoMachinesPlay:
\r
5052 if (cps->twoMachinesColor[0] == 'w')
\r
5053 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5055 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5058 /* can't happen */
\r
5062 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5063 if (WhiteOnMove(forwardMostMove)) {
\r
5064 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5066 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5069 } else if (strstr(message, "Draw") != NULL ||
\r
5070 strstr(message, "game is a draw") != NULL) {
\r
5071 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5073 } else if (strstr(message, "offer") != NULL &&
\r
5074 strstr(message, "draw") != NULL) {
\r
5076 if (appData.zippyPlay && first.initDone) {
\r
5077 /* Relay offer to ICS */
\r
5078 SendToICS(ics_prefix);
\r
5079 SendToICS("draw\n");
\r
5082 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5083 if (gameMode == TwoMachinesPlay) {
\r
5084 if (cps->other->offeredDraw) {
\r
5085 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5087 if (cps->other->sendDrawOffers) {
\r
5088 SendToProgram("draw\n", cps->other);
\r
5091 } else if (gameMode == MachinePlaysWhite ||
\r
5092 gameMode == MachinePlaysBlack) {
\r
5093 if (userOfferedDraw) {
\r
5094 DisplayInformation("Machine accepts your draw offer");
\r
5095 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5097 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5104 * Look for thinking output
\r
5106 if ( appData.showThinking) {
\r
5107 int plylev, mvleft, mvtot, curscore, time;
\r
5108 char mvname[MOVE_LEN];
\r
5109 unsigned long nodes;
\r
5111 int ignore = FALSE;
\r
5112 int prefixHint = FALSE;
\r
5113 mvname[0] = NULLCHAR;
\r
5115 switch (gameMode) {
\r
5116 case MachinePlaysBlack:
\r
5117 case IcsPlayingBlack:
\r
5118 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5120 case MachinePlaysWhite:
\r
5121 case IcsPlayingWhite:
\r
5122 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5127 case TwoMachinesPlay:
\r
5128 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5138 buf1[0] = NULLCHAR;
\r
5139 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5140 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5142 if (plyext != ' ' && plyext != '\t') {
\r
5146 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5147 if( cps->scoreIsAbsolute &&
\r
5148 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5150 curscore = -curscore;
\r
5154 programStats.depth = plylev;
\r
5155 programStats.nodes = nodes;
\r
5156 programStats.time = time;
\r
5157 programStats.score = curscore;
\r
5158 programStats.got_only_move = 0;
\r
5160 /* Buffer overflow protection */
\r
5161 if (buf1[0] != NULLCHAR) {
\r
5162 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5163 && appData.debugMode) {
\r
5165 "PV is too long; using the first %d bytes.\n",
\r
5166 sizeof(programStats.movelist) - 1);
\r
5169 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5171 sprintf(programStats.movelist, " no PV\n");
\r
5174 if (programStats.seen_stat) {
\r
5175 programStats.ok_to_send = 1;
\r
5178 if (strchr(programStats.movelist, '(') != NULL) {
\r
5179 programStats.line_is_book = 1;
\r
5180 programStats.nr_moves = 0;
\r
5181 programStats.moves_left = 0;
\r
5183 programStats.line_is_book = 0;
\r
5186 SendProgramStatsToFrontend( cps, &programStats );
\r
5189 [AS] Protect the thinkOutput buffer from overflow... this
\r
5190 is only useful if buf1 hasn't overflowed first!
\r
5192 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5194 (gameMode == TwoMachinesPlay ?
\r
5195 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5196 ((double) curscore) / 100.0,
\r
5197 prefixHint ? lastHint : "",
\r
5198 prefixHint ? " " : "" );
\r
5200 if( buf1[0] != NULLCHAR ) {
\r
5201 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5203 if( strlen(buf1) > max_len ) {
\r
5204 if( appData.debugMode) {
\r
5205 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5207 buf1[max_len+1] = '\0';
\r
5210 strcat( thinkOutput, buf1 );
\r
5213 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5214 DisplayMove(currentMove - 1);
\r
5215 DisplayAnalysis();
\r
5219 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
5220 /* crafty (9.25+) says "(only move) <move>"
\r
5221 * if there is only 1 legal move
\r
5223 sscanf(p, "(only move) %s", buf1);
\r
5224 sprintf(thinkOutput, "%s (only move)", buf1);
\r
5225 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
5226 programStats.depth = 1;
\r
5227 programStats.nr_moves = 1;
\r
5228 programStats.moves_left = 1;
\r
5229 programStats.nodes = 1;
\r
5230 programStats.time = 1;
\r
5231 programStats.got_only_move = 1;
\r
5233 /* Not really, but we also use this member to
\r
5234 mean "line isn't going to change" (Crafty
\r
5235 isn't searching, so stats won't change) */
\r
5236 programStats.line_is_book = 1;
\r
5238 SendProgramStatsToFrontend( cps, &programStats );
\r
5240 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5241 DisplayMove(currentMove - 1);
\r
5242 DisplayAnalysis();
\r
5245 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
5246 &time, &nodes, &plylev, &mvleft,
\r
5247 &mvtot, mvname) >= 5) {
\r
5248 /* The stat01: line is from Crafty (9.29+) in response
\r
5249 to the "." command */
\r
5250 programStats.seen_stat = 1;
\r
5251 cps->maybeThinking = TRUE;
\r
5253 if (programStats.got_only_move || !appData.periodicUpdates)
\r
5256 programStats.depth = plylev;
\r
5257 programStats.time = time;
\r
5258 programStats.nodes = nodes;
\r
5259 programStats.moves_left = mvleft;
\r
5260 programStats.nr_moves = mvtot;
\r
5261 strcpy(programStats.move_name, mvname);
\r
5262 programStats.ok_to_send = 1;
\r
5263 programStats.movelist[0] = '\0';
\r
5265 SendProgramStatsToFrontend( cps, &programStats );
\r
5267 DisplayAnalysis();
\r
5270 } else if (strncmp(message,"++",2) == 0) {
\r
5271 /* Crafty 9.29+ outputs this */
\r
5272 programStats.got_fail = 2;
\r
5275 } else if (strncmp(message,"--",2) == 0) {
\r
5276 /* Crafty 9.29+ outputs this */
\r
5277 programStats.got_fail = 1;
\r
5280 } else if (thinkOutput[0] != NULLCHAR &&
\r
5281 strncmp(message, " ", 4) == 0) {
\r
5282 unsigned message_len;
\r
5285 while (*p && *p == ' ') p++;
\r
5287 message_len = strlen( p );
\r
5289 /* [AS] Avoid buffer overflow */
\r
5290 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
5291 strcat(thinkOutput, " ");
\r
5292 strcat(thinkOutput, p);
\r
5295 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
5296 strcat(programStats.movelist, " ");
\r
5297 strcat(programStats.movelist, p);
\r
5300 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
5301 DisplayMove(currentMove - 1);
\r
5302 DisplayAnalysis();
\r
5308 buf1[0] = NULLCHAR;
\r
5310 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5311 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
5313 ChessProgramStats cpstats;
\r
5315 if (plyext != ' ' && plyext != '\t') {
\r
5319 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5320 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
5321 curscore = -curscore;
\r
5324 cpstats.depth = plylev;
\r
5325 cpstats.nodes = nodes;
\r
5326 cpstats.time = time;
\r
5327 cpstats.score = curscore;
\r
5328 cpstats.got_only_move = 0;
\r
5329 cpstats.movelist[0] = '\0';
\r
5331 if (buf1[0] != NULLCHAR) {
\r
5332 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
5335 cpstats.ok_to_send = 0;
\r
5336 cpstats.line_is_book = 0;
\r
5337 cpstats.nr_moves = 0;
\r
5338 cpstats.moves_left = 0;
\r
5340 SendProgramStatsToFrontend( cps, &cpstats );
\r
5347 /* Parse a game score from the character string "game", and
\r
5348 record it as the history of the current game. The game
\r
5349 score is NOT assumed to start from the standard position.
\r
5350 The display is not updated in any way.
\r
5353 ParseGameHistory(game)
\r
5356 ChessMove moveType;
\r
5357 int fromX, fromY, toX, toY, boardIndex;
\r
5360 char buf[MSG_SIZ];
\r
5362 if (appData.debugMode)
\r
5363 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
5365 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
5366 gameInfo.site = StrSave(appData.icsHost);
\r
5367 gameInfo.date = PGNDate();
\r
5368 gameInfo.round = StrSave("-");
\r
5370 /* Parse out names of players */
\r
5371 while (*game == ' ') game++;
\r
5373 while (*game != ' ') *p++ = *game++;
\r
5375 gameInfo.white = StrSave(buf);
\r
5376 while (*game == ' ') game++;
\r
5378 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
5380 gameInfo.black = StrSave(buf);
\r
5383 boardIndex = blackPlaysFirst ? 1 : 0;
\r
5386 yyboardindex = boardIndex;
\r
5387 moveType = (ChessMove) yylex();
\r
5388 switch (moveType) {
\r
5390 case WhitePromotionChancellor:
\r
5391 case BlackPromotionChancellor:
\r
5392 case WhitePromotionArchbishop:
\r
5393 case BlackPromotionArchbishop:
\r
5395 case WhitePromotionQueen:
\r
5396 case BlackPromotionQueen:
\r
5397 case WhitePromotionRook:
\r
5398 case BlackPromotionRook:
\r
5399 case WhitePromotionBishop:
\r
5400 case BlackPromotionBishop:
\r
5401 case WhitePromotionKnight:
\r
5402 case BlackPromotionKnight:
\r
5403 case WhitePromotionKing:
\r
5404 case BlackPromotionKing:
\r
5406 case WhiteCapturesEnPassant:
\r
5407 case BlackCapturesEnPassant:
\r
5408 case WhiteKingSideCastle:
\r
5409 case WhiteQueenSideCastle:
\r
5410 case BlackKingSideCastle:
\r
5411 case BlackQueenSideCastle:
\r
5412 case WhiteKingSideCastleWild:
\r
5413 case WhiteQueenSideCastleWild:
\r
5414 case BlackKingSideCastleWild:
\r
5415 case BlackQueenSideCastleWild:
\r
5417 case WhiteHSideCastleFR:
\r
5418 case WhiteASideCastleFR:
\r
5419 case BlackHSideCastleFR:
\r
5420 case BlackASideCastleFR:
\r
5422 case IllegalMove: /* maybe suicide chess, etc. */
\r
5423 fromX = currentMoveString[0] - 'a';
\r
5424 fromY = currentMoveString[1] - ONE;
\r
5425 toX = currentMoveString[2] - 'a';
\r
5426 toY = currentMoveString[3] - ONE;
\r
5427 promoChar = currentMoveString[4];
\r
5431 fromX = moveType == WhiteDrop ?
\r
5432 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
5433 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
5434 fromY = DROP_RANK;
\r
5435 toX = currentMoveString[2] - 'a';
\r
5436 toY = currentMoveString[3] - ONE;
\r
5437 promoChar = NULLCHAR;
\r
5439 case AmbiguousMove:
\r
5441 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
5442 DisplayError(buf, 0);
\r
5444 case ImpossibleMove:
\r
5446 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
5447 DisplayError(buf, 0);
\r
5449 case (ChessMove) 0: /* end of file */
\r
5450 if (boardIndex < backwardMostMove) {
\r
5451 /* Oops, gap. How did that happen? */
\r
5452 DisplayError("Gap in move list", 0);
\r
5455 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5456 if (boardIndex > forwardMostMove) {
\r
5457 forwardMostMove = boardIndex;
\r
5461 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
5462 strcat(parseList[boardIndex-1], " ");
\r
5463 strcat(parseList[boardIndex-1], yy_text);
\r
5475 case GameUnfinished:
\r
5476 if (gameMode == IcsExamining) {
\r
5477 if (boardIndex < backwardMostMove) {
\r
5478 /* Oops, gap. How did that happen? */
\r
5481 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5484 gameInfo.result = moveType;
\r
5485 p = strchr(yy_text, '{');
\r
5486 if (p == NULL) p = strchr(yy_text, '(');
\r
5489 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
5491 q = strchr(p, *p == '{' ? '}' : ')');
\r
5492 if (q != NULL) *q = NULLCHAR;
\r
5495 gameInfo.resultDetails = StrSave(p);
\r
5498 if (boardIndex >= forwardMostMove &&
\r
5499 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
5500 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
5503 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
5504 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
5505 parseList[boardIndex]);
\r
5506 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
5507 /* currentMoveString is set as a side-effect of yylex */
\r
5508 strcpy(moveList[boardIndex], currentMoveString);
\r
5509 strcat(moveList[boardIndex], "\n");
\r
5511 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
5512 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
5513 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
5515 case MT_STALEMATE:
\r
5519 strcat(parseList[boardIndex - 1], "+");
\r
5521 case MT_CHECKMATE:
\r
5522 strcat(parseList[boardIndex - 1], "#");
\r
5529 /* Apply a move to the given board */
\r
5531 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
5532 int fromX, fromY, toX, toY;
\r
5536 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
5537 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
5538 && promoChar != 0) promoChar = 'f';
\r
5540 ChessSquare captured = board[toY][toX];
\r
5541 if (fromY == DROP_RANK) {
\r
5542 /* must be first */
\r
5543 board[toY][toX] = (ChessSquare) fromX;
\r
5544 } else if (fromX == toX && fromY == toY) {
\r
5548 /* Code added by Tord: */
\r
5549 /* FRC castling assumed when king captures friendly rook. */
\r
5550 else if (board[fromY][fromX] == WhiteKing &&
\r
5551 board[toY][toX] == WhiteRook) {
\r
5552 board[fromY][fromX] = EmptySquare;
\r
5553 board[toY][toX] = EmptySquare;
\r
5555 board[0][BOARD_WIDTH-2] = WhiteKing; board[0][BOARD_WIDTH-3] = WhiteRook;
\r
5557 board[0][2] = WhiteKing; board[0][3] = WhiteRook;
\r
5559 } else if (board[fromY][fromX] == BlackKing &&
\r
5560 board[toY][toX] == BlackRook) {
\r
5561 board[fromY][fromX] = EmptySquare;
\r
5562 board[toY][toX] = EmptySquare;
\r
5564 board[BOARD_HEIGHT-1][BOARD_WIDTH-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_WIDTH-3] = BlackRook;
\r
5566 board[BOARD_HEIGHT-1][2] = BlackKing; board[BOARD_HEIGHT-1][3] = BlackRook;
\r
5568 /* End of code added by Tord */
\r
5570 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5571 && board[fromY][fromX] == WhiteKing
\r
5572 && toY == fromY && toX > fromX+1) {
\r
5573 board[fromY][fromX] = EmptySquare;
\r
5574 board[toY][toX] = WhiteKing;
\r
5575 board[fromY][BOARD_WIDTH-1] = EmptySquare;
\r
5576 board[toY][toX-1] = WhiteRook;
\r
5577 } else if (initialPosition[fromY][fromX] == WhiteKing
\r
5578 && board[fromY][fromX] == WhiteKing
\r
5579 && toY == fromY && toX < fromX-1) {
\r
5580 board[fromY][fromX] = EmptySquare;
\r
5581 board[toY][toX] = WhiteKing;
\r
5582 board[fromY][0] = EmptySquare;
\r
5583 board[toY][toX+1] = WhiteRook;
\r
5584 } else if (fromY == 0 && fromX == 3
\r
5585 && board[fromY][fromX] == WhiteKing
\r
5586 && toY == 0 && toX == 5) {
\r
5587 board[fromY][fromX] = EmptySquare;
\r
5588 board[toY][toX] = WhiteKing;
\r
5589 board[fromY][7] = EmptySquare;
\r
5590 board[toY][4] = WhiteRook;
\r
5591 } else if (fromY == 0 && fromX == 3
\r
5592 && board[fromY][fromX] == WhiteKing
\r
5593 && toY == 0 && toX == 1) {
\r
5594 board[fromY][fromX] = EmptySquare;
\r
5595 board[toY][toX] = WhiteKing;
\r
5596 board[fromY][0] = EmptySquare;
\r
5597 board[toY][2] = WhiteRook;
\r
5598 } else if (board[fromY][fromX] == WhitePawn
\r
5599 && toY == BOARD_HEIGHT-1
\r
5601 && gameInfo.variant != VariantXiangqi
\r
5604 /* white pawn promotion */
\r
5605 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
5606 if (board[toY][toX] == EmptySquare) {
\r
5607 board[toY][toX] = WhiteQueen;
\r
5609 board[fromY][fromX] = EmptySquare;
\r
5610 } else if ((fromY == BOARD_HEIGHT-4)
\r
5612 && (board[fromY][fromX] == WhitePawn)
\r
5613 && (board[toY][toX] == EmptySquare)) {
\r
5614 board[fromY][fromX] = EmptySquare;
\r
5615 board[toY][toX] = WhitePawn;
\r
5616 captured = board[toY - 1][toX];
\r
5617 board[toY - 1][toX] = EmptySquare;
\r
5618 } else if (initialPosition[fromY][fromX] == BlackKing
\r
5619 && board[fromY][fromX] == BlackKing
\r
5620 && toY == fromY && toX > fromX+1) {
\r
5621 board[fromY][fromX] = EmptySquare;
\r
5622 board[toY][toX] = BlackKing;
\r
5623 board[fromY][BOARD_WIDTH-1] = EmptySquare;
\r
5624 board[toY][toX-1] = BlackRook;
\r
5625 } else if (initialPosition[fromY][fromX] == BlackKing
\r
5626 && board[fromY][fromX] == BlackKing
\r
5627 && toY == fromY && toX < fromX-1) {
\r
5628 board[fromY][fromX] = EmptySquare;
\r
5629 board[toY][toX] = BlackKing;
\r
5630 board[fromY][0] = EmptySquare;
\r
5631 board[toY][toX+1] = BlackRook;
\r
5632 } else if (fromY == 7 && fromX == 3
\r
5633 && board[fromY][fromX] == BlackKing
\r
5634 && toY == 7 && toX == 5) {
\r
5635 board[fromY][fromX] = EmptySquare;
\r
5636 board[toY][toX] = BlackKing;
\r
5637 board[fromY][7] = EmptySquare;
\r
5638 board[toY][4] = BlackRook;
\r
5639 } else if (fromY == 7 && fromX == 3
\r
5640 && board[fromY][fromX] == BlackKing
\r
5641 && toY == 7 && toX == 1) {
\r
5642 board[fromY][fromX] = EmptySquare;
\r
5643 board[toY][toX] = BlackKing;
\r
5644 board[fromY][0] = EmptySquare;
\r
5645 board[toY][2] = BlackRook;
\r
5646 } else if (board[fromY][fromX] == BlackPawn
\r
5649 && gameInfo.variant != VariantXiangqi
\r
5652 /* black pawn promotion */
\r
5653 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
5654 if (board[0][toX] == EmptySquare) {
\r
5655 board[0][toX] = BlackQueen;
\r
5657 board[fromY][fromX] = EmptySquare;
\r
5658 } else if ((fromY == 3)
\r
5660 && (board[fromY][fromX] == BlackPawn)
\r
5661 && (board[toY][toX] == EmptySquare)) {
\r
5662 board[fromY][fromX] = EmptySquare;
\r
5663 board[toY][toX] = BlackPawn;
\r
5664 captured = board[toY + 1][toX];
\r
5665 board[toY + 1][toX] = EmptySquare;
\r
5667 board[toY][toX] = board[fromY][fromX];
\r
5668 board[fromY][fromX] = EmptySquare;
\r
5670 if (gameInfo.variant == VariantCrazyhouse) {
\r
5672 /* !!A lot more code needs to be written to support holdings */
\r
5673 if (fromY == DROP_RANK) {
\r
5674 /* Delete from holdings */
\r
5675 if (holdings[(int) fromX] > 0) holdings[(int) fromX]--;
\r
5677 if (captured != EmptySquare) {
\r
5678 /* Add to holdings */
\r
5679 if (captured < BlackPawn) {
\r
5680 holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++;
\r
5682 holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++;
\r
5686 } else if (gameInfo.variant == VariantAtomic) {
\r
5687 if (captured != EmptySquare) {
\r
5689 for (y = toY-1; y <= toY+1; y++) {
\r
5690 for (x = toX-1; x <= toX+1; x++) {
\r
5691 if (y >= 0 && y < BOARD_WIDTH && x >= 0 && x < BOARD_WIDTH &&
\r
5692 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
5693 board[y][x] = EmptySquare;
\r
5697 board[toY][toX] = EmptySquare;
\r
5702 /* Updates forwardMostMove */
\r
5704 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
5705 int fromX, fromY, toX, toY;
\r
5708 forwardMostMove++;
\r
5710 if (forwardMostMove >= MAX_MOVES) {
\r
5711 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
5716 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
5717 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
5718 if (commentList[forwardMostMove] != NULL) {
\r
5719 free(commentList[forwardMostMove]);
\r
5720 commentList[forwardMostMove] = NULL;
\r
5722 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
5723 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
5726 epStatus[forwardMostMove] = EP_NONE;
\r
5728 if( boards[forwardMostMove][toY][toX] != EmptySquare )
\r
5729 epStatus[forwardMostMove] = EP_CAPTURE;
\r
5731 if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {
\r
5732 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
5733 if( toY-fromY==2 &&
\r
5734 (toX>1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||
\r
5735 toX<BOARD_WIDTH-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )
\r
5736 epStatus[forwardMostMove] = toX;
\r
5738 if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {
\r
5739 epStatus[forwardMostMove] = EP_PAWN_MOVE;
\r
5740 if( toY-fromY== -2 &&
\r
5741 (toX>1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||
\r
5742 toX<BOARD_WIDTH-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )
\r
5743 epStatus[forwardMostMove] = toX;
\r
5746 for(i=0; i<nrCastlingRights; i++) {
\r
5747 castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];
\r
5748 if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||
\r
5749 castlingRights[forwardMostMove][i] == toX && castlingRank[i] == toY
\r
5750 ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece
\r
5755 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
5756 gameInfo.result = GameUnfinished;
\r
5757 if (gameInfo.resultDetails != NULL) {
\r
5758 free(gameInfo.resultDetails);
\r
5759 gameInfo.resultDetails = NULL;
\r
5761 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
5762 moveList[forwardMostMove - 1]);
\r
5763 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
5764 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
5765 fromY, fromX, toY, toX, promoChar,
\r
5766 parseList[forwardMostMove - 1]);
\r
5767 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5768 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
5769 castlingRights[forwardMostMove]) ) {
\r
5771 case MT_STALEMATE:
\r
5775 strcat(parseList[forwardMostMove - 1], "+");
\r
5777 case MT_CHECKMATE:
\r
5778 strcat(parseList[forwardMostMove - 1], "#");
\r
5783 /* Updates currentMove if not pausing */
\r
5785 ShowMove(fromX, fromY, toX, toY)
\r
5787 int instant = (gameMode == PlayFromGameFile) ?
\r
5788 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
5789 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
5791 if (forwardMostMove == currentMove + 1) {
\r
5792 AnimateMove(boards[forwardMostMove - 1],
\r
5793 fromX, fromY, toX, toY);
\r
5795 if (appData.highlightLastMove) {
\r
5796 SetHighlights(fromX, fromY, toX, toY);
\r
5799 currentMove = forwardMostMove;
\r
5802 if (instant) return;
\r
5804 DisplayMove(currentMove - 1);
\r
5805 DrawPosition(FALSE, boards[currentMove]);
\r
5806 DisplayBothClocks();
\r
5807 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
5812 InitChessProgram(cps)
\r
5813 ChessProgramState *cps;
\r
5815 char buf[MSG_SIZ];
\r
5816 if (appData.noChessProgram) return;
\r
5817 hintRequested = FALSE;
\r
5818 bookRequested = FALSE;
\r
5819 SendToProgram(cps->initString, cps);
\r
5820 if (gameInfo.variant != VariantNormal &&
\r
5821 gameInfo.variant != VariantLoadable) {
\r
5822 char *v = VariantName(gameInfo.variant);
\r
5823 if (StrStr(cps->variants, v) == NULL) {
\r
5824 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
5825 DisplayFatalError(buf, 0, 1);
\r
5828 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
5829 SendToProgram(buf, cps);
\r
5831 if (cps->sendICS) {
\r
5832 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
5833 SendToProgram(buf, cps);
\r
5835 cps->maybeThinking = FALSE;
\r
5836 cps->offeredDraw = 0;
\r
5837 if (!appData.icsActive) {
\r
5838 SendTimeControl(cps, movesPerSession, timeControl,
\r
5839 timeIncrement, appData.searchDepth,
\r
5842 if (appData.showThinking) {
\r
5843 SendToProgram("post\n", cps);
\r
5845 SendToProgram("hard\n", cps);
\r
5846 if (!appData.ponderNextMove) {
\r
5847 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
5848 it without being sure what state we are in first. "hard"
\r
5849 is not a toggle, so that one is OK.
\r
5851 SendToProgram("easy\n", cps);
\r
5853 if (cps->usePing) {
\r
5854 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
5855 SendToProgram(buf, cps);
\r
5857 cps->initDone = TRUE;
\r
5862 StartChessProgram(cps)
\r
5863 ChessProgramState *cps;
\r
5865 char buf[MSG_SIZ];
\r
5868 if (appData.noChessProgram) return;
\r
5869 cps->initDone = FALSE;
\r
5871 if (strcmp(cps->host, "localhost") == 0) {
\r
5872 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
5873 } else if (*appData.remoteShell == NULLCHAR) {
\r
5874 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
5876 if (*appData.remoteUser == NULLCHAR) {
\r
5877 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
5880 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
5881 cps->host, appData.remoteUser, cps->program);
\r
5883 err = StartChildProcess(buf, "", &cps->pr);
\r
5887 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
5888 DisplayFatalError(buf, err, 1);
\r
5894 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
5895 if (cps->protocolVersion > 1) {
\r
5896 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
5897 SendToProgram(buf, cps);
\r
5899 SendToProgram("xboard\n", cps);
\r
5905 TwoMachinesEventIfReady P((void))
\r
5907 if (first.lastPing != first.lastPong) {
\r
5908 DisplayMessage("", "Waiting for first chess program");
\r
5909 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
5912 if (second.lastPing != second.lastPong) {
\r
5913 DisplayMessage("", "Waiting for second chess program");
\r
5914 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
5918 TwoMachinesEvent();
\r
5922 NextMatchGame P((void))
\r
5924 Reset(FALSE, TRUE);
\r
5925 if (*appData.loadGameFile != NULLCHAR) {
\r
5926 LoadGameFromFile(appData.loadGameFile,
\r
5927 appData.loadGameIndex,
\r
5928 appData.loadGameFile, FALSE);
\r
5929 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
5930 LoadPositionFromFile(appData.loadPositionFile,
\r
5931 appData.loadPositionIndex,
\r
5932 appData.loadPositionFile);
\r
5934 TwoMachinesEventIfReady();
\r
5937 void UserAdjudicationEvent( int result )
\r
5939 ChessMove gameResult = GameIsDrawn;
\r
5941 if( result > 0 ) {
\r
5942 gameResult = WhiteWins;
\r
5944 else if( result < 0 ) {
\r
5945 gameResult = BlackWins;
\r
5948 if( gameMode == TwoMachinesPlay ) {
\r
5949 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
5955 GameEnds(result, resultDetails, whosays)
\r
5957 char *resultDetails;
\r
5960 GameMode nextGameMode;
\r
5962 char buf[MSG_SIZ];
\r
5964 if (appData.debugMode) {
\r
5965 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
5966 result, resultDetails ? resultDetails : "(null)", whosays);
\r
5969 if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
5970 /* If we are playing on ICS, the server decides when the
\r
5971 game is over, but the engine can offer to draw, claim
\r
5972 a draw, or resign.
\r
5975 if (appData.zippyPlay && first.initDone) {
\r
5976 if (result == GameIsDrawn) {
\r
5977 /* In case draw still needs to be claimed */
\r
5978 SendToICS(ics_prefix);
\r
5979 SendToICS("draw\n");
\r
5980 } else if (StrCaseStr(resultDetails, "resign")) {
\r
5981 SendToICS(ics_prefix);
\r
5982 SendToICS("resign\n");
\r
5989 /* If we're loading the game from a file, stop */
\r
5990 if (whosays == GE_FILE) {
\r
5991 (void) StopLoadGameTimer();
\r
5992 gameFileFP = NULL;
\r
5995 /* Cancel draw offers */
\r
5996 first.offeredDraw = second.offeredDraw = 0;
\r
5998 /* If this is an ICS game, only ICS can really say it's done;
\r
5999 if not, anyone can. */
\r
6000 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6001 gameMode == IcsPlayingBlack ||
\r
6002 gameMode == IcsObserving ||
\r
6003 gameMode == IcsExamining);
\r
6005 if (!isIcsGame || whosays == GE_ICS) {
\r
6006 /* OK -- not an ICS game, or ICS said it was done */
\r
6008 if (appData.debugMode) {
\r
6009 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6010 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6012 if (!isIcsGame && !appData.noChessProgram)
\r
6013 SetUserThinkingEnables();
\r
6015 /* [HGM] if a machine claims the game end we verify this claim */
\r
6016 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6017 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6020 if (appData.debugMode) {
\r
6021 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6022 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6024 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6025 first.twoMachinesColor[0] :
\r
6026 second.twoMachinesColor[0] ;
\r
6027 if( result == WhiteWins && claimer == 'w' ||
\r
6028 result == BlackWins && claimer == 'b' ) {
\r
6029 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6030 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6031 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6032 resultDetails = buf;
\r
6034 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {
\r
6035 /* Draw that was not flagged by Xboard is false */
\r
6036 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6037 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6038 resultDetails = buf;
\r
6040 /* (Claiming a loss is accepted no questions asked!) */
\r
6043 if (appData.debugMode) {
\r
6044 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6045 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6047 if (resultDetails != NULL) {
\r
6048 gameInfo.result = result;
\r
6049 gameInfo.resultDetails = StrSave(resultDetails);
\r
6051 /* Tell program how game ended in case it is learning */
\r
6052 if (gameMode == MachinePlaysWhite ||
\r
6053 gameMode == MachinePlaysBlack ||
\r
6054 gameMode == TwoMachinesPlay ||
\r
6055 gameMode == IcsPlayingWhite ||
\r
6056 gameMode == IcsPlayingBlack ||
\r
6057 gameMode == BeginningOfGame) {
\r
6058 char buf[MSG_SIZ];
\r
6059 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
6061 if (first.pr != NoProc) {
\r
6062 SendToProgram(buf, &first);
\r
6064 if (second.pr != NoProc &&
\r
6065 gameMode == TwoMachinesPlay) {
\r
6066 SendToProgram(buf, &second);
\r
6070 /* display last move only if game was not loaded from file */
\r
6071 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
6072 DisplayMove(currentMove - 1);
\r
6074 if (forwardMostMove != 0) {
\r
6075 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
6076 if (*appData.saveGameFile != NULLCHAR) {
\r
6077 SaveGameToFile(appData.saveGameFile, TRUE);
\r
6078 } else if (appData.autoSaveGames) {
\r
6081 if (*appData.savePositionFile != NULLCHAR) {
\r
6082 SavePositionToFile(appData.savePositionFile);
\r
6088 if (appData.icsActive) {
\r
6089 if (appData.quietPlay &&
\r
6090 (gameMode == IcsPlayingWhite ||
\r
6091 gameMode == IcsPlayingBlack)) {
\r
6092 SendToICS(ics_prefix);
\r
6093 SendToICS("set shout 1\n");
\r
6095 nextGameMode = IcsIdle;
\r
6096 ics_user_moved = FALSE;
\r
6097 /* clean up premove. It's ugly when the game has ended and the
\r
6098 * premove highlights are still on the board.
\r
6101 gotPremove = FALSE;
\r
6102 ClearPremoveHighlights();
\r
6103 DrawPosition(FALSE, boards[currentMove]);
\r
6105 if (whosays == GE_ICS) {
\r
6108 if (gameMode == IcsPlayingWhite)
\r
6109 PlayIcsWinSound();
\r
6110 else if(gameMode == IcsPlayingBlack)
\r
6111 PlayIcsLossSound();
\r
6114 if (gameMode == IcsPlayingBlack)
\r
6115 PlayIcsWinSound();
\r
6116 else if(gameMode == IcsPlayingWhite)
\r
6117 PlayIcsLossSound();
\r
6120 PlayIcsDrawSound();
\r
6123 PlayIcsUnfinishedSound();
\r
6126 } else if (gameMode == EditGame ||
\r
6127 gameMode == PlayFromGameFile ||
\r
6128 gameMode == AnalyzeMode ||
\r
6129 gameMode == AnalyzeFile) {
\r
6130 nextGameMode = gameMode;
\r
6132 nextGameMode = EndOfGame;
\r
6137 nextGameMode = gameMode;
\r
6140 if (appData.noChessProgram) {
\r
6141 gameMode = nextGameMode;
\r
6146 if (first.reuse) {
\r
6147 /* Put first chess program into idle state */
\r
6148 if (first.pr != NoProc &&
\r
6149 (gameMode == MachinePlaysWhite ||
\r
6150 gameMode == MachinePlaysBlack ||
\r
6151 gameMode == TwoMachinesPlay ||
\r
6152 gameMode == IcsPlayingWhite ||
\r
6153 gameMode == IcsPlayingBlack ||
\r
6154 gameMode == BeginningOfGame)) {
\r
6155 SendToProgram("force\n", &first);
\r
6156 if (first.usePing) {
\r
6157 char buf[MSG_SIZ];
\r
6158 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
6159 SendToProgram(buf, &first);
\r
6162 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6163 /* Kill off first chess program */
\r
6164 if (first.isr != NULL)
\r
6165 RemoveInputSource(first.isr);
\r
6168 if (first.pr != NoProc) {
\r
6169 ExitAnalyzeMode();
\r
6170 DoSleep( appData.delayBeforeQuit );
\r
6171 SendToProgram("quit\n", &first);
\r
6172 DoSleep( appData.delayAfterQuit );
\r
6173 DestroyChildProcess(first.pr, first.useSigterm);
\r
6175 first.pr = NoProc;
\r
6177 if (second.reuse) {
\r
6178 /* Put second chess program into idle state */
\r
6179 if (second.pr != NoProc &&
\r
6180 gameMode == TwoMachinesPlay) {
\r
6181 SendToProgram("force\n", &second);
\r
6182 if (second.usePing) {
\r
6183 char buf[MSG_SIZ];
\r
6184 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
6185 SendToProgram(buf, &second);
\r
6188 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
6189 /* Kill off second chess program */
\r
6190 if (second.isr != NULL)
\r
6191 RemoveInputSource(second.isr);
\r
6192 second.isr = NULL;
\r
6194 if (second.pr != NoProc) {
\r
6195 DoSleep( appData.delayBeforeQuit );
\r
6196 SendToProgram("quit\n", &second);
\r
6197 DoSleep( appData.delayAfterQuit );
\r
6198 DestroyChildProcess(second.pr, second.useSigterm);
\r
6200 second.pr = NoProc;
\r
6203 if (matchMode && gameMode == TwoMachinesPlay) {
\r
6206 if (first.twoMachinesColor[0] == 'w') {
\r
6207 first.matchWins++;
\r
6209 second.matchWins++;
\r
6213 if (first.twoMachinesColor[0] == 'b') {
\r
6214 first.matchWins++;
\r
6216 second.matchWins++;
\r
6222 if (matchGame < appData.matchGames) {
\r
6224 tmp = first.twoMachinesColor;
\r
6225 first.twoMachinesColor = second.twoMachinesColor;
\r
6226 second.twoMachinesColor = tmp;
\r
6227 gameMode = nextGameMode;
\r
6229 if(appData.matchPause>10000 || appData.matchPause<10)
\r
6230 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
6231 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
6234 char buf[MSG_SIZ];
\r
6235 gameMode = nextGameMode;
\r
6236 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
6237 first.tidy, second.tidy,
\r
6238 first.matchWins, second.matchWins,
\r
6239 appData.matchGames - (first.matchWins + second.matchWins));
\r
6240 DisplayFatalError(buf, 0, 0);
\r
6243 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6244 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
6245 ExitAnalyzeMode();
\r
6246 gameMode = nextGameMode;
\r
6250 /* Assumes program was just initialized (initString sent).
\r
6251 Leaves program in force mode. */
\r
6253 FeedMovesToProgram(cps, upto)
\r
6254 ChessProgramState *cps;
\r
6259 if (appData.debugMode)
\r
6260 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
6261 startedFromSetupPosition ? "position and " : "",
\r
6262 backwardMostMove, upto, cps->which);
\r
6263 SendToProgram("force\n", cps);
\r
6264 if (startedFromSetupPosition) {
\r
6265 SendBoard(cps, backwardMostMove);
\r
6267 for (i = backwardMostMove; i < upto; i++) {
\r
6268 SendMoveToProgram(i, cps);
\r
6274 ResurrectChessProgram()
\r
6276 /* The chess program may have exited.
\r
6277 If so, restart it and feed it all the moves made so far. */
\r
6279 if (appData.noChessProgram || first.pr != NoProc) return;
\r
6281 StartChessProgram(&first);
\r
6282 InitChessProgram(&first);
\r
6283 FeedMovesToProgram(&first, currentMove);
\r
6285 if (!first.sendTime) {
\r
6286 /* can't tell gnuchess what its clock should read,
\r
6287 so we bow to its notion. */
\r
6289 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
6290 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
6293 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
6294 first.analysisSupport) {
\r
6295 SendToProgram("analyze\n", &first);
\r
6296 first.analyzing = TRUE;
\r
6301 * Button procedures
\r
6304 Reset(redraw, init)
\r
6309 if (appData.debugMode) {
\r
6310 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
6311 redraw, init, gameMode);
\r
6314 pausing = pauseExamInvalid = FALSE;
\r
6315 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
6317 whiteFlag = blackFlag = FALSE;
\r
6318 userOfferedDraw = FALSE;
\r
6319 hintRequested = bookRequested = FALSE;
\r
6320 first.maybeThinking = FALSE;
\r
6321 second.maybeThinking = FALSE;
\r
6322 thinkOutput[0] = NULLCHAR;
\r
6323 lastHint[0] = NULLCHAR;
\r
6324 ClearGameInfo(&gameInfo);
\r
6325 gameInfo.variant = StringToVariant(appData.variant);
\r
6326 ics_user_moved = ics_clock_paused = FALSE;
\r
6327 ics_getting_history = H_FALSE;
\r
6329 white_holding[0] = black_holding[0] = NULLCHAR;
\r
6330 ClearProgramStats();
\r
6333 ClearHighlights();
\r
6334 flipView = appData.flipView;
\r
6335 ClearPremoveHighlights();
\r
6336 gotPremove = FALSE;
\r
6337 alarmSounded = FALSE;
\r
6339 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
6340 ExitAnalyzeMode();
\r
6341 gameMode = BeginningOfGame;
\r
6343 InitPosition(redraw);
\r
6344 for (i = 0; i < MAX_MOVES; i++) {
\r
6345 if (commentList[i] != NULL) {
\r
6346 free(commentList[i]);
\r
6347 commentList[i] = NULL;
\r
6351 timeRemaining[0][0] = whiteTimeRemaining;
\r
6352 timeRemaining[1][0] = blackTimeRemaining;
\r
6353 if (first.pr == NULL) {
\r
6354 StartChessProgram(&first);
\r
6356 if (init) InitChessProgram(&first);
\r
6358 DisplayMessage("", "");
\r
6359 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
6363 AutoPlayGameLoop()
\r
6366 if (!AutoPlayOneMove())
\r
6368 if (matchMode || appData.timeDelay == 0)
\r
6370 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
6372 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
6381 int fromX, fromY, toX, toY;
\r
6383 if (appData.debugMode) {
\r
6384 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
6387 if (gameMode != PlayFromGameFile)
\r
6390 if (currentMove >= forwardMostMove) {
\r
6391 gameMode = EditGame;
\r
6394 /* [AS] Clear current move marker at the end of a game */
\r
6395 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
6400 toX = moveList[currentMove][2] - 'a';
\r
6401 toY = moveList[currentMove][3] - ONE;
\r
6403 if (moveList[currentMove][1] == '@') {
\r
6404 if (appData.highlightLastMove) {
\r
6405 SetHighlights(-1, -1, toX, toY);
\r
6408 fromX = moveList[currentMove][0] - 'a';
\r
6409 fromY = moveList[currentMove][1] - ONE;
\r
6411 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
6413 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
6415 if (appData.highlightLastMove) {
\r
6416 SetHighlights(fromX, fromY, toX, toY);
\r
6419 DisplayMove(currentMove);
\r
6420 SendMoveToProgram(currentMove++, &first);
\r
6421 DisplayBothClocks();
\r
6422 DrawPosition(FALSE, boards[currentMove]);
\r
6423 if (commentList[currentMove] != NULL) {
\r
6424 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6431 LoadGameOneMove(readAhead)
\r
6432 ChessMove readAhead;
\r
6434 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
6435 char promoChar = NULLCHAR;
\r
6436 ChessMove moveType;
\r
6437 char move[MSG_SIZ];
\r
6440 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
6441 gameMode != AnalyzeMode && gameMode != Training) {
\r
6442 gameFileFP = NULL;
\r
6446 yyboardindex = forwardMostMove;
\r
6447 if (readAhead != (ChessMove)0) {
\r
6448 moveType = readAhead;
\r
6450 if (gameFileFP == NULL)
\r
6452 moveType = (ChessMove) yylex();
\r
6456 switch (moveType) {
\r
6458 if (appData.debugMode)
\r
6459 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
6461 if (*p == '{' || *p == '[' || *p == '(') {
\r
6462 p[strlen(p) - 1] = NULLCHAR;
\r
6466 /* append the comment but don't display it */
\r
6467 while (*p == '\n') p++;
\r
6468 AppendComment(currentMove, p);
\r
6471 case WhiteCapturesEnPassant:
\r
6472 case BlackCapturesEnPassant:
\r
6474 case WhitePromotionChancellor:
\r
6475 case BlackPromotionChancellor:
\r
6476 case WhitePromotionArchbishop:
\r
6477 case BlackPromotionArchbishop:
\r
6479 case WhitePromotionQueen:
\r
6480 case BlackPromotionQueen:
\r
6481 case WhitePromotionRook:
\r
6482 case BlackPromotionRook:
\r
6483 case WhitePromotionBishop:
\r
6484 case BlackPromotionBishop:
\r
6485 case WhitePromotionKnight:
\r
6486 case BlackPromotionKnight:
\r
6487 case WhitePromotionKing:
\r
6488 case BlackPromotionKing:
\r
6490 case WhiteKingSideCastle:
\r
6491 case WhiteQueenSideCastle:
\r
6492 case BlackKingSideCastle:
\r
6493 case BlackQueenSideCastle:
\r
6494 case WhiteKingSideCastleWild:
\r
6495 case WhiteQueenSideCastleWild:
\r
6496 case BlackKingSideCastleWild:
\r
6497 case BlackQueenSideCastleWild:
\r
6499 case WhiteHSideCastleFR:
\r
6500 case WhiteASideCastleFR:
\r
6501 case BlackHSideCastleFR:
\r
6502 case BlackASideCastleFR:
\r
6504 if (appData.debugMode)
\r
6505 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
6506 fromX = currentMoveString[0] - 'a';
\r
6507 fromY = currentMoveString[1] - ONE;
\r
6508 toX = currentMoveString[2] - 'a';
\r
6509 toY = currentMoveString[3] - ONE;
\r
6510 promoChar = currentMoveString[4];
\r
6515 if (appData.debugMode)
\r
6516 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
6517 fromX = moveType == WhiteDrop ?
\r
6518 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6519 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6520 fromY = DROP_RANK;
\r
6521 toX = currentMoveString[2] - 'a';
\r
6522 toY = currentMoveString[3] - ONE;
\r
6528 case GameUnfinished:
\r
6529 if (appData.debugMode)
\r
6530 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
6531 p = strchr(yy_text, '{');
\r
6532 if (p == NULL) p = strchr(yy_text, '(');
\r
6535 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6537 q = strchr(p, *p == '{' ? '}' : ')');
\r
6538 if (q != NULL) *q = NULLCHAR;
\r
6541 GameEnds(moveType, p, GE_FILE);
\r
6543 if (cmailMsgLoaded) {
\r
6544 ClearHighlights();
\r
6545 flipView = WhiteOnMove(currentMove);
\r
6546 if (moveType == GameUnfinished) flipView = !flipView;
\r
6547 if (appData.debugMode)
\r
6548 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
6552 case (ChessMove) 0: /* end of file */
\r
6553 if (appData.debugMode)
\r
6554 fprintf(debugFP, "Parser hit end of file\n");
\r
6555 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
6556 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
6560 case MT_CHECKMATE:
\r
6561 if (WhiteOnMove(currentMove)) {
\r
6562 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
6564 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
6567 case MT_STALEMATE:
\r
6568 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
6574 case MoveNumberOne:
\r
6575 if (lastLoadGameStart == GNUChessGame) {
\r
6576 /* GNUChessGames have numbers, but they aren't move numbers */
\r
6577 if (appData.debugMode)
\r
6578 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
6579 yy_text, (int) moveType);
\r
6580 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
6582 /* else fall thru */
\r
6585 case GNUChessGame:
\r
6587 /* Reached start of next game in file */
\r
6588 if (appData.debugMode)
\r
6589 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
6590 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
6591 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
6595 case MT_CHECKMATE:
\r
6596 if (WhiteOnMove(currentMove)) {
\r
6597 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
6599 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
6602 case MT_STALEMATE:
\r
6603 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
6609 case PositionDiagram: /* should not happen; ignore */
\r
6610 case ElapsedTime: /* ignore */
\r
6611 case NAG: /* ignore */
\r
6612 if (appData.debugMode)
\r
6613 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
6614 yy_text, (int) moveType);
\r
6615 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
6618 if (appData.testLegality) {
\r
6619 if (appData.debugMode)
\r
6620 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
6621 sprintf(move, "Illegal move: %d.%s%s",
\r
6622 (forwardMostMove / 2) + 1,
\r
6623 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6624 DisplayError(move, 0);
\r
6627 if (appData.debugMode)
\r
6628 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
6629 yy_text, currentMoveString);
\r
6630 fromX = currentMoveString[0] - 'a';
\r
6631 fromY = currentMoveString[1] - ONE;
\r
6632 toX = currentMoveString[2] - 'a';
\r
6633 toY = currentMoveString[3] - ONE;
\r
6634 promoChar = currentMoveString[4];
\r
6638 case AmbiguousMove:
\r
6639 if (appData.debugMode)
\r
6640 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
6641 sprintf(move, "Ambiguous move: %d.%s%s",
\r
6642 (forwardMostMove / 2) + 1,
\r
6643 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6644 DisplayError(move, 0);
\r
6649 case ImpossibleMove:
\r
6650 if (appData.debugMode)
\r
6651 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
6652 sprintf(move, "Illegal move: %d.%s%s",
\r
6653 (forwardMostMove / 2) + 1,
\r
6654 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
6655 DisplayError(move, 0);
\r
6661 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
6662 DrawPosition(FALSE, boards[currentMove]);
\r
6663 DisplayBothClocks();
\r
6664 if (!appData.matchMode && commentList[currentMove] != NULL)
\r
6665 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6667 (void) StopLoadGameTimer();
\r
6668 gameFileFP = NULL;
\r
6669 cmailOldMove = forwardMostMove;
\r
6672 /* currentMoveString is set as a side-effect of yylex */
\r
6673 strcat(currentMoveString, "\n");
\r
6674 strcpy(moveList[forwardMostMove], currentMoveString);
\r
6676 thinkOutput[0] = NULLCHAR;
\r
6677 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
6678 currentMove = forwardMostMove;
\r
6683 /* Load the nth game from the given file */
\r
6685 LoadGameFromFile(filename, n, title, useList)
\r
6689 /*Boolean*/ int useList;
\r
6692 char buf[MSG_SIZ];
\r
6694 if (strcmp(filename, "-") == 0) {
\r
6698 f = fopen(filename, "rb");
\r
6700 sprintf(buf, "Can't open \"%s\"", filename);
\r
6701 DisplayError(buf, errno);
\r
6705 if (fseek(f, 0, 0) == -1) {
\r
6706 /* f is not seekable; probably a pipe */
\r
6709 if (useList && n == 0) {
\r
6710 int error = GameListBuild(f);
\r
6712 DisplayError("Cannot build game list", error);
\r
6713 } else if (!ListEmpty(&gameList) &&
\r
6714 ((ListGame *) gameList.tailPred)->number > 1) {
\r
6715 GameListPopUp(f, title);
\r
6718 GameListDestroy();
\r
6721 if (n == 0) n = 1;
\r
6722 return LoadGame(f, n, title, FALSE);
\r
6727 MakeRegisteredMove()
\r
6729 int fromX, fromY, toX, toY;
\r
6731 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
6732 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
6735 if (appData.debugMode)
\r
6736 fprintf(debugFP, "Restoring %s for game %d\n",
\r
6737 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
6739 thinkOutput[0] = NULLCHAR;
\r
6740 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
6741 fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a';
\r
6742 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
6743 toX = cmailMove[lastLoadGameNumber - 1][2] - 'a';
\r
6744 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
6745 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
6746 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
6747 ShowMove(fromX, fromY, toX, toY);
\r
6749 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
6750 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
6755 case MT_CHECKMATE:
\r
6756 if (WhiteOnMove(currentMove)) {
\r
6757 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
6759 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
6763 case MT_STALEMATE:
\r
6764 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
6770 case CMAIL_RESIGN:
\r
6771 if (WhiteOnMove(currentMove)) {
\r
6772 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
6774 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
6778 case CMAIL_ACCEPT:
\r
6779 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
6790 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
6792 CmailLoadGame(f, gameNumber, title, useList)
\r
6800 if (gameNumber > nCmailGames) {
\r
6801 DisplayError("No more games in this message", 0);
\r
6804 if (f == lastLoadGameFP) {
\r
6805 int offset = gameNumber - lastLoadGameNumber;
\r
6806 if (offset == 0) {
\r
6807 cmailMsg[0] = NULLCHAR;
\r
6808 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
6809 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
6810 nCmailMovesRegistered--;
\r
6812 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
6813 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
6814 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
6817 if (! RegisterMove()) return FALSE;
\r
6821 retVal = LoadGame(f, gameNumber, title, useList);
\r
6823 /* Make move registered during previous look at this game, if any */
\r
6824 MakeRegisteredMove();
\r
6826 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
6827 commentList[currentMove]
\r
6828 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
6829 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
6835 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
6837 ReloadGame(offset)
\r
6840 int gameNumber = lastLoadGameNumber + offset;
\r
6841 if (lastLoadGameFP == NULL) {
\r
6842 DisplayError("No game has been loaded yet", 0);
\r
6845 if (gameNumber <= 0) {
\r
6846 DisplayError("Can't back up any further", 0);
\r
6849 if (cmailMsgLoaded) {
\r
6850 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
6851 lastLoadGameTitle, lastLoadGameUseList);
\r
6853 return LoadGame(lastLoadGameFP, gameNumber,
\r
6854 lastLoadGameTitle, lastLoadGameUseList);
\r
6860 /* Load the nth game from open file f */
\r
6862 LoadGame(f, gameNumber, title, useList)
\r
6869 char buf[MSG_SIZ];
\r
6870 int gn = gameNumber;
\r
6871 ListGame *lg = NULL;
\r
6872 int numPGNTags = 0;
\r
6874 GameMode oldGameMode;
\r
6876 if (appData.debugMode)
\r
6877 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
6879 if (gameMode == Training )
\r
6880 SetTrainingModeOff();
\r
6882 oldGameMode = gameMode;
\r
6883 if (gameMode != BeginningOfGame) {
\r
6884 Reset(FALSE, TRUE);
\r
6888 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
6889 fclose(lastLoadGameFP);
\r
6893 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
6896 fseek(f, lg->offset, 0);
\r
6897 GameListHighlight(gameNumber);
\r
6901 DisplayError("Game number out of range", 0);
\r
6905 GameListDestroy();
\r
6906 if (fseek(f, 0, 0) == -1) {
\r
6907 if (f == lastLoadGameFP ?
\r
6908 gameNumber == lastLoadGameNumber + 1 :
\r
6909 gameNumber == 1) {
\r
6912 DisplayError("Can't seek on game file", 0);
\r
6917 lastLoadGameFP = f;
\r
6918 lastLoadGameNumber = gameNumber;
\r
6919 strcpy(lastLoadGameTitle, title);
\r
6920 lastLoadGameUseList = useList;
\r
6925 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
6926 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
6927 lg->gameInfo.black);
\r
6928 DisplayTitle(buf);
\r
6929 } else if (*title != NULLCHAR) {
\r
6930 if (gameNumber > 1) {
\r
6931 sprintf(buf, "%s %d", title, gameNumber);
\r
6932 DisplayTitle(buf);
\r
6934 DisplayTitle(title);
\r
6938 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
6939 gameMode = PlayFromGameFile;
\r
6943 currentMove = forwardMostMove = backwardMostMove = 0;
\r
6944 CopyBoard(boards[0], initialPosition);
\r
6948 * Skip the first gn-1 games in the file.
\r
6949 * Also skip over anything that precedes an identifiable
\r
6950 * start of game marker, to avoid being confused by
\r
6951 * garbage at the start of the file. Currently
\r
6952 * recognized start of game markers are the move number "1",
\r
6953 * the pattern "gnuchess .* game", the pattern
\r
6954 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
6955 * A game that starts with one of the latter two patterns
\r
6956 * will also have a move number 1, possibly
\r
6957 * following a position diagram.
\r
6958 * 5-4-02: Let's try being more lenient and allowing a game to
\r
6959 * start with an unnumbered move. Does that break anything?
\r
6961 cm = lastLoadGameStart = (ChessMove) 0;
\r
6963 yyboardindex = forwardMostMove;
\r
6964 cm = (ChessMove) yylex();
\r
6966 case (ChessMove) 0:
\r
6967 if (cmailMsgLoaded) {
\r
6968 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
6970 Reset(TRUE, TRUE);
\r
6971 DisplayError("Game not found in file", 0);
\r
6975 case GNUChessGame:
\r
6978 lastLoadGameStart = cm;
\r
6981 case MoveNumberOne:
\r
6982 switch (lastLoadGameStart) {
\r
6983 case GNUChessGame:
\r
6987 case MoveNumberOne:
\r
6988 case (ChessMove) 0:
\r
6989 gn--; /* count this game */
\r
6990 lastLoadGameStart = cm;
\r
6999 switch (lastLoadGameStart) {
\r
7000 case GNUChessGame:
\r
7002 case MoveNumberOne:
\r
7003 case (ChessMove) 0:
\r
7004 gn--; /* count this game */
\r
7005 lastLoadGameStart = cm;
\r
7008 lastLoadGameStart = cm; /* game counted already */
\r
7016 yyboardindex = forwardMostMove;
\r
7017 cm = (ChessMove) yylex();
\r
7018 } while (cm == PGNTag || cm == Comment);
\r
7025 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
7026 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
7027 != CMAIL_OLD_RESULT) {
\r
7028 nCmailResults ++ ;
\r
7029 cmailResult[ CMAIL_MAX_GAMES
\r
7030 - gn - 1] = CMAIL_OLD_RESULT;
\r
7036 /* Only a NormalMove can be at the start of a game
\r
7037 * without a position diagram. */
\r
7038 if (lastLoadGameStart == (ChessMove) 0) {
\r
7040 lastLoadGameStart = MoveNumberOne;
\r
7049 if (appData.debugMode)
\r
7050 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
7052 if (cm == XBoardGame) {
\r
7053 /* Skip any header junk before position diagram and/or move 1 */
\r
7055 yyboardindex = forwardMostMove;
\r
7056 cm = (ChessMove) yylex();
\r
7058 if (cm == (ChessMove) 0 ||
\r
7059 cm == GNUChessGame || cm == XBoardGame) {
\r
7060 /* Empty game; pretend end-of-file and handle later */
\r
7061 cm = (ChessMove) 0;
\r
7065 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
7066 cm == PGNTag || cm == Comment)
\r
7069 } else if (cm == GNUChessGame) {
\r
7070 if (gameInfo.event != NULL) {
\r
7071 free(gameInfo.event);
\r
7073 gameInfo.event = StrSave(yy_text);
\r
7076 startedFromSetupPosition = FALSE;
\r
7077 while (cm == PGNTag) {
\r
7078 if (appData.debugMode)
\r
7079 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
7080 err = ParsePGNTag(yy_text, &gameInfo);
\r
7081 if (!err) numPGNTags++;
\r
7083 if (gameInfo.fen != NULL) {
\r
7084 Board initial_position;
\r
7085 startedFromSetupPosition = TRUE;
\r
7086 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
7087 Reset(TRUE, TRUE);
\r
7088 DisplayError("Bad FEN position in file", 0);
\r
7091 CopyBoard(boards[0], initial_position);
\r
7092 /* [HGM] copy FEN attributes as well */
\r
7094 initialRulePlies = FENrulePlies;
\r
7095 epStatus[0] = FENepStatus;
\r
7096 for( i=0; i< nrCastlingRights; i++ )
\r
7097 castlingRights[0][i] = FENcastlingRights[i];
\r
7099 if (blackPlaysFirst) {
\r
7100 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7101 CopyBoard(boards[1], initial_position);
\r
7102 strcpy(moveList[0], "");
\r
7103 strcpy(parseList[0], "");
\r
7104 timeRemaining[0][1] = whiteTimeRemaining;
\r
7105 timeRemaining[1][1] = blackTimeRemaining;
\r
7106 if (commentList[0] != NULL) {
\r
7107 commentList[1] = commentList[0];
\r
7108 commentList[0] = NULL;
\r
7111 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7113 yyboardindex = forwardMostMove;
\r
7114 free(gameInfo.fen);
\r
7115 gameInfo.fen = NULL;
\r
7118 yyboardindex = forwardMostMove;
\r
7119 cm = (ChessMove) yylex();
\r
7121 /* Handle comments interspersed among the tags */
\r
7122 while (cm == Comment) {
\r
7124 if (appData.debugMode)
\r
7125 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7127 if (*p == '{' || *p == '[' || *p == '(') {
\r
7128 p[strlen(p) - 1] = NULLCHAR;
\r
7131 while (*p == '\n') p++;
\r
7132 AppendComment(currentMove, p);
\r
7133 yyboardindex = forwardMostMove;
\r
7134 cm = (ChessMove) yylex();
\r
7138 /* don't rely on existence of Event tag since if game was
\r
7139 * pasted from clipboard the Event tag may not exist
\r
7141 if (numPGNTags > 0){
\r
7143 if (gameInfo.variant == VariantNormal) {
\r
7144 gameInfo.variant = StringToVariant(gameInfo.event);
\r
7147 if( appData.autoDisplayTags ) {
\r
7148 tags = PGNTags(&gameInfo);
\r
7149 TagsPopUp(tags, CmailMsg());
\r
7154 /* Make something up, but don't display it now */
\r
7159 if (cm == PositionDiagram) {
\r
7162 Board initial_position;
\r
7164 if (appData.debugMode)
\r
7165 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
7167 if (!startedFromSetupPosition) {
\r
7169 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
7170 for (j = 0; j < BOARD_WIDTH; p++)
\r
7180 initial_position[i][j++] = CharToPiece(*p);
\r
7183 while (*p == ' ' || *p == '\t' ||
\r
7184 *p == '\n' || *p == '\r') p++;
\r
7186 if (strncmp(p, "black", strlen("black"))==0)
\r
7187 blackPlaysFirst = TRUE;
\r
7189 blackPlaysFirst = FALSE;
\r
7190 startedFromSetupPosition = TRUE;
\r
7192 CopyBoard(boards[0], initial_position);
\r
7193 if (blackPlaysFirst) {
\r
7194 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7195 CopyBoard(boards[1], initial_position);
\r
7196 strcpy(moveList[0], "");
\r
7197 strcpy(parseList[0], "");
\r
7198 timeRemaining[0][1] = whiteTimeRemaining;
\r
7199 timeRemaining[1][1] = blackTimeRemaining;
\r
7200 if (commentList[0] != NULL) {
\r
7201 commentList[1] = commentList[0];
\r
7202 commentList[0] = NULL;
\r
7205 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7208 yyboardindex = forwardMostMove;
\r
7209 cm = (ChessMove) yylex();
\r
7212 if (first.pr == NoProc) {
\r
7213 StartChessProgram(&first);
\r
7215 InitChessProgram(&first);
\r
7216 SendToProgram("force\n", &first);
\r
7217 if (startedFromSetupPosition) {
\r
7218 SendBoard(&first, forwardMostMove);
\r
7219 DisplayBothClocks();
\r
7222 while (cm == Comment) {
\r
7224 if (appData.debugMode)
\r
7225 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7227 if (*p == '{' || *p == '[' || *p == '(') {
\r
7228 p[strlen(p) - 1] = NULLCHAR;
\r
7231 while (*p == '\n') p++;
\r
7232 AppendComment(currentMove, p);
\r
7233 yyboardindex = forwardMostMove;
\r
7234 cm = (ChessMove) yylex();
\r
7237 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
7238 cm == WhiteWins || cm == BlackWins ||
\r
7239 cm == GameIsDrawn || cm == GameUnfinished) {
\r
7240 DisplayMessage("", "No moves in game");
\r
7241 if (cmailMsgLoaded) {
\r
7242 if (appData.debugMode)
\r
7243 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
7244 ClearHighlights();
\r
7247 DrawPosition(FALSE, boards[currentMove]);
\r
7248 DisplayBothClocks();
\r
7249 gameMode = EditGame;
\r
7251 gameFileFP = NULL;
\r
7256 if (commentList[currentMove] != NULL) {
\r
7257 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
7258 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7261 if (!matchMode && appData.timeDelay != 0)
\r
7262 DrawPosition(FALSE, boards[currentMove]);
\r
7264 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
7265 programStats.ok_to_send = 1;
\r
7268 /* if the first token after the PGN tags is a move
\r
7269 * and not move number 1, retrieve it from the parser
\r
7271 if (cm != MoveNumberOne)
\r
7272 LoadGameOneMove(cm);
\r
7274 /* load the remaining moves from the file */
\r
7275 while (LoadGameOneMove((ChessMove)0)) {
\r
7276 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7277 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7280 /* rewind to the start of the game */
\r
7281 currentMove = backwardMostMove;
\r
7283 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7285 if (oldGameMode == AnalyzeFile ||
\r
7286 oldGameMode == AnalyzeMode) {
\r
7287 AnalyzeFileEvent();
\r
7290 if (matchMode || appData.timeDelay == 0) {
\r
7292 gameMode = EditGame;
\r
7294 } else if (appData.timeDelay > 0) {
\r
7295 AutoPlayGameLoop();
\r
7298 if (appData.debugMode)
\r
7299 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
7303 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
7305 ReloadPosition(offset)
\r
7308 int positionNumber = lastLoadPositionNumber + offset;
\r
7309 if (lastLoadPositionFP == NULL) {
\r
7310 DisplayError("No position has been loaded yet", 0);
\r
7313 if (positionNumber <= 0) {
\r
7314 DisplayError("Can't back up any further", 0);
\r
7317 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
7318 lastLoadPositionTitle);
\r
7321 /* Load the nth position from the given file */
\r
7323 LoadPositionFromFile(filename, n, title)
\r
7329 char buf[MSG_SIZ];
\r
7331 if (strcmp(filename, "-") == 0) {
\r
7332 return LoadPosition(stdin, n, "stdin");
\r
7334 f = fopen(filename, "rb");
\r
7336 sprintf(buf, "Can't open \"%s\"", filename);
\r
7337 DisplayError(buf, errno);
\r
7340 return LoadPosition(f, n, title);
\r
7345 /* Load the nth position from the given open file, and close it */
\r
7347 LoadPosition(f, positionNumber, title)
\r
7349 int positionNumber;
\r
7352 char *p, line[MSG_SIZ];
\r
7353 Board initial_position;
\r
7354 int i, j, fenMode, pn;
\r
7356 if (gameMode == Training )
\r
7357 SetTrainingModeOff();
\r
7359 if (gameMode != BeginningOfGame) {
\r
7360 Reset(FALSE, TRUE);
\r
7362 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
7363 fclose(lastLoadPositionFP);
\r
7365 if (positionNumber == 0) positionNumber = 1;
\r
7366 lastLoadPositionFP = f;
\r
7367 lastLoadPositionNumber = positionNumber;
\r
7368 strcpy(lastLoadPositionTitle, title);
\r
7369 if (first.pr == NoProc) {
\r
7370 StartChessProgram(&first);
\r
7371 InitChessProgram(&first);
\r
7373 pn = positionNumber;
\r
7374 if (positionNumber < 0) {
\r
7375 /* Negative position number means to seek to that byte offset */
\r
7376 if (fseek(f, -positionNumber, 0) == -1) {
\r
7377 DisplayError("Can't seek on position file", 0);
\r
7382 if (fseek(f, 0, 0) == -1) {
\r
7383 if (f == lastLoadPositionFP ?
\r
7384 positionNumber == lastLoadPositionNumber + 1 :
\r
7385 positionNumber == 1) {
\r
7388 DisplayError("Can't seek on position file", 0);
\r
7393 /* See if this file is FEN or old-style xboard */
\r
7394 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7395 DisplayError("Position not found in file", 0);
\r
7398 switch (line[0]) {
\r
7399 case '#': case 'x':
\r
7403 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
7404 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
7405 case '1': case '2': case '3': case '4': case '5': case '6':
\r
7406 case '7': case '8': case '9':
\r
7408 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
7409 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
7410 case 'C': case 'W': case 'c': case 'w':
\r
7417 if (fenMode || line[0] == '#') pn--;
\r
7419 /* skip postions before number pn */
\r
7420 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
7421 Reset(TRUE, TRUE);
\r
7422 DisplayError("Position not found in file", 0);
\r
7425 if (fenMode || line[0] == '#') pn--;
\r
7430 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
7431 DisplayError("Bad FEN position in file", 0);
\r
7435 (void) fgets(line, MSG_SIZ, f);
\r
7436 (void) fgets(line, MSG_SIZ, f);
\r
7438 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
7439 (void) fgets(line, MSG_SIZ, f);
\r
7440 for (p = line, j = 0; j < BOARD_WIDTH; p++) {
\r
7443 initial_position[i][j++] = CharToPiece(*p);
\r
7447 blackPlaysFirst = FALSE;
\r
7449 (void) fgets(line, MSG_SIZ, f);
\r
7450 if (strncmp(line, "black", strlen("black"))==0)
\r
7451 blackPlaysFirst = TRUE;
\r
7454 startedFromSetupPosition = TRUE;
\r
7456 SendToProgram("force\n", &first);
\r
7457 CopyBoard(boards[0], initial_position);
\r
7458 /* [HGM] copy FEN attributes as well */
\r
7460 initialRulePlies = FENrulePlies;
\r
7461 epStatus[0] = FENepStatus;
\r
7462 for( i=0; i< nrCastlingRights; i++ )
\r
7463 castlingRights[0][i] = FENcastlingRights[i];
\r
7465 if (blackPlaysFirst) {
\r
7466 currentMove = forwardMostMove = backwardMostMove = 1;
\r
7467 strcpy(moveList[0], "");
\r
7468 strcpy(parseList[0], "");
\r
7469 CopyBoard(boards[1], initial_position);
\r
7470 DisplayMessage("", "Black to play");
\r
7472 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7473 DisplayMessage("", "White to play");
\r
7475 SendBoard(&first, forwardMostMove);
\r
7477 if (positionNumber > 1) {
\r
7478 sprintf(line, "%s %d", title, positionNumber);
\r
7479 DisplayTitle(line);
\r
7481 DisplayTitle(title);
\r
7483 gameMode = EditGame;
\r
7486 timeRemaining[0][1] = whiteTimeRemaining;
\r
7487 timeRemaining[1][1] = blackTimeRemaining;
\r
7488 DrawPosition(FALSE, boards[currentMove]);
\r
7495 CopyPlayerNameIntoFileName(dest, src)
\r
7496 char **dest, *src;
\r
7498 while (*src != NULLCHAR && *src != ',') {
\r
7499 if (*src == ' ') {
\r
7503 *(*dest)++ = *src++;
\r
7508 char *DefaultFileName(ext)
\r
7511 static char def[MSG_SIZ];
\r
7514 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
7516 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
7518 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
7522 def[0] = NULLCHAR;
\r
7527 /* Save the current game to the given file */
\r
7529 SaveGameToFile(filename, append)
\r
7534 char buf[MSG_SIZ];
\r
7536 if (strcmp(filename, "-") == 0) {
\r
7537 return SaveGame(stdout, 0, NULL);
\r
7539 f = fopen(filename, append ? "a" : "w");
\r
7541 sprintf(buf, "Can't open \"%s\"", filename);
\r
7542 DisplayError(buf, errno);
\r
7545 return SaveGame(f, 0, NULL);
\r
7554 static char buf[MSG_SIZ];
\r
7557 p = strchr(str, ' ');
\r
7558 if (p == NULL) return str;
\r
7559 strncpy(buf, str, p - str);
\r
7560 buf[p - str] = NULLCHAR;
\r
7564 #define PGN_MAX_LINE 75
\r
7566 #define PGN_SIDE_WHITE 0
\r
7567 #define PGN_SIDE_BLACK 1
\r
7570 static int FindFirstMoveOutOfBook( int side )
\r
7574 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
7575 int index = backwardMostMove;
\r
7576 int has_book_hit = 0;
\r
7578 if( (index % 2) != side ) {
\r
7582 while( index < forwardMostMove ) {
\r
7583 /* Check to see if engine is in book */
\r
7584 int depth = pvInfoList[index].depth;
\r
7585 int score = pvInfoList[index].score;
\r
7588 if( depth <= 2 ) {
\r
7591 else if( score == 0 && depth == 63 ) {
\r
7592 in_book = 1; /* Zappa */
\r
7594 else if( score == 2 && depth == 99 ) {
\r
7595 in_book = 1; /* Abrok */
\r
7598 has_book_hit += in_book;
\r
7614 void GetOutOfBookInfo( char * buf )
\r
7618 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
7620 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
7621 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
7625 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
7626 for( i=0; i<2; i++ ) {
\r
7630 if( i > 0 && oob[0] >= 0 ) {
\r
7631 strcat( buf, " " );
\r
7634 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
7635 sprintf( buf+strlen(buf), "%s%.2f",
\r
7636 pvInfoList[idx].score >= 0 ? "+" : "",
\r
7637 pvInfoList[idx].score / 100.0 );
\r
7643 /* Save game in PGN style and close the file */
\r
7648 int i, offset, linelen, newblock;
\r
7652 int movelen, numlen, blank;
\r
7653 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
7655 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
7657 tm = time((time_t *) NULL);
\r
7659 PrintPGNTags(f, &gameInfo);
\r
7661 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
7662 char *fen = PositionToFEN(backwardMostMove, 1);
\r
7663 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
7664 fprintf(f, "\n{--------------\n");
\r
7665 PrintPosition(f, backwardMostMove);
\r
7666 fprintf(f, "--------------}\n");
\r
7670 /* [AS] Out of book annotation */
\r
7671 if( appData.saveOutOfBookInfo ) {
\r
7674 GetOutOfBookInfo( buf );
\r
7676 if( buf[0] != '\0' ) {
\r
7677 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
7684 i = backwardMostMove;
\r
7688 while (i < forwardMostMove) {
\r
7689 /* Print comments preceding this move */
\r
7690 if (commentList[i] != NULL) {
\r
7691 if (linelen > 0) fprintf(f, "\n");
\r
7692 fprintf(f, "{\n%s}\n", commentList[i]);
\r
7697 /* Format move number */
\r
7698 if ((i % 2) == 0) {
\r
7699 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
7702 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
7704 numtext[0] = NULLCHAR;
\r
7707 numlen = strlen(numtext);
\r
7710 /* Print move number */
\r
7711 blank = linelen > 0 && numlen > 0;
\r
7712 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
7721 fprintf(f, numtext);
\r
7722 linelen += numlen;
\r
7725 movetext = SavePart(parseList[i]);
\r
7727 /* [AS] Add PV info if present */
\r
7728 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
7729 /* [HGM] add time */
\r
7730 char buf[MSG_SIZ]; int seconds = 0;
\r
7732 if(i >= backwardMostMove) {
\r
7733 /* take the time that changed */
\r
7734 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
7736 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
7739 if (appData.debugMode) {
\r
7740 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
7741 timeRemaining[0][i+1], timeRemaining[0][i],
\r
7742 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
7746 if( seconds < 0 ) buf[0] = 0; else
\r
7747 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
7748 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
7750 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
7752 pvInfoList[i].score >= 0 ? "+" : "",
\r
7753 pvInfoList[i].score / 100.0,
\r
7754 pvInfoList[i].depth,
\r
7756 movetext = move_buffer;
\r
7759 movelen = strlen(movetext);
\r
7762 blank = linelen > 0 && movelen > 0;
\r
7763 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
7772 fprintf(f, movetext);
\r
7773 linelen += movelen;
\r
7778 /* Start a new line */
\r
7779 if (linelen > 0) fprintf(f, "\n");
\r
7781 /* Print comments after last move */
\r
7782 if (commentList[i] != NULL) {
\r
7783 fprintf(f, "{\n%s}\n", commentList[i]);
\r
7786 /* Print result */
\r
7787 if (gameInfo.resultDetails != NULL &&
\r
7788 gameInfo.resultDetails[0] != NULLCHAR) {
\r
7789 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
7790 PGNResult(gameInfo.result));
\r
7792 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
7799 /* Save game in old style and close the file */
\r
7801 SaveGameOldStyle(f)
\r
7807 tm = time((time_t *) NULL);
\r
7809 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
7810 PrintOpponents(f);
\r
7812 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
7813 fprintf(f, "\n[--------------\n");
\r
7814 PrintPosition(f, backwardMostMove);
\r
7815 fprintf(f, "--------------]\n");
\r
7820 i = backwardMostMove;
\r
7821 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
7823 while (i < forwardMostMove) {
\r
7824 if (commentList[i] != NULL) {
\r
7825 fprintf(f, "[%s]\n", commentList[i]);
\r
7828 if ((i % 2) == 1) {
\r
7829 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
7832 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
7834 if (commentList[i] != NULL) {
\r
7838 if (i >= forwardMostMove) {
\r
7842 fprintf(f, "%s\n", parseList[i]);
\r
7847 if (commentList[i] != NULL) {
\r
7848 fprintf(f, "[%s]\n", commentList[i]);
\r
7851 /* This isn't really the old style, but it's close enough */
\r
7852 if (gameInfo.resultDetails != NULL &&
\r
7853 gameInfo.resultDetails[0] != NULLCHAR) {
\r
7854 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
7855 gameInfo.resultDetails);
\r
7857 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
7864 /* Save the current game to open file f and close the file */
\r
7866 SaveGame(f, dummy, dummy2)
\r
7871 if (gameMode == EditPosition) EditPositionDone();
\r
7872 if (appData.oldSaveStyle)
\r
7873 return SaveGameOldStyle(f);
\r
7875 return SaveGamePGN(f);
\r
7878 /* Save the current position to the given file */
\r
7880 SavePositionToFile(filename)
\r
7884 char buf[MSG_SIZ];
\r
7886 if (strcmp(filename, "-") == 0) {
\r
7887 return SavePosition(stdout, 0, NULL);
\r
7889 f = fopen(filename, "a");
\r
7891 sprintf(buf, "Can't open \"%s\"", filename);
\r
7892 DisplayError(buf, errno);
\r
7895 SavePosition(f, 0, NULL);
\r
7901 /* Save the current position to the given open file and close the file */
\r
7903 SavePosition(f, dummy, dummy2)
\r
7911 if (appData.oldSaveStyle) {
\r
7912 tm = time((time_t *) NULL);
\r
7914 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
7915 PrintOpponents(f);
\r
7916 fprintf(f, "[--------------\n");
\r
7917 PrintPosition(f, currentMove);
\r
7918 fprintf(f, "--------------]\n");
\r
7920 fen = PositionToFEN(currentMove, 1);
\r
7921 fprintf(f, "%s\n", fen);
\r
7929 ReloadCmailMsgEvent(unregister)
\r
7933 static char *inFilename = NULL;
\r
7934 static char *outFilename;
\r
7936 struct stat inbuf, outbuf;
\r
7939 /* Any registered moves are unregistered if unregister is set, */
\r
7940 /* i.e. invoked by the signal handler */
\r
7942 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
7943 cmailMoveRegistered[i] = FALSE;
\r
7944 if (cmailCommentList[i] != NULL) {
\r
7945 free(cmailCommentList[i]);
\r
7946 cmailCommentList[i] = NULL;
\r
7949 nCmailMovesRegistered = 0;
\r
7952 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
7953 cmailResult[i] = CMAIL_NOT_RESULT;
\r
7955 nCmailResults = 0;
\r
7957 if (inFilename == NULL) {
\r
7958 /* Because the filenames are static they only get malloced once */
\r
7959 /* and they never get freed */
\r
7960 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
7961 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
7963 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
7964 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
7967 status = stat(outFilename, &outbuf);
\r
7969 cmailMailedMove = FALSE;
\r
7971 status = stat(inFilename, &inbuf);
\r
7972 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
7975 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
7976 counts the games, notes how each one terminated, etc.
\r
7978 It would be nice to remove this kludge and instead gather all
\r
7979 the information while building the game list. (And to keep it
\r
7980 in the game list nodes instead of having a bunch of fixed-size
\r
7981 parallel arrays.) Note this will require getting each game's
\r
7982 termination from the PGN tags, as the game list builder does
\r
7983 not process the game moves. --mann
\r
7985 cmailMsgLoaded = TRUE;
\r
7986 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
7988 /* Load first game in the file or popup game menu */
\r
7989 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
7991 #endif /* !WIN32 */
\r
7999 char string[MSG_SIZ];
\r
8001 if ( cmailMailedMove
\r
8002 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
8003 return TRUE; /* Allow free viewing */
\r
8006 /* Unregister move to ensure that we don't leave RegisterMove */
\r
8007 /* with the move registered when the conditions for registering no */
\r
8009 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8010 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8011 nCmailMovesRegistered --;
\r
8013 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
8015 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
8016 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
8020 if (cmailOldMove == -1) {
\r
8021 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
8025 if (currentMove > cmailOldMove + 1) {
\r
8026 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
8030 if (currentMove < cmailOldMove) {
\r
8031 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
8035 if (forwardMostMove > currentMove) {
\r
8036 /* Silently truncate extra moves */
\r
8040 if ( (currentMove == cmailOldMove + 1)
\r
8041 || ( (currentMove == cmailOldMove)
\r
8042 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
8043 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
8044 if (gameInfo.result != GameUnfinished) {
\r
8045 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
8048 if (commentList[currentMove] != NULL) {
\r
8049 cmailCommentList[lastLoadGameNumber - 1]
\r
8050 = StrSave(commentList[currentMove]);
\r
8052 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
8054 if (appData.debugMode)
\r
8055 fprintf(debugFP, "Saving %s for game %d\n",
\r
8056 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8059 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
8061 f = fopen(string, "w");
\r
8062 if (appData.oldSaveStyle) {
\r
8063 SaveGameOldStyle(f); /* also closes the file */
\r
8065 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
8066 f = fopen(string, "w");
\r
8067 SavePosition(f, 0, NULL); /* also closes the file */
\r
8069 fprintf(f, "{--------------\n");
\r
8070 PrintPosition(f, currentMove);
\r
8071 fprintf(f, "--------------}\n\n");
\r
8073 SaveGame(f, 0, NULL); /* also closes the file*/
\r
8076 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
8077 nCmailMovesRegistered ++;
\r
8078 } else if (nCmailGames == 1) {
\r
8079 DisplayError("You have not made a move yet", 0);
\r
8090 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
8091 FILE *commandOutput;
\r
8092 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
8093 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
8099 if (! cmailMsgLoaded) {
\r
8100 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
8104 if (nCmailGames == nCmailResults) {
\r
8105 DisplayError("No unfinished games", 0);
\r
8109 #if CMAIL_PROHIBIT_REMAIL
\r
8110 if (cmailMailedMove) {
\r
8111 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
8112 DisplayError(msg, 0);
\r
8117 if (! (cmailMailedMove || RegisterMove())) return;
\r
8119 if ( cmailMailedMove
\r
8120 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
8121 sprintf(string, partCommandString,
\r
8122 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
8123 commandOutput = popen(string, "rb");
\r
8125 if (commandOutput == NULL) {
\r
8126 DisplayError("Failed to invoke cmail", 0);
\r
8128 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
8129 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
8131 if (nBuffers > 1) {
\r
8132 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
8133 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
8134 nBytes = MSG_SIZ - 1;
\r
8136 (void) memcpy(msg, buffer, nBytes);
\r
8138 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
8140 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
8141 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
8144 for (i = 0; i < nCmailGames; i ++) {
\r
8145 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
8150 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
8152 sprintf(buffer, "%s/%s.%s.archive",
\r
8154 appData.cmailGameName,
\r
8156 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
8157 cmailMsgLoaded = FALSE;
\r
8161 DisplayInformation(msg);
\r
8162 pclose(commandOutput);
\r
8165 if ((*cmailMsg) != '\0') {
\r
8166 DisplayInformation(cmailMsg);
\r
8171 #endif /* !WIN32 */
\r
8180 int prependComma = 0;
\r
8182 char string[MSG_SIZ]; /* Space for game-list */
\r
8185 if (!cmailMsgLoaded) return "";
\r
8187 if (cmailMailedMove) {
\r
8188 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
8190 /* Create a list of games left */
\r
8191 sprintf(string, "[");
\r
8192 for (i = 0; i < nCmailGames; i ++) {
\r
8193 if (! ( cmailMoveRegistered[i]
\r
8194 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
8195 if (prependComma) {
\r
8196 sprintf(number, ",%d", i + 1);
\r
8198 sprintf(number, "%d", i + 1);
\r
8202 strcat(string, number);
\r
8205 strcat(string, "]");
\r
8207 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
8208 switch (nCmailGames) {
\r
8211 "Still need to make move for game\n");
\r
8216 "Still need to make moves for both games\n");
\r
8221 "Still need to make moves for all %d games\n",
\r
8226 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
8229 "Still need to make a move for game %s\n",
\r
8234 if (nCmailResults == nCmailGames) {
\r
8235 sprintf(cmailMsg, "No unfinished games\n");
\r
8237 sprintf(cmailMsg, "Ready to send mail\n");
\r
8243 "Still need to make moves for games %s\n",
\r
8249 #endif /* WIN32 */
\r
8255 if (gameMode == Training)
\r
8256 SetTrainingModeOff();
\r
8258 Reset(TRUE, TRUE);
\r
8259 cmailMsgLoaded = FALSE;
\r
8260 if (appData.icsActive) {
\r
8261 SendToICS(ics_prefix);
\r
8262 SendToICS("refresh\n");
\r
8266 static int exiting = 0;
\r
8273 if (exiting > 2) {
\r
8274 /* Give up on clean exit */
\r
8277 if (exiting > 1) {
\r
8278 /* Keep trying for clean exit */
\r
8282 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
8284 if (telnetISR != NULL) {
\r
8285 RemoveInputSource(telnetISR);
\r
8287 if (icsPR != NoProc) {
\r
8288 DestroyChildProcess(icsPR, TRUE);
\r
8290 /* Save game if resource set and not already saved by GameEnds() */
\r
8291 if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {
\r
8292 if (*appData.saveGameFile != NULLCHAR) {
\r
8293 SaveGameToFile(appData.saveGameFile, TRUE);
\r
8294 } else if (appData.autoSaveGames) {
\r
8297 if (*appData.savePositionFile != NULLCHAR) {
\r
8298 SavePositionToFile(appData.savePositionFile);
\r
8301 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8303 /* Kill off chess programs */
\r
8304 if (first.pr != NoProc) {
\r
8305 ExitAnalyzeMode();
\r
8307 DoSleep( appData.delayBeforeQuit );
\r
8308 SendToProgram("quit\n", &first);
\r
8309 DoSleep( appData.delayAfterQuit );
\r
8310 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
8312 if (second.pr != NoProc) {
\r
8313 DoSleep( appData.delayBeforeQuit );
\r
8314 SendToProgram("quit\n", &second);
\r
8315 DoSleep( appData.delayAfterQuit );
\r
8316 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
8318 if (first.isr != NULL) {
\r
8319 RemoveInputSource(first.isr);
\r
8321 if (second.isr != NULL) {
\r
8322 RemoveInputSource(second.isr);
\r
8325 ShutDownFrontEnd();
\r
8332 if (appData.debugMode)
\r
8333 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
8337 if (gameMode == MachinePlaysWhite ||
\r
8338 gameMode == MachinePlaysBlack) {
\r
8341 DisplayBothClocks();
\r
8343 if (gameMode == PlayFromGameFile) {
\r
8344 if (appData.timeDelay >= 0)
\r
8345 AutoPlayGameLoop();
\r
8346 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
8347 Reset(FALSE, TRUE);
\r
8348 SendToICS(ics_prefix);
\r
8349 SendToICS("refresh\n");
\r
8350 } else if (currentMove < forwardMostMove) {
\r
8351 ForwardInner(forwardMostMove);
\r
8353 pauseExamInvalid = FALSE;
\r
8355 switch (gameMode) {
\r
8358 case IcsExamining:
\r
8359 pauseExamForwardMostMove = forwardMostMove;
\r
8360 pauseExamInvalid = FALSE;
\r
8361 /* fall through */
\r
8362 case IcsObserving:
\r
8363 case IcsPlayingWhite:
\r
8364 case IcsPlayingBlack:
\r
8368 case PlayFromGameFile:
\r
8369 (void) StopLoadGameTimer();
\r
8373 case BeginningOfGame:
\r
8374 if (appData.icsActive) return;
\r
8375 /* else fall through */
\r
8376 case MachinePlaysWhite:
\r
8377 case MachinePlaysBlack:
\r
8378 case TwoMachinesPlay:
\r
8379 if (forwardMostMove == 0)
\r
8380 return; /* don't pause if no one has moved */
\r
8381 if ((gameMode == MachinePlaysWhite &&
\r
8382 !WhiteOnMove(forwardMostMove)) ||
\r
8383 (gameMode == MachinePlaysBlack &&
\r
8384 WhiteOnMove(forwardMostMove))) {
\r
8395 EditCommentEvent()
\r
8397 char title[MSG_SIZ];
\r
8399 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
8400 strcpy(title, "Edit comment");
\r
8402 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
8403 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
8404 parseList[currentMove - 1]);
\r
8407 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
8414 char *tags = PGNTags(&gameInfo);
\r
8415 EditTagsPopUp(tags);
\r
8420 AnalyzeModeEvent()
\r
8422 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
8425 if (gameMode != AnalyzeFile) {
\r
8427 if (gameMode != EditGame) return;
\r
8428 ResurrectChessProgram();
\r
8429 SendToProgram("analyze\n", &first);
\r
8430 first.analyzing = TRUE;
\r
8431 /*first.maybeThinking = TRUE;*/
\r
8432 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8433 AnalysisPopUp("Analysis",
\r
8434 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8436 gameMode = AnalyzeMode;
\r
8441 StartAnalysisClock();
\r
8442 GetTimeMark(&lastNodeCountTime);
\r
8443 lastNodeCount = 0;
\r
8447 AnalyzeFileEvent()
\r
8449 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
8452 if (gameMode != AnalyzeMode) {
\r
8454 if (gameMode != EditGame) return;
\r
8455 ResurrectChessProgram();
\r
8456 SendToProgram("analyze\n", &first);
\r
8457 first.analyzing = TRUE;
\r
8458 /*first.maybeThinking = TRUE;*/
\r
8459 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
8460 AnalysisPopUp("Analysis",
\r
8461 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
8463 gameMode = AnalyzeFile;
\r
8468 StartAnalysisClock();
\r
8469 GetTimeMark(&lastNodeCountTime);
\r
8470 lastNodeCount = 0;
\r
8474 MachineWhiteEvent()
\r
8476 char buf[MSG_SIZ];
\r
8478 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
8482 if (gameMode == PlayFromGameFile ||
\r
8483 gameMode == TwoMachinesPlay ||
\r
8484 gameMode == Training ||
\r
8485 gameMode == AnalyzeMode ||
\r
8486 gameMode == EndOfGame)
\r
8489 if (gameMode == EditPosition)
\r
8490 EditPositionDone();
\r
8492 if (!WhiteOnMove(currentMove)) {
\r
8493 DisplayError("It is not White's turn", 0);
\r
8497 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
8498 ExitAnalyzeMode();
\r
8500 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
8501 gameMode == AnalyzeFile)
\r
8504 ResurrectChessProgram(); /* in case it isn't running */
\r
8505 gameMode = MachinePlaysWhite;
\r
8509 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8510 DisplayTitle(buf);
\r
8511 if (first.sendName) {
\r
8512 sprintf(buf, "name %s\n", gameInfo.black);
\r
8513 SendToProgram(buf, &first);
\r
8515 if (first.sendTime) {
\r
8516 if (first.useColors) {
\r
8517 SendToProgram("black\n", &first); /*gnu kludge*/
\r
8519 SendTimeRemaining(&first, TRUE);
\r
8521 if (first.useColors) {
\r
8522 SendToProgram("white\ngo\n", &first);
\r
8524 SendToProgram("go\n", &first);
\r
8526 SetMachineThinkingEnables();
\r
8527 first.maybeThinking = TRUE;
\r
8530 if (appData.autoFlipView && !flipView) {
\r
8531 flipView = !flipView;
\r
8532 DrawPosition(FALSE, NULL);
\r
8537 MachineBlackEvent()
\r
8539 char buf[MSG_SIZ];
\r
8541 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
8545 if (gameMode == PlayFromGameFile ||
\r
8546 gameMode == TwoMachinesPlay ||
\r
8547 gameMode == Training ||
\r
8548 gameMode == AnalyzeMode ||
\r
8549 gameMode == EndOfGame)
\r
8552 if (gameMode == EditPosition)
\r
8553 EditPositionDone();
\r
8555 if (WhiteOnMove(currentMove)) {
\r
8556 DisplayError("It is not Black's turn", 0);
\r
8560 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
8561 ExitAnalyzeMode();
\r
8563 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
8564 gameMode == AnalyzeFile)
\r
8567 ResurrectChessProgram(); /* in case it isn't running */
\r
8568 gameMode = MachinePlaysBlack;
\r
8572 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8573 DisplayTitle(buf);
\r
8574 if (first.sendName) {
\r
8575 sprintf(buf, "name %s\n", gameInfo.white);
\r
8576 SendToProgram(buf, &first);
\r
8578 if (first.sendTime) {
\r
8579 if (first.useColors) {
\r
8580 SendToProgram("white\n", &first); /*gnu kludge*/
\r
8582 SendTimeRemaining(&first, FALSE);
\r
8584 if (first.useColors) {
\r
8585 SendToProgram("black\ngo\n", &first);
\r
8587 SendToProgram("go\n", &first);
\r
8589 SetMachineThinkingEnables();
\r
8590 first.maybeThinking = TRUE;
\r
8593 if (appData.autoFlipView && flipView) {
\r
8594 flipView = !flipView;
\r
8595 DrawPosition(FALSE, NULL);
\r
8601 DisplayTwoMachinesTitle()
\r
8603 char buf[MSG_SIZ];
\r
8604 if (appData.matchGames > 0) {
\r
8605 if (first.twoMachinesColor[0] == 'w') {
\r
8606 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
8607 gameInfo.white, gameInfo.black,
\r
8608 first.matchWins, second.matchWins,
\r
8609 matchGame - 1 - (first.matchWins + second.matchWins));
\r
8611 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
8612 gameInfo.white, gameInfo.black,
\r
8613 second.matchWins, first.matchWins,
\r
8614 matchGame - 1 - (first.matchWins + second.matchWins));
\r
8617 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
8619 DisplayTitle(buf);
\r
8623 TwoMachinesEvent P((void))
\r
8626 char buf[MSG_SIZ];
\r
8627 ChessProgramState *onmove;
\r
8629 if (appData.noChessProgram) return;
\r
8631 switch (gameMode) {
\r
8632 case TwoMachinesPlay:
\r
8634 case MachinePlaysWhite:
\r
8635 case MachinePlaysBlack:
\r
8636 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
8637 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
8640 /* fall through */
\r
8641 case BeginningOfGame:
\r
8642 case PlayFromGameFile:
\r
8645 if (gameMode != EditGame) return;
\r
8647 case EditPosition:
\r
8648 EditPositionDone();
\r
8652 ExitAnalyzeMode();
\r
8659 forwardMostMove = currentMove;
\r
8660 ResurrectChessProgram(); /* in case first program isn't running */
\r
8662 if (second.pr == NULL) {
\r
8663 StartChessProgram(&second);
\r
8664 if (second.protocolVersion == 1) {
\r
8665 TwoMachinesEventIfReady();
\r
8667 /* kludge: allow timeout for initial "feature" command */
\r
8669 DisplayMessage("", "Starting second chess program");
\r
8670 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
8674 DisplayMessage("", "");
\r
8675 InitChessProgram(&second);
\r
8676 SendToProgram("force\n", &second);
\r
8677 if (startedFromSetupPosition) {
\r
8678 SendBoard(&second, backwardMostMove);
\r
8680 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
8681 SendMoveToProgram(i, &second);
\r
8684 gameMode = TwoMachinesPlay;
\r
8688 DisplayTwoMachinesTitle();
\r
8690 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
8696 SendToProgram(first.computerString, &first);
\r
8697 if (first.sendName) {
\r
8698 sprintf(buf, "name %s\n", second.tidy);
\r
8699 SendToProgram(buf, &first);
\r
8701 SendToProgram(second.computerString, &second);
\r
8702 if (second.sendName) {
\r
8703 sprintf(buf, "name %s\n", first.tidy);
\r
8704 SendToProgram(buf, &second);
\r
8707 if (!first.sendTime || !second.sendTime) {
\r
8709 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
8710 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
8712 if (onmove->sendTime) {
\r
8713 if (onmove->useColors) {
\r
8714 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
8716 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
8718 if (onmove->useColors) {
\r
8719 SendToProgram(onmove->twoMachinesColor, onmove);
\r
8721 SendToProgram("go\n", onmove);
\r
8722 onmove->maybeThinking = TRUE;
\r
8723 SetMachineThinkingEnables();
\r
8731 if (gameMode == Training) {
\r
8732 SetTrainingModeOff();
\r
8733 gameMode = PlayFromGameFile;
\r
8734 DisplayMessage("", "Training mode off");
\r
8736 gameMode = Training;
\r
8737 animateTraining = appData.animate;
\r
8739 /* make sure we are not already at the end of the game */
\r
8740 if (currentMove < forwardMostMove) {
\r
8741 SetTrainingModeOn();
\r
8742 DisplayMessage("", "Training mode on");
\r
8744 gameMode = PlayFromGameFile;
\r
8745 DisplayError("Already at end of game", 0);
\r
8754 if (!appData.icsActive) return;
\r
8755 switch (gameMode) {
\r
8756 case IcsPlayingWhite:
\r
8757 case IcsPlayingBlack:
\r
8758 case IcsObserving:
\r
8760 case BeginningOfGame:
\r
8761 case IcsExamining:
\r
8767 case EditPosition:
\r
8768 EditPositionDone();
\r
8773 ExitAnalyzeMode();
\r
8781 gameMode = IcsIdle;
\r
8792 switch (gameMode) {
\r
8794 SetTrainingModeOff();
\r
8796 case MachinePlaysWhite:
\r
8797 case MachinePlaysBlack:
\r
8798 case BeginningOfGame:
\r
8799 SendToProgram("force\n", &first);
\r
8800 SetUserThinkingEnables();
\r
8802 case PlayFromGameFile:
\r
8803 (void) StopLoadGameTimer();
\r
8804 if (gameFileFP != NULL) {
\r
8805 gameFileFP = NULL;
\r
8808 case EditPosition:
\r
8809 EditPositionDone();
\r
8813 ExitAnalyzeMode();
\r
8814 SendToProgram("force\n", &first);
\r
8816 case TwoMachinesPlay:
\r
8817 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8818 ResurrectChessProgram();
\r
8819 SetUserThinkingEnables();
\r
8822 ResurrectChessProgram();
\r
8824 case IcsPlayingBlack:
\r
8825 case IcsPlayingWhite:
\r
8826 DisplayError("Warning: You are still playing a game", 0);
\r
8828 case IcsObserving:
\r
8829 DisplayError("Warning: You are still observing a game", 0);
\r
8831 case IcsExamining:
\r
8832 DisplayError("Warning: You are still examining a game", 0);
\r
8843 first.offeredDraw = second.offeredDraw = 0;
\r
8845 if (gameMode == PlayFromGameFile) {
\r
8846 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
8847 blackTimeRemaining = timeRemaining[1][currentMove];
\r
8851 if (gameMode == MachinePlaysWhite ||
\r
8852 gameMode == MachinePlaysBlack ||
\r
8853 gameMode == TwoMachinesPlay ||
\r
8854 gameMode == EndOfGame) {
\r
8855 i = forwardMostMove;
\r
8856 while (i > currentMove) {
\r
8857 SendToProgram("undo\n", &first);
\r
8860 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
8861 blackTimeRemaining = timeRemaining[1][currentMove];
\r
8862 DisplayBothClocks();
\r
8863 if (whiteFlag || blackFlag) {
\r
8864 whiteFlag = blackFlag = 0;
\r
8869 gameMode = EditGame;
\r
8876 EditPositionEvent()
\r
8878 if (gameMode == EditPosition) {
\r
8884 if (gameMode != EditGame) return;
\r
8886 gameMode = EditPosition;
\r
8889 if (currentMove > 0)
\r
8890 CopyBoard(boards[0], boards[currentMove]);
\r
8892 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
8894 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8895 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8902 if (first.analysisSupport && first.analyzing) {
\r
8903 SendToProgram("exit\n", &first);
\r
8904 first.analyzing = FALSE;
\r
8906 AnalysisPopDown();
\r
8907 thinkOutput[0] = NULLCHAR;
\r
8911 EditPositionDone()
\r
8913 startedFromSetupPosition = TRUE;
\r
8914 InitChessProgram(&first);
\r
8915 SendToProgram("force\n", &first);
\r
8916 if (blackPlaysFirst) {
\r
8917 strcpy(moveList[0], "");
\r
8918 strcpy(parseList[0], "");
\r
8919 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8920 CopyBoard(boards[1], boards[0]);
\r
8922 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8924 SendBoard(&first, forwardMostMove);
\r
8926 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
8927 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
8928 gameMode = EditGame;
\r
8930 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8931 ClearHighlights(); /* [AS] */
\r
8934 /* Pause for `ms' milliseconds */
\r
8935 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
8945 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
8948 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
8950 SendMultiLineToICS(buf)
\r
8953 char temp[MSG_SIZ+1], *p;
\r
8956 len = strlen(buf);
\r
8957 if (len > MSG_SIZ)
\r
8960 strncpy(temp, buf, len);
\r
8965 if (*p == '\n' || *p == '\r')
\r
8970 strcat(temp, "\n");
\r
8972 SendToPlayer(temp, strlen(temp));
\r
8976 SetWhiteToPlayEvent()
\r
8978 if (gameMode == EditPosition) {
\r
8979 blackPlaysFirst = FALSE;
\r
8980 DisplayBothClocks(); /* works because currentMove is 0 */
\r
8981 } else if (gameMode == IcsExamining) {
\r
8982 SendToICS(ics_prefix);
\r
8983 SendToICS("tomove white\n");
\r
8988 SetBlackToPlayEvent()
\r
8990 if (gameMode == EditPosition) {
\r
8991 blackPlaysFirst = TRUE;
\r
8992 currentMove = 1; /* kludge */
\r
8993 DisplayBothClocks();
\r
8995 } else if (gameMode == IcsExamining) {
\r
8996 SendToICS(ics_prefix);
\r
8997 SendToICS("tomove black\n");
\r
9002 EditPositionMenuEvent(selection, x, y)
\r
9003 ChessSquare selection;
\r
9006 char buf[MSG_SIZ];
\r
9008 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
9010 switch (selection) {
\r
9012 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
9013 SendToICS(ics_prefix);
\r
9014 SendToICS("bsetup clear\n");
\r
9015 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
9016 SendToICS(ics_prefix);
\r
9017 SendToICS("clearboard\n");
\r
9019 for (x = 0; x < BOARD_WIDTH; x++) {
\r
9020 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
9021 if (gameMode == IcsExamining) {
\r
9022 if (boards[currentMove][y][x] != EmptySquare) {
\r
9023 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
9024 'a' + x, ONE + y);
\r
9028 boards[0][y][x] = EmptySquare;
\r
9033 if (gameMode == EditPosition) {
\r
9034 DrawPosition(FALSE, boards[0]);
\r
9039 SetWhiteToPlayEvent();
\r
9043 SetBlackToPlayEvent();
\r
9047 if (gameMode == IcsExamining) {
\r
9048 sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, ONE + y);
\r
9051 boards[0][y][x] = EmptySquare;
\r
9052 DrawPosition(FALSE, boards[0]);
\r
9057 if (gameMode == IcsExamining) {
\r
9058 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
9059 PieceToChar(selection), 'a' + x, ONE + y);
\r
9062 boards[0][y][x] = selection;
\r
9063 DrawPosition(FALSE, boards[0]);
\r
9071 DropMenuEvent(selection, x, y)
\r
9072 ChessSquare selection;
\r
9075 ChessMove moveType;
\r
9077 switch (gameMode) {
\r
9078 case IcsPlayingWhite:
\r
9079 case MachinePlaysBlack:
\r
9080 if (!WhiteOnMove(currentMove)) {
\r
9081 DisplayMoveError("It is Black's turn");
\r
9084 moveType = WhiteDrop;
\r
9086 case IcsPlayingBlack:
\r
9087 case MachinePlaysWhite:
\r
9088 if (WhiteOnMove(currentMove)) {
\r
9089 DisplayMoveError("It is White's turn");
\r
9092 moveType = BlackDrop;
\r
9095 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
9101 if (moveType == BlackDrop && selection < BlackPawn) {
\r
9102 selection = (ChessSquare) ((int) selection
\r
9103 + (int) BlackPawn - (int) WhitePawn);
\r
9105 if (boards[currentMove][y][x] != EmptySquare) {
\r
9106 DisplayMoveError("That square is occupied");
\r
9110 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
9116 /* Accept a pending offer of any kind from opponent */
\r
9118 if (appData.icsActive) {
\r
9119 SendToICS(ics_prefix);
\r
9120 SendToICS("accept\n");
\r
9121 } else if (cmailMsgLoaded) {
\r
9122 if (currentMove == cmailOldMove &&
\r
9123 commentList[cmailOldMove] != NULL &&
\r
9124 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9125 "Black offers a draw" : "White offers a draw")) {
\r
9127 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9128 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9130 DisplayError("There is no pending offer on this move", 0);
\r
9131 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9134 /* Not used for offers from chess program */
\r
9141 /* Decline a pending offer of any kind from opponent */
\r
9143 if (appData.icsActive) {
\r
9144 SendToICS(ics_prefix);
\r
9145 SendToICS("decline\n");
\r
9146 } else if (cmailMsgLoaded) {
\r
9147 if (currentMove == cmailOldMove &&
\r
9148 commentList[cmailOldMove] != NULL &&
\r
9149 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9150 "Black offers a draw" : "White offers a draw")) {
\r
9152 AppendComment(cmailOldMove, "Draw declined");
\r
9153 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
9156 DisplayError("There is no pending offer on this move", 0);
\r
9159 /* Not used for offers from chess program */
\r
9166 /* Issue ICS rematch command */
\r
9167 if (appData.icsActive) {
\r
9168 SendToICS(ics_prefix);
\r
9169 SendToICS("rematch\n");
\r
9176 /* Call your opponent's flag (claim a win on time) */
\r
9177 if (appData.icsActive) {
\r
9178 SendToICS(ics_prefix);
\r
9179 SendToICS("flag\n");
\r
9181 switch (gameMode) {
\r
9184 case MachinePlaysWhite:
\r
9187 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9190 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
9192 DisplayError("Your opponent is not out of time", 0);
\r
9195 case MachinePlaysBlack:
\r
9198 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
9201 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
9203 DisplayError("Your opponent is not out of time", 0);
\r
9213 /* Offer draw or accept pending draw offer from opponent */
\r
9215 if (appData.icsActive) {
\r
9216 /* Note: tournament rules require draw offers to be
\r
9217 made after you make your move but before you punch
\r
9218 your clock. Currently ICS doesn't let you do that;
\r
9219 instead, you immediately punch your clock after making
\r
9220 a move, but you can offer a draw at any time. */
\r
9222 SendToICS(ics_prefix);
\r
9223 SendToICS("draw\n");
\r
9224 } else if (cmailMsgLoaded) {
\r
9225 if (currentMove == cmailOldMove &&
\r
9226 commentList[cmailOldMove] != NULL &&
\r
9227 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
9228 "Black offers a draw" : "White offers a draw")) {
\r
9229 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
9230 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
9231 } else if (currentMove == cmailOldMove + 1) {
\r
9232 char *offer = WhiteOnMove(cmailOldMove) ?
\r
9233 "White offers a draw" : "Black offers a draw";
\r
9234 AppendComment(currentMove, offer);
\r
9235 DisplayComment(currentMove - 1, offer);
\r
9236 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
9238 DisplayError("You must make your move before offering a draw", 0);
\r
9239 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
9241 } else if (first.offeredDraw) {
\r
9242 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
9244 if (first.sendDrawOffers) {
\r
9245 SendToProgram("draw\n", &first);
\r
9246 userOfferedDraw = TRUE;
\r
9254 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
9256 if (appData.icsActive) {
\r
9257 SendToICS(ics_prefix);
\r
9258 SendToICS("adjourn\n");
\r
9260 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
9268 /* Offer Abort or accept pending Abort offer from opponent */
\r
9270 if (appData.icsActive) {
\r
9271 SendToICS(ics_prefix);
\r
9272 SendToICS("abort\n");
\r
9274 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
9281 /* Resign. You can do this even if it's not your turn. */
\r
9283 if (appData.icsActive) {
\r
9284 SendToICS(ics_prefix);
\r
9285 SendToICS("resign\n");
\r
9287 switch (gameMode) {
\r
9288 case MachinePlaysWhite:
\r
9289 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9291 case MachinePlaysBlack:
\r
9292 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9295 if (cmailMsgLoaded) {
\r
9297 if (WhiteOnMove(cmailOldMove)) {
\r
9298 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
9300 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
9302 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
9313 StopObservingEvent()
\r
9315 /* Stop observing current games */
\r
9316 SendToICS(ics_prefix);
\r
9317 SendToICS("unobserve\n");
\r
9321 StopExaminingEvent()
\r
9323 /* Stop observing current game */
\r
9324 SendToICS(ics_prefix);
\r
9325 SendToICS("unexamine\n");
\r
9329 ForwardInner(target)
\r
9334 if (appData.debugMode)
\r
9335 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
9336 target, currentMove, forwardMostMove);
\r
9338 if (gameMode == EditPosition)
\r
9341 if (gameMode == PlayFromGameFile && !pausing)
\r
9344 if (gameMode == IcsExamining && pausing)
\r
9345 limit = pauseExamForwardMostMove;
\r
9347 limit = forwardMostMove;
\r
9349 if (target > limit) target = limit;
\r
9351 if (target > 0 && moveList[target - 1][0]) {
\r
9352 int fromX, fromY, toX, toY;
\r
9353 toX = moveList[target - 1][2] - 'a';
\r
9354 toY = moveList[target - 1][3] - ONE;
\r
9355 if (moveList[target - 1][1] == '@') {
\r
9356 if (appData.highlightLastMove) {
\r
9357 SetHighlights(-1, -1, toX, toY);
\r
9360 fromX = moveList[target - 1][0] - 'a';
\r
9361 fromY = moveList[target - 1][1] - ONE;
\r
9362 if (target == currentMove + 1) {
\r
9363 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
9365 if (appData.highlightLastMove) {
\r
9366 SetHighlights(fromX, fromY, toX, toY);
\r
9370 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9371 gameMode == Training || gameMode == PlayFromGameFile ||
\r
9372 gameMode == AnalyzeFile) {
\r
9373 while (currentMove < target) {
\r
9374 SendMoveToProgram(currentMove++, &first);
\r
9377 currentMove = target;
\r
9380 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9381 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9382 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9384 DisplayBothClocks();
\r
9385 DisplayMove(currentMove - 1);
\r
9386 DrawPosition(FALSE, boards[currentMove]);
\r
9387 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9388 if (commentList[currentMove] && !matchMode && gameMode != Training) {
\r
9389 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9397 if (gameMode == IcsExamining && !pausing) {
\r
9398 SendToICS(ics_prefix);
\r
9399 SendToICS("forward\n");
\r
9401 ForwardInner(currentMove + 1);
\r
9408 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9409 /* to optimze, we temporarily turn off analysis mode while we feed
\r
9410 * the remaining moves to the engine. Otherwise we get analysis output
\r
9411 * after each move.
\r
9413 if (first.analysisSupport) {
\r
9414 SendToProgram("exit\nforce\n", &first);
\r
9415 first.analyzing = FALSE;
\r
9419 if (gameMode == IcsExamining && !pausing) {
\r
9420 SendToICS(ics_prefix);
\r
9421 SendToICS("forward 999999\n");
\r
9423 ForwardInner(forwardMostMove);
\r
9426 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9427 /* we have fed all the moves, so reactivate analysis mode */
\r
9428 SendToProgram("analyze\n", &first);
\r
9429 first.analyzing = TRUE;
\r
9430 /*first.maybeThinking = TRUE;*/
\r
9431 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9436 BackwardInner(target)
\r
9439 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
9441 if (appData.debugMode)
\r
9442 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
9443 target, currentMove, forwardMostMove);
\r
9445 if (gameMode == EditPosition) return;
\r
9446 if (currentMove <= backwardMostMove) {
\r
9447 ClearHighlights();
\r
9448 DrawPosition(full_redraw, boards[currentMove]);
\r
9451 if (gameMode == PlayFromGameFile && !pausing)
\r
9454 if (moveList[target][0]) {
\r
9455 int fromX, fromY, toX, toY;
\r
9456 toX = moveList[target][2] - 'a';
\r
9457 toY = moveList[target][3] - ONE;
\r
9458 if (moveList[target][1] == '@') {
\r
9459 if (appData.highlightLastMove) {
\r
9460 SetHighlights(-1, -1, toX, toY);
\r
9463 fromX = moveList[target][0] - 'a';
\r
9464 fromY = moveList[target][1] - ONE;
\r
9465 if (target == currentMove - 1) {
\r
9466 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
9468 if (appData.highlightLastMove) {
\r
9469 SetHighlights(fromX, fromY, toX, toY);
\r
9473 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
9474 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
9475 while (currentMove > target) {
\r
9476 SendToProgram("undo\n", &first);
\r
9480 currentMove = target;
\r
9483 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
9484 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9485 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9487 DisplayBothClocks();
\r
9488 DisplayMove(currentMove - 1);
\r
9489 DrawPosition(full_redraw, boards[currentMove]);
\r
9490 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
9491 if (commentList[currentMove] != NULL) {
\r
9492 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9499 if (gameMode == IcsExamining && !pausing) {
\r
9500 SendToICS(ics_prefix);
\r
9501 SendToICS("backward\n");
\r
9503 BackwardInner(currentMove - 1);
\r
9510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9511 /* to optimze, we temporarily turn off analysis mode while we undo
\r
9512 * all the moves. Otherwise we get analysis output after each undo.
\r
9514 if (first.analysisSupport) {
\r
9515 SendToProgram("exit\nforce\n", &first);
\r
9516 first.analyzing = FALSE;
\r
9520 if (gameMode == IcsExamining && !pausing) {
\r
9521 SendToICS(ics_prefix);
\r
9522 SendToICS("backward 999999\n");
\r
9524 BackwardInner(backwardMostMove);
\r
9527 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
9528 /* we have fed all the moves, so reactivate analysis mode */
\r
9529 SendToProgram("analyze\n", &first);
\r
9530 first.analyzing = TRUE;
\r
9531 /*first.maybeThinking = TRUE;*/
\r
9532 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9539 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
9540 if (to >= forwardMostMove) to = forwardMostMove;
\r
9541 if (to <= backwardMostMove) to = backwardMostMove;
\r
9542 if (to < currentMove) {
\r
9543 BackwardInner(to);
\r
9552 if (gameMode != IcsExamining) {
\r
9553 DisplayError("You are not examining a game", 0);
\r
9557 DisplayError("You can't revert while pausing", 0);
\r
9560 SendToICS(ics_prefix);
\r
9561 SendToICS("revert\n");
\r
9565 RetractMoveEvent()
\r
9567 switch (gameMode) {
\r
9568 case MachinePlaysWhite:
\r
9569 case MachinePlaysBlack:
\r
9570 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9571 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9574 if (forwardMostMove < 2) return;
\r
9575 currentMove = forwardMostMove = forwardMostMove - 2;
\r
9576 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9577 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9578 DisplayBothClocks();
\r
9579 DisplayMove(currentMove - 1);
\r
9580 ClearHighlights();/*!! could figure this out*/
\r
9581 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
9582 SendToProgram("remove\n", &first);
\r
9583 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
9586 case BeginningOfGame:
\r
9590 case IcsPlayingWhite:
\r
9591 case IcsPlayingBlack:
\r
9592 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
9593 SendToICS(ics_prefix);
\r
9594 SendToICS("takeback 2\n");
\r
9596 SendToICS(ics_prefix);
\r
9597 SendToICS("takeback 1\n");
\r
9606 ChessProgramState *cps;
\r
9608 switch (gameMode) {
\r
9609 case MachinePlaysWhite:
\r
9610 if (!WhiteOnMove(forwardMostMove)) {
\r
9611 DisplayError("It is your turn", 0);
\r
9616 case MachinePlaysBlack:
\r
9617 if (WhiteOnMove(forwardMostMove)) {
\r
9618 DisplayError("It is your turn", 0);
\r
9623 case TwoMachinesPlay:
\r
9624 if (WhiteOnMove(forwardMostMove) ==
\r
9625 (first.twoMachinesColor[0] == 'w')) {
\r
9631 case BeginningOfGame:
\r
9635 SendToProgram("?\n", cps);
\r
9639 TruncateGameEvent()
\r
9642 if (gameMode != EditGame) return;
\r
9649 if (forwardMostMove > currentMove) {
\r
9650 if (gameInfo.resultDetails != NULL) {
\r
9651 free(gameInfo.resultDetails);
\r
9652 gameInfo.resultDetails = NULL;
\r
9653 gameInfo.result = GameUnfinished;
\r
9655 forwardMostMove = currentMove;
\r
9656 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
9664 if (appData.noChessProgram) return;
\r
9665 switch (gameMode) {
\r
9666 case MachinePlaysWhite:
\r
9667 if (WhiteOnMove(forwardMostMove)) {
\r
9668 DisplayError("Wait until your turn", 0);
\r
9672 case BeginningOfGame:
\r
9673 case MachinePlaysBlack:
\r
9674 if (!WhiteOnMove(forwardMostMove)) {
\r
9675 DisplayError("Wait until your turn", 0);
\r
9680 DisplayError("No hint available", 0);
\r
9683 SendToProgram("hint\n", &first);
\r
9684 hintRequested = TRUE;
\r
9690 if (appData.noChessProgram) return;
\r
9691 switch (gameMode) {
\r
9692 case MachinePlaysWhite:
\r
9693 if (WhiteOnMove(forwardMostMove)) {
\r
9694 DisplayError("Wait until your turn", 0);
\r
9698 case BeginningOfGame:
\r
9699 case MachinePlaysBlack:
\r
9700 if (!WhiteOnMove(forwardMostMove)) {
\r
9701 DisplayError("Wait until your turn", 0);
\r
9705 case EditPosition:
\r
9706 EditPositionDone();
\r
9708 case TwoMachinesPlay:
\r
9713 SendToProgram("bk\n", &first);
\r
9714 bookOutput[0] = NULLCHAR;
\r
9715 bookRequested = TRUE;
\r
9721 char *tags = PGNTags(&gameInfo);
\r
9722 TagsPopUp(tags, CmailMsg());
\r
9726 /* end button procedures */
\r
9729 PrintPosition(fp, move)
\r
9735 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9736 for (j = 0; j < BOARD_WIDTH; j++) {
\r
9737 char c = PieceToChar(boards[move][i][j]);
\r
9738 fputc(c == 'x' ? '.' : c, fp);
\r
9739 fputc(j == BOARD_WIDTH - 1 ? '\n' : ' ', fp);
\r
9742 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
9743 fprintf(fp, "white to play\n");
\r
9745 fprintf(fp, "black to play\n");
\r
9749 PrintOpponents(fp)
\r
9752 if (gameInfo.white != NULL) {
\r
9753 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
9755 fprintf(fp, "\n");
\r
9759 /* Find last component of program's own name, using some heuristics */
\r
9761 TidyProgramName(prog, host, buf)
\r
9762 char *prog, *host, buf[MSG_SIZ];
\r
9765 int local = (strcmp(host, "localhost") == 0);
\r
9766 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
9768 while (*p == ' ') p++;
\r
9771 if (*prog == '"' || *prog == '\'') {
\r
9772 q = strchr(prog + 1, *prog);
\r
9774 q = strchr(prog, ' ');
\r
9776 if (q == NULL) q = prog + strlen(prog);
\r
9778 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
9780 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
9781 memcpy(buf, p, q - p);
\r
9782 buf[q - p] = NULLCHAR;
\r
9785 strcat(buf, host);
\r
9790 TimeControlTagValue()
\r
9792 char buf[MSG_SIZ];
\r
9793 if (!appData.clockMode) {
\r
9795 } else if (movesPerSession > 0) {
\r
9796 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
9797 } else if (timeIncrement == 0) {
\r
9798 sprintf(buf, "%ld", timeControl/1000);
\r
9800 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
9802 return StrSave(buf);
\r
9808 /* This routine is used only for certain modes */
\r
9809 VariantClass v = gameInfo.variant;
\r
9810 ClearGameInfo(&gameInfo);
\r
9811 gameInfo.variant = v;
\r
9813 switch (gameMode) {
\r
9814 case MachinePlaysWhite:
\r
9815 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
9816 gameInfo.site = StrSave(HostName());
\r
9817 gameInfo.date = PGNDate();
\r
9818 gameInfo.round = StrSave("-");
\r
9819 gameInfo.white = StrSave(first.tidy);
\r
9820 gameInfo.black = StrSave(UserName());
\r
9821 gameInfo.timeControl = TimeControlTagValue();
\r
9824 case MachinePlaysBlack:
\r
9825 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
9826 gameInfo.site = StrSave(HostName());
\r
9827 gameInfo.date = PGNDate();
\r
9828 gameInfo.round = StrSave("-");
\r
9829 gameInfo.white = StrSave(UserName());
\r
9830 gameInfo.black = StrSave(first.tidy);
\r
9831 gameInfo.timeControl = TimeControlTagValue();
\r
9834 case TwoMachinesPlay:
\r
9835 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
9836 gameInfo.site = StrSave(HostName());
\r
9837 gameInfo.date = PGNDate();
\r
9838 if (matchGame > 0) {
\r
9839 char buf[MSG_SIZ];
\r
9840 sprintf(buf, "%d", matchGame);
\r
9841 gameInfo.round = StrSave(buf);
\r
9843 gameInfo.round = StrSave("-");
\r
9845 if (first.twoMachinesColor[0] == 'w') {
\r
9846 gameInfo.white = StrSave(first.tidy);
\r
9847 gameInfo.black = StrSave(second.tidy);
\r
9849 gameInfo.white = StrSave(second.tidy);
\r
9850 gameInfo.black = StrSave(first.tidy);
\r
9852 gameInfo.timeControl = TimeControlTagValue();
\r
9856 gameInfo.event = StrSave("Edited game");
\r
9857 gameInfo.site = StrSave(HostName());
\r
9858 gameInfo.date = PGNDate();
\r
9859 gameInfo.round = StrSave("-");
\r
9860 gameInfo.white = StrSave("-");
\r
9861 gameInfo.black = StrSave("-");
\r
9864 case EditPosition:
\r
9865 gameInfo.event = StrSave("Edited position");
\r
9866 gameInfo.site = StrSave(HostName());
\r
9867 gameInfo.date = PGNDate();
\r
9868 gameInfo.round = StrSave("-");
\r
9869 gameInfo.white = StrSave("-");
\r
9870 gameInfo.black = StrSave("-");
\r
9873 case IcsPlayingWhite:
\r
9874 case IcsPlayingBlack:
\r
9875 case IcsObserving:
\r
9876 case IcsExamining:
\r
9879 case PlayFromGameFile:
\r
9880 gameInfo.event = StrSave("Game from non-PGN file");
\r
9881 gameInfo.site = StrSave(HostName());
\r
9882 gameInfo.date = PGNDate();
\r
9883 gameInfo.round = StrSave("-");
\r
9884 gameInfo.white = StrSave("?");
\r
9885 gameInfo.black = StrSave("?");
\r
9894 ReplaceComment(index, text)
\r
9900 while (*text == '\n') text++;
\r
9901 len = strlen(text);
\r
9902 while (len > 0 && text[len - 1] == '\n') len--;
\r
9904 if (commentList[index] != NULL)
\r
9905 free(commentList[index]);
\r
9908 commentList[index] = NULL;
\r
9911 commentList[index] = (char *) malloc(len + 2);
\r
9912 strncpy(commentList[index], text, len);
\r
9913 commentList[index][len] = '\n';
\r
9914 commentList[index][len + 1] = NULLCHAR;
\r
9927 if (ch == '\r') continue;
\r
9929 } while (ch != '\0');
\r
9933 AppendComment(index, text)
\r
9940 GetInfoFromComment( index, text );
\r
9943 while (*text == '\n') text++;
\r
9944 len = strlen(text);
\r
9945 while (len > 0 && text[len - 1] == '\n') len--;
\r
9947 if (len == 0) return;
\r
9949 if (commentList[index] != NULL) {
\r
9950 old = commentList[index];
\r
9951 oldlen = strlen(old);
\r
9952 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
9953 strcpy(commentList[index], old);
\r
9955 strncpy(&commentList[index][oldlen], text, len);
\r
9956 commentList[index][oldlen + len] = '\n';
\r
9957 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
9959 commentList[index] = (char *) malloc(len + 2);
\r
9960 strncpy(commentList[index], text, len);
\r
9961 commentList[index][len] = '\n';
\r
9962 commentList[index][len + 1] = NULLCHAR;
\r
9966 static char * FindStr( char * text, char * sub_text )
\r
9968 char * result = strstr( text, sub_text );
\r
9970 if( result != NULL ) {
\r
9971 result += strlen( sub_text );
\r
9977 /* [AS] Try to extract PV info from PGN comment */
\r
9978 void GetInfoFromComment( int index, char * text )
\r
9980 if( text != NULL && index > 0 ) {
\r
9984 char * s_eval = FindStr( text, "[%eval " );
\r
9985 char * s_emt = FindStr( text, "[%emt " );
\r
9987 if( s_eval != NULL || s_emt != NULL ) {
\r
9991 if( s_eval != NULL ) {
\r
9992 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
9996 if( delim != ']' ) {
\r
10001 if( s_emt != NULL ) {
\r
10005 /* We expect something like: [+|-]nnn.nn/dd */
\r
10006 char * sep = strchr( text, '/' );
\r
10007 int score_lo = 0;
\r
10009 if( sep == NULL || sep < (text+4) ) {
\r
10013 if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
10017 if( score_lo < 0 || score_lo >= 100 ) {
\r
10021 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
10024 if( depth <= 0 ) {
\r
10032 pvInfoList[index-1].depth = depth;
\r
10033 pvInfoList[index-1].score = score;
\r
10034 pvInfoList[index-1].time = time;
\r
10039 SendToProgram(message, cps)
\r
10041 ChessProgramState *cps;
\r
10043 int count, outCount, error;
\r
10044 char buf[MSG_SIZ];
\r
10046 if (cps->pr == NULL) return;
\r
10049 if (appData.debugMode) {
\r
10051 GetTimeMark(&now);
\r
10052 fprintf(debugFP, "%ld >%-6s: %s",
\r
10053 SubtractTimeMarks(&now, &programStartTime),
\r
10054 cps->which, message);
\r
10057 count = strlen(message);
\r
10058 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
10059 if (outCount < count && !exiting) {
\r
10060 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
10061 DisplayFatalError(buf, error, 1);
\r
10066 ReceiveFromProgram(isr, closure, message, count, error)
\r
10067 InputSourceRef isr;
\r
10068 VOIDSTAR closure;
\r
10074 char buf[MSG_SIZ];
\r
10075 ChessProgramState *cps = (ChessProgramState *)closure;
\r
10077 if (isr != cps->isr) return; /* Killed intentionally */
\r
10078 if (count <= 0) {
\r
10079 if (count == 0) {
\r
10081 "Error: %s chess program (%s) exited unexpectedly",
\r
10082 cps->which, cps->program);
\r
10083 RemoveInputSource(cps->isr);
\r
10084 DisplayFatalError(buf, 0, 1);
\r
10087 "Error reading from %s chess program (%s)",
\r
10088 cps->which, cps->program);
\r
10089 RemoveInputSource(cps->isr);
\r
10091 /* [AS] Program is misbehaving badly... kill it */
\r
10092 if( count == -2 ) {
\r
10093 DestroyChildProcess( cps->pr, 9 );
\r
10094 cps->pr = NoProc;
\r
10097 DisplayFatalError(buf, error, 1);
\r
10099 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10103 if ((end_str = strchr(message, '\r')) != NULL)
\r
10104 *end_str = NULLCHAR;
\r
10105 if ((end_str = strchr(message, '\n')) != NULL)
\r
10106 *end_str = NULLCHAR;
\r
10108 if (appData.debugMode) {
\r
10110 GetTimeMark(&now);
\r
10111 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
10112 SubtractTimeMarks(&now, &programStartTime),
\r
10113 cps->which, message);
\r
10115 HandleMachineMove(message, cps);
\r
10120 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
10121 ChessProgramState *cps;
\r
10122 int mps, inc, sd, st;
\r
10125 char buf[MSG_SIZ];
\r
10126 int seconds = (tc / 1000) % 60;
\r
10128 if( timeControl_2 > 0 ) {
\r
10129 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
10130 tc = timeControl_2;
\r
10135 /* Set exact time per move, normally using st command */
\r
10136 if (cps->stKludge) {
\r
10137 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
10138 seconds = st % 60;
\r
10139 if (seconds == 0) {
\r
10140 sprintf(buf, "level 1 %d\n", st/60);
\r
10142 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
10145 sprintf(buf, "st %d\n", st);
\r
10148 /* Set conventional or incremental time control, using level command */
\r
10149 if (seconds == 0) {
\r
10150 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
10151 Fixed in later versions, but still avoid :seconds
\r
10152 when seconds is 0. */
\r
10153 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
10155 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
10156 seconds, inc/1000);
\r
10159 SendToProgram(buf, cps);
\r
10161 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
10162 /* Orthogonally, limit search to given depth */
\r
10164 if (cps->sdKludge) {
\r
10165 sprintf(buf, "depth\n%d\n", sd);
\r
10167 sprintf(buf, "sd %d\n", sd);
\r
10169 SendToProgram(buf, cps);
\r
10174 SendTimeRemaining(cps, machineWhite)
\r
10175 ChessProgramState *cps;
\r
10176 int /*boolean*/ machineWhite;
\r
10178 char message[MSG_SIZ];
\r
10179 long time, otime;
\r
10181 /* Note: this routine must be called when the clocks are stopped
\r
10182 or when they have *just* been set or switched; otherwise
\r
10183 it will be off by the time since the current tick started.
\r
10185 if (machineWhite) {
\r
10186 time = whiteTimeRemaining / 10;
\r
10187 otime = blackTimeRemaining / 10;
\r
10189 time = blackTimeRemaining / 10;
\r
10190 otime = whiteTimeRemaining / 10;
\r
10192 if (time <= 0) time = 1;
\r
10193 if (otime <= 0) otime = 1;
\r
10195 sprintf(message, "time %ld\n", time);
\r
10196 SendToProgram(message, cps);
\r
10198 sprintf(message, "otim %ld\n", otime);
\r
10199 SendToProgram(message, cps);
\r
10203 BoolFeature(p, name, loc, cps)
\r
10207 ChessProgramState *cps;
\r
10209 char buf[MSG_SIZ];
\r
10210 int len = strlen(name);
\r
10212 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10214 sscanf(*p, "%d", &val);
\r
10215 *loc = (val != 0);
\r
10216 while (**p && **p != ' ') (*p)++;
\r
10217 sprintf(buf, "accepted %s\n", name);
\r
10218 SendToProgram(buf, cps);
\r
10225 IntFeature(p, name, loc, cps)
\r
10229 ChessProgramState *cps;
\r
10231 char buf[MSG_SIZ];
\r
10232 int len = strlen(name);
\r
10233 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
10235 sscanf(*p, "%d", loc);
\r
10236 while (**p && **p != ' ') (*p)++;
\r
10237 sprintf(buf, "accepted %s\n", name);
\r
10238 SendToProgram(buf, cps);
\r
10245 StringFeature(p, name, loc, cps)
\r
10249 ChessProgramState *cps;
\r
10251 char buf[MSG_SIZ];
\r
10252 int len = strlen(name);
\r
10253 if (strncmp((*p), name, len) == 0
\r
10254 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
10256 sscanf(*p, "%[^\"]", loc);
\r
10257 while (**p && **p != '\"') (*p)++;
\r
10258 if (**p == '\"') (*p)++;
\r
10259 sprintf(buf, "accepted %s\n", name);
\r
10260 SendToProgram(buf, cps);
\r
10267 FeatureDone(cps, val)
\r
10268 ChessProgramState* cps;
\r
10271 DelayedEventCallback cb = GetDelayedEvent();
\r
10272 if ((cb == InitBackEnd3 && cps == &first) ||
\r
10273 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
10274 CancelDelayedEvent();
\r
10275 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
10277 cps->initDone = val;
\r
10280 /* Parse feature command from engine */
\r
10282 ParseFeatures(args, cps)
\r
10284 ChessProgramState *cps;
\r
10289 char buf[MSG_SIZ];
\r
10292 while (*p == ' ') p++;
\r
10293 if (*p == NULLCHAR) return;
\r
10295 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
10296 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
10297 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
10298 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
10299 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
10300 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
10301 /* Engine can disable reuse, but can't enable it if user said no */
\r
10302 if (!val) cps->reuse = FALSE;
\r
10305 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
10306 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
10307 if (gameMode == TwoMachinesPlay) {
\r
10308 DisplayTwoMachinesTitle();
\r
10310 DisplayTitle("");
\r
10314 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
10315 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
10316 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
10317 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
10318 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
10319 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
10320 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
10321 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
10322 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
10323 if (IntFeature(&p, "done", &val, cps)) {
\r
10324 FeatureDone(cps, val);
\r
10327 /* Added by Tord: */
\r
10328 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
10329 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
10330 /* End of additions by Tord */
\r
10332 /* unknown feature: complain and skip */
\r
10334 while (*q && *q != '=') q++;
\r
10335 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
10336 SendToProgram(buf, cps);
\r
10340 if (*p == '\"') {
\r
10342 while (*p && *p != '\"') p++;
\r
10343 if (*p == '\"') p++;
\r
10345 while (*p && *p != ' ') p++;
\r
10353 PeriodicUpdatesEvent(newState)
\r
10356 if (newState == appData.periodicUpdates)
\r
10359 appData.periodicUpdates=newState;
\r
10361 /* Display type changes, so update it now */
\r
10362 DisplayAnalysis();
\r
10364 /* Get the ball rolling again... */
\r
10366 AnalysisPeriodicEvent(1);
\r
10367 StartAnalysisClock();
\r
10372 PonderNextMoveEvent(newState)
\r
10375 if (newState == appData.ponderNextMove) return;
\r
10376 if (gameMode == EditPosition) EditPositionDone();
\r
10378 SendToProgram("hard\n", &first);
\r
10379 if (gameMode == TwoMachinesPlay) {
\r
10380 SendToProgram("hard\n", &second);
\r
10383 SendToProgram("easy\n", &first);
\r
10384 thinkOutput[0] = NULLCHAR;
\r
10385 if (gameMode == TwoMachinesPlay) {
\r
10386 SendToProgram("easy\n", &second);
\r
10389 appData.ponderNextMove = newState;
\r
10393 ShowThinkingEvent(newState)
\r
10396 if (newState == appData.showThinking) return;
\r
10397 if (gameMode == EditPosition) EditPositionDone();
\r
10399 SendToProgram("post\n", &first);
\r
10400 if (gameMode == TwoMachinesPlay) {
\r
10401 SendToProgram("post\n", &second);
\r
10404 SendToProgram("nopost\n", &first);
\r
10405 thinkOutput[0] = NULLCHAR;
\r
10406 if (gameMode == TwoMachinesPlay) {
\r
10407 SendToProgram("nopost\n", &second);
\r
10410 appData.showThinking = newState;
\r
10414 AskQuestionEvent(title, question, replyPrefix, which)
\r
10415 char *title; char *question; char *replyPrefix; char *which;
\r
10417 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
10418 if (pr == NoProc) return;
\r
10419 AskQuestion(title, question, replyPrefix, pr);
\r
10423 DisplayMove(moveNumber)
\r
10426 char message[MSG_SIZ];
\r
10427 char res[MSG_SIZ];
\r
10428 char cpThinkOutput[MSG_SIZ];
\r
10430 if (moveNumber == forwardMostMove - 1 ||
\r
10431 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10433 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
10435 if (strchr(cpThinkOutput, '\n')) {
\r
10436 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
10439 *cpThinkOutput = NULLCHAR;
\r
10442 /* [AS] Hide thinking from human user */
\r
10443 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
10444 *cpThinkOutput = NULLCHAR;
\r
10445 if( thinkOutput[0] != NULLCHAR ) {
\r
10448 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
10449 cpThinkOutput[i] = '.';
\r
10451 cpThinkOutput[i] = NULLCHAR;
\r
10452 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
10456 if (moveNumber == forwardMostMove - 1 &&
\r
10457 gameInfo.resultDetails != NULL) {
\r
10458 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
10459 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
10461 sprintf(res, " {%s} %s",
\r
10462 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
10465 res[0] = NULLCHAR;
\r
10468 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
10469 DisplayMessage(res, cpThinkOutput);
\r
10471 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
10472 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
10473 parseList[moveNumber], res);
\r
10474 DisplayMessage(message, cpThinkOutput);
\r
10479 DisplayAnalysisText(text)
\r
10482 char buf[MSG_SIZ];
\r
10484 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10485 sprintf(buf, "Analysis (%s)", first.tidy);
\r
10486 AnalysisPopUp(buf, text);
\r
10491 only_one_move(str)
\r
10494 while (*str && isspace(*str)) ++str;
\r
10495 while (*str && !isspace(*str)) ++str;
\r
10496 if (!*str) return 1;
\r
10497 while (*str && isspace(*str)) ++str;
\r
10498 if (!*str) return 1;
\r
10503 DisplayAnalysis()
\r
10505 char buf[MSG_SIZ];
\r
10506 char lst[MSG_SIZ / 2];
\r
10508 static char *xtra[] = { "", " (--)", " (++)" };
\r
10511 if (programStats.time == 0) {
\r
10512 programStats.time = 1;
\r
10515 if (programStats.got_only_move) {
\r
10516 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
10518 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
10520 nps = (((double)programStats.nodes) /
\r
10521 (((double)programStats.time)/100.0));
\r
10523 cs = programStats.time % 100;
\r
10524 s = programStats.time / 100;
\r
10525 h = (s / (60*60));
\r
10530 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
10531 if (programStats.move_name[0] != NULLCHAR) {
\r
10532 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10533 programStats.depth,
\r
10534 programStats.nr_moves-programStats.moves_left,
\r
10535 programStats.nr_moves, programStats.move_name,
\r
10536 ((float)programStats.score)/100.0, lst,
\r
10537 only_one_move(lst)?
\r
10538 xtra[programStats.got_fail] : "",
\r
10539 programStats.nodes, (int)nps, h, m, s, cs);
\r
10541 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10542 programStats.depth,
\r
10543 programStats.nr_moves-programStats.moves_left,
\r
10544 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
10546 only_one_move(lst)?
\r
10547 xtra[programStats.got_fail] : "",
\r
10548 programStats.nodes, (int)nps, h, m, s, cs);
\r
10551 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
10552 programStats.depth,
\r
10553 ((float)programStats.score)/100.0,
\r
10555 only_one_move(lst)?
\r
10556 xtra[programStats.got_fail] : "",
\r
10557 programStats.nodes, (int)nps, h, m, s, cs);
\r
10560 DisplayAnalysisText(buf);
\r
10564 DisplayComment(moveNumber, text)
\r
10568 char title[MSG_SIZ];
\r
10570 if( appData.autoDisplayComment ) {
\r
10571 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
10572 strcpy(title, "Comment");
\r
10574 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
10575 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
10576 parseList[moveNumber]);
\r
10579 CommentPopUp(title, text);
\r
10583 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
10584 * might be busy thinking or pondering. It can be omitted if your
\r
10585 * gnuchess is configured to stop thinking immediately on any user
\r
10586 * input. However, that gnuchess feature depends on the FIONREAD
\r
10587 * ioctl, which does not work properly on some flavors of Unix.
\r
10591 ChessProgramState *cps;
\r
10594 if (!cps->useSigint) return;
\r
10595 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
10596 switch (gameMode) {
\r
10597 case MachinePlaysWhite:
\r
10598 case MachinePlaysBlack:
\r
10599 case TwoMachinesPlay:
\r
10600 case IcsPlayingWhite:
\r
10601 case IcsPlayingBlack:
\r
10602 case AnalyzeMode:
\r
10603 case AnalyzeFile:
\r
10604 /* Skip if we know it isn't thinking */
\r
10605 if (!cps->maybeThinking) return;
\r
10606 if (appData.debugMode)
\r
10607 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
10608 InterruptChildProcess(cps->pr);
\r
10609 cps->maybeThinking = FALSE;
\r
10614 #endif /*ATTENTION*/
\r
10620 if (whiteTimeRemaining <= 0) {
\r
10621 if (!whiteFlag) {
\r
10622 whiteFlag = TRUE;
\r
10623 if (appData.icsActive) {
\r
10624 if (appData.autoCallFlag &&
\r
10625 gameMode == IcsPlayingBlack && !blackFlag) {
\r
10626 SendToICS(ics_prefix);
\r
10627 SendToICS("flag\n");
\r
10631 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
10633 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
10634 if (appData.autoCallFlag) {
\r
10635 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
10642 if (blackTimeRemaining <= 0) {
\r
10643 if (!blackFlag) {
\r
10644 blackFlag = TRUE;
\r
10645 if (appData.icsActive) {
\r
10646 if (appData.autoCallFlag &&
\r
10647 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
10648 SendToICS(ics_prefix);
\r
10649 SendToICS("flag\n");
\r
10653 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
10655 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
10656 if (appData.autoCallFlag) {
\r
10657 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
10668 CheckTimeControl()
\r
10670 if (!appData.clockMode || appData.icsActive ||
\r
10671 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
10673 if (timeIncrement >= 0) {
\r
10674 if (WhiteOnMove(forwardMostMove)) {
\r
10675 blackTimeRemaining += timeIncrement;
\r
10677 whiteTimeRemaining += timeIncrement;
\r
10681 * add time to clocks when time control is achieved
\r
10683 if (movesPerSession) {
\r
10684 switch ((forwardMostMove + 1) % (movesPerSession * 2)) {
\r
10686 /* White made time control */
\r
10687 whiteTimeRemaining += GetTimeControlForWhite();
\r
10690 /* Black made time control */
\r
10691 blackTimeRemaining += GetTimeControlForBlack();
\r
10700 DisplayBothClocks()
\r
10702 int wom = gameMode == EditPosition ?
\r
10703 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
10704 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
10705 DisplayBlackClock(blackTimeRemaining, !wom);
\r
10709 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
10710 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
10711 to use other calls if you don't. Clocks will be less accurate if
\r
10712 you have neither ftime nor gettimeofday.
\r
10715 /* Get the current time as a TimeMark */
\r
10720 #if HAVE_GETTIMEOFDAY
\r
10722 struct timeval timeVal;
\r
10723 struct timezone timeZone;
\r
10725 gettimeofday(&timeVal, &timeZone);
\r
10726 tm->sec = (long) timeVal.tv_sec;
\r
10727 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
10729 #else /*!HAVE_GETTIMEOFDAY*/
\r
10732 #include <sys/timeb.h>
\r
10733 struct timeb timeB;
\r
10736 tm->sec = (long) timeB.time;
\r
10737 tm->ms = (int) timeB.millitm;
\r
10739 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
10740 tm->sec = (long) time(NULL);
\r
10746 /* Return the difference in milliseconds between two
\r
10747 time marks. We assume the difference will fit in a long!
\r
10750 SubtractTimeMarks(tm2, tm1)
\r
10751 TimeMark *tm2, *tm1;
\r
10753 return 1000L*(tm2->sec - tm1->sec) +
\r
10754 (long) (tm2->ms - tm1->ms);
\r
10759 * Code to manage the game clocks.
\r
10761 * In tournament play, black starts the clock and then white makes a move.
\r
10762 * We give the human user a slight advantage if he is playing white---the
\r
10763 * clocks don't run until he makes his first move, so it takes zero time.
\r
10764 * Also, we don't account for network lag, so we could get out of sync
\r
10765 * with GNU Chess's clock -- but then, referees are always right.
\r
10768 static TimeMark tickStartTM;
\r
10769 static long intendedTickLength;
\r
10772 NextTickLength(timeRemaining)
\r
10773 long timeRemaining;
\r
10775 long nominalTickLength, nextTickLength;
\r
10777 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
10778 nominalTickLength = 100L;
\r
10780 nominalTickLength = 1000L;
\r
10781 nextTickLength = timeRemaining % nominalTickLength;
\r
10782 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
10784 return nextTickLength;
\r
10787 /* Stop clocks and reset to a fresh time control */
\r
10791 (void) StopClockTimer();
\r
10792 if (appData.icsActive) {
\r
10793 whiteTimeRemaining = blackTimeRemaining = 0;
\r
10795 whiteTimeRemaining = GetTimeControlForWhite();
\r
10796 blackTimeRemaining = GetTimeControlForBlack();
\r
10798 if (whiteFlag || blackFlag) {
\r
10799 DisplayTitle("");
\r
10800 whiteFlag = blackFlag = FALSE;
\r
10802 DisplayBothClocks();
\r
10805 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
10807 /* Decrement running clock by amount of time that has passed */
\r
10809 DecrementClocks()
\r
10811 long timeRemaining;
\r
10812 long lastTickLength, fudge;
\r
10815 if (!appData.clockMode) return;
\r
10816 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
10818 GetTimeMark(&now);
\r
10820 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
10822 /* Fudge if we woke up a little too soon */
\r
10823 fudge = intendedTickLength - lastTickLength;
\r
10824 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
10826 if (WhiteOnMove(forwardMostMove)) {
\r
10827 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
10828 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
10829 WhiteOnMove(currentMove));
\r
10831 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
10832 DisplayBlackClock(blackTimeRemaining - fudge,
\r
10833 !WhiteOnMove(currentMove));
\r
10836 if (CheckFlags()) return;
\r
10838 tickStartTM = now;
\r
10839 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
10840 StartClockTimer(intendedTickLength);
\r
10842 /* if the time remaining has fallen below the alarm threshold, sound the
\r
10843 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
10844 * with increment) the time remaining has increased to a level above the
\r
10845 * threshold, reset the alarm so it can sound again.
\r
10848 if (appData.icsActive && appData.icsAlarm) {
\r
10850 /* make sure we are dealing with the user's clock */
\r
10851 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
10852 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
10855 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
10856 alarmSounded = FALSE;
\r
10857 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
10858 PlayAlarmSound();
\r
10859 alarmSounded = TRUE;
\r
10865 /* A player has just moved, so stop the previously running
\r
10866 clock and (if in clock mode) start the other one.
\r
10867 We redisplay both clocks in case we're in ICS mode, because
\r
10868 ICS gives us an update to both clocks after every move.
\r
10869 Note that this routine is called *after* forwardMostMove
\r
10870 is updated, so the last fractional tick must be subtracted
\r
10871 from the color that is *not* on move now.
\r
10876 long lastTickLength;
\r
10878 int flagged = FALSE;
\r
10880 GetTimeMark(&now);
\r
10882 if (StopClockTimer() && appData.clockMode) {
\r
10883 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
10884 if (WhiteOnMove(forwardMostMove)) {
\r
10885 blackTimeRemaining -= lastTickLength;
\r
10887 whiteTimeRemaining -= lastTickLength;
\r
10889 /* [HGM] save time for PGN file if engine did not give it */
\r
10890 if(pvInfoList[forwardMostMove-1].time == -1)
\r
10891 pvInfoList[forwardMostMove-1].time = lastTickLength/100;
\r
10892 flagged = CheckFlags();
\r
10894 CheckTimeControl();
\r
10896 if (flagged || !appData.clockMode) return;
\r
10898 switch (gameMode) {
\r
10899 case MachinePlaysBlack:
\r
10900 case MachinePlaysWhite:
\r
10901 case BeginningOfGame:
\r
10902 if (pausing) return;
\r
10906 case PlayFromGameFile:
\r
10907 case IcsExamining:
\r
10914 tickStartTM = now;
\r
10915 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
10916 whiteTimeRemaining : blackTimeRemaining);
\r
10917 StartClockTimer(intendedTickLength);
\r
10921 /* Stop both clocks */
\r
10925 long lastTickLength;
\r
10928 if (!StopClockTimer()) return;
\r
10929 if (!appData.clockMode) return;
\r
10931 GetTimeMark(&now);
\r
10933 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
10934 if (WhiteOnMove(forwardMostMove)) {
\r
10935 whiteTimeRemaining -= lastTickLength;
\r
10936 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
10938 blackTimeRemaining -= lastTickLength;
\r
10939 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
10944 /* Start clock of player on move. Time may have been reset, so
\r
10945 if clock is already running, stop and restart it. */
\r
10949 (void) StopClockTimer(); /* in case it was running already */
\r
10950 DisplayBothClocks();
\r
10951 if (CheckFlags()) return;
\r
10953 if (!appData.clockMode) return;
\r
10954 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
10956 GetTimeMark(&tickStartTM);
\r
10957 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
10958 whiteTimeRemaining : blackTimeRemaining);
\r
10959 StartClockTimer(intendedTickLength);
\r
10966 long second, minute, hour, day;
\r
10968 static char buf[32];
\r
10970 if (ms > 0 && ms <= 9900) {
\r
10971 /* convert milliseconds to tenths, rounding up */
\r
10972 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
10974 sprintf(buf, " %03.1f ", tenths/10.0);
\r
10978 /* convert milliseconds to seconds, rounding up */
\r
10979 /* use floating point to avoid strangeness of integer division
\r
10980 with negative dividends on many machines */
\r
10981 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
10983 if (second < 0) {
\r
10985 second = -second;
\r
10988 day = second / (60 * 60 * 24);
\r
10989 second = second % (60 * 60 * 24);
\r
10990 hour = second / (60 * 60);
\r
10991 second = second % (60 * 60);
\r
10992 minute = second / 60;
\r
10993 second = second % 60;
\r
10996 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
10997 sign, day, hour, minute, second);
\r
10998 else if (hour > 0)
\r
10999 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
11001 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
11008 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
11011 StrStr(string, match)
\r
11012 char *string, *match;
\r
11016 length = strlen(match);
\r
11018 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
11019 if (!strncmp(match, string, length))
\r
11026 StrCaseStr(string, match)
\r
11027 char *string, *match;
\r
11029 int i, j, length;
\r
11031 length = strlen(match);
\r
11033 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
11034 for (j = 0; j < length; j++) {
\r
11035 if (ToLower(match[j]) != ToLower(string[j]))
\r
11038 if (j == length) return string;
\r
11044 #ifndef _amigados
\r
11046 StrCaseCmp(s1, s2)
\r
11052 c1 = ToLower(*s1++);
\r
11053 c2 = ToLower(*s2++);
\r
11054 if (c1 > c2) return 1;
\r
11055 if (c1 < c2) return -1;
\r
11056 if (c1 == NULLCHAR) return 0;
\r
11065 return isupper(c) ? tolower(c) : c;
\r
11073 return islower(c) ? toupper(c) : c;
\r
11075 #endif /* !_amigados */
\r
11083 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
11090 StrSavePtr(s, savePtr)
\r
11091 char *s, **savePtr;
\r
11096 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
11097 strcpy(*savePtr, s);
\r
11099 return(*savePtr);
\r
11107 char buf[MSG_SIZ];
\r
11109 clock = time((time_t *)NULL);
\r
11110 tm = localtime(&clock);
\r
11111 sprintf(buf, "%04d.%02d.%02d",
\r
11112 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
11113 return StrSave(buf);
\r
11118 PositionToFEN(move, useFEN960)
\r
11122 int i, j, fromX, fromY, toX, toY;
\r
11128 whiteToPlay = (gameMode == EditPosition) ?
\r
11129 !blackPlaysFirst : (move % 2 == 0);
\r
11132 /* Piece placement data */
\r
11133 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11135 for (j = 0; j < BOARD_WIDTH; j++) {
\r
11136 if (boards[move][i][j] == EmptySquare) {
\r
11139 if (emptycount > 0) {
\r
11140 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11141 *p++ = '0' + emptycount;
\r
11142 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11145 *p++ = PieceToChar(boards[move][i][j]);
\r
11148 if (emptycount > 0) {
\r
11149 if(emptycount<10) /* [HGM] can be >= 10 */
\r
11150 *p++ = '0' + emptycount;
\r
11151 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
11158 /* Active color */
\r
11159 *p++ = whiteToPlay ? 'w' : 'b';
\r
11162 /* HACK: we don't keep track of castling availability, so fake it! */
\r
11163 /* Tord! please fix with the aid of castlingRights[move][...] */
\r
11165 /* PUSH Fabien & Tord */
\r
11167 /* Declare all potential FRC castling rights (conservative) */
\r
11168 /* outermost rook on each side of the king */
\r
11170 if( gameInfo.variant == VariantFischeRandom ) {
\r
11175 /* White castling rights */
\r
11177 for (fk = 1; fk < BOARD_WIDTH-1; fk++) {
\r
11179 if (boards[move][0][fk] == WhiteKing) {
\r
11181 for (fr = BOARD_WIDTH-1; fr > fk; fr--) { /* H side */
\r
11182 if (boards[move][0][fr] == WhiteRook) {
\r
11183 *p++ = useFEN960 ? 'A' + fr : 'K';
\r
11188 for (fr = 0; fr < fk; fr++) { /* A side */
\r
11189 if (boards[move][0][fr] == WhiteRook) {
\r
11190 *p++ = useFEN960 ? 'A' + fr : 'Q';
\r
11197 /* Black castling rights */
\r
11199 for (fk = 1; fk < BOARD_WIDTH-1; fk++) {
\r
11201 if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {
\r
11203 for (fr = BOARD_WIDTH-1; fr > fk; fr--) { /* H side */
\r
11204 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11205 *p++ = useFEN960 ? 'a' + fr : 'k';
\r
11210 for (fr = 0; fr < fk; fr++) { /* A side */
\r
11211 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {
\r
11212 *p++ = useFEN960 ? 'a' + fr : 'q';
\r
11219 if (q == p) *p++ = '-'; /* No castling rights */
\r
11225 #ifdef OLDCASTLINGCODE
\r
11226 if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {
\r
11227 if (boards[move][0][BOARD_WIDTH-1] == WhiteRook) *p++ = 'K';
\r
11228 if (boards[move][0][0] == WhiteRook) *p++ = 'Q';
\r
11230 if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {
\r
11231 if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';
\r
11232 if (boards[move][BOARD_HEIGHT-1][0] == BlackRook) *p++ = 'q';
\r
11235 /* [HGM] write true castling rights */
\r
11236 if( nrCastlingRights == 6 ) {
\r
11237 if(castlingRights[move][0] == BOARD_WIDTH-1 &&
\r
11238 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
11239 if(castlingRights[move][1] == 0 &&
\r
11240 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
11241 if(castlingRights[move][3] == BOARD_WIDTH-1 &&
\r
11242 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
11243 if(castlingRights[move][4] == 0 &&
\r
11244 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
11247 if (q == p) *p++ = '-';
\r
11251 /* POP Fabien & Tord */
\r
11253 /* En passant target square */
\r
11254 if (move > backwardMostMove) {
\r
11255 fromX = moveList[move - 1][0] - 'a';
\r
11256 fromY = moveList[move - 1][1] - ONE;
\r
11257 toX = moveList[move - 1][2] - 'a';
\r
11258 toY = moveList[move - 1][3] - ONE;
\r
11259 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
11260 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
11261 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
11263 /* 2-square pawn move just happened */
\r
11264 *p++ = toX + 'a';
\r
11265 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
11273 /* [HGM] find reversible plies */
\r
11274 { int i = 0, j=move;
\r
11276 if (appData.debugMode) { int k;
\r
11277 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
11278 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
11279 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
11283 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
11284 if( j == backwardMostMove ) i += initialRulePlies;
\r
11285 sprintf(p, " %d", i);
\r
11286 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
11288 /* Fullmove number */
\r
11289 sprintf(p, " %d", (move / 2) + 1);
\r
11291 return StrSave(buf);
\r
11295 ParseFEN(board, blackPlaysFirst, fen)
\r
11297 int *blackPlaysFirst;
\r
11306 /* Piece placement data */
\r
11307 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11310 if (*p == '/' || *p == ' ') {
\r
11311 if (*p == '/') p++;
\r
11312 emptycount = BOARD_WIDTH - j;
\r
11313 while (emptycount--) board[i][j++] = EmptySquare;
\r
11315 #if(BOARD_SIZE >= 10)
\r
11316 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
11317 p++; emptycount=10;
\r
11318 if (j + emptycount > BOARD_WIDTH) return FALSE;
\r
11319 while (emptycount--) board[i][j++] = EmptySquare;
\r
11321 } else if (isdigit(*p)) {
\r
11322 emptycount = *p++ - '0';
\r
11323 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
11324 if (j + emptycount > BOARD_WIDTH) return FALSE;
\r
11325 while (emptycount--) board[i][j++] = EmptySquare;
\r
11326 } else if (isalpha(*p)) {
\r
11327 if (j >= BOARD_WIDTH) return FALSE;
\r
11328 board[i][j++] = CharToPiece(*p++);
\r
11334 while (*p == '/' || *p == ' ') p++;
\r
11336 /* Active color */
\r
11339 *blackPlaysFirst = FALSE;
\r
11342 *blackPlaysFirst = TRUE;
\r
11348 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
11349 /* return the extra info in global variiables */
\r
11351 /* set defaults in case FEN is incomplete */
\r
11352 FENepStatus = EP_UNKNOWN;
\r
11353 for(i=0; i<nrCastlingRights; i++ ) {
\r
11354 FENcastlingRights[i] = initialRights[i];
\r
11355 } /* assume possible unless obviously impossible */
\r
11356 if(board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
11357 if(board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
11358 if(board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
11359 if(board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
11360 if(board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
11361 if(board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
11362 FENrulePlies = 0;
\r
11364 while(*p==' ') p++;
\r
11366 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11367 /* castling indicator present, so default is impossible */
\r
11368 for(i=0; i<nrCastlingRights; i++ ) {
\r
11369 FENcastlingRights[i] = -1;
\r
11372 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
11375 FENcastlingRights[0] = BOARD_WIDTH-1;
\r
11376 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11379 FENcastlingRights[1] = 0;
\r
11380 FENcastlingRights[2] = BOARD_WIDTH>>1;
\r
11383 FENcastlingRights[3] = BOARD_WIDTH-1;
\r
11384 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11387 FENcastlingRights[4] = 0;
\r
11388 FENcastlingRights[5] = BOARD_WIDTH>>1;
\r
11394 while(*p==' ') p++;
\r
11398 p++; FENepStatus = EP_NONE;
\r
11400 char c = *p++ - 'a';
\r
11402 if(c < 0 || c >= BOARD_WIDTH) return TRUE;
\r
11403 if(*p >= '0' && *p <='9') *p++;
\r
11407 if(sscanf(p, "%d", &i) == 1) {
\r
11408 FENrulePlies = i; /* 50-move ply counter */
\r
11409 /* (The move number is still ignored) */
\r
11416 EditPositionPasteFEN(char *fen)
\r
11418 if (fen != NULL) {
\r
11419 Board initial_position;
\r
11421 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
11422 DisplayError("Bad FEN position in clipboard", 0);
\r
11425 int savedBlackPlaysFirst = blackPlaysFirst;
\r
11426 EditPositionEvent();
\r
11427 blackPlaysFirst = savedBlackPlaysFirst;
\r
11428 CopyBoard(boards[0], initial_position);
\r
11429 /* [HGM] copy FEN attributes as well */
\r
11431 initialRulePlies = FENrulePlies;
\r
11432 epStatus[0] = FENepStatus;
\r
11433 for( i=0; i<nrCastlingRights; i++ )
\r
11434 castlingRights[0][i] = FENcastlingRights[i];
\r
11436 EditPositionDone();
\r
11437 DisplayBothClocks();
\r
11438 DrawPosition(FALSE, boards[currentMove]);
\r