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
69 #include <sys/types.h>
\r
70 #include <sys/stat.h>
\r
74 # include <stdlib.h>
\r
75 # include <string.h>
\r
76 #else /* not STDC_HEADERS */
\r
78 # include <string.h>
\r
79 # else /* not HAVE_STRING_H */
\r
80 # include <strings.h>
\r
81 # endif /* not HAVE_STRING_H */
\r
82 #endif /* not STDC_HEADERS */
\r
84 #if HAVE_SYS_FCNTL_H
\r
85 # include <sys/fcntl.h>
\r
86 #else /* not HAVE_SYS_FCNTL_H */
\r
89 # endif /* HAVE_FCNTL_H */
\r
90 #endif /* not HAVE_SYS_FCNTL_H */
\r
92 #if TIME_WITH_SYS_TIME
\r
93 # include <sys/time.h>
\r
96 # if HAVE_SYS_TIME_H
\r
97 # include <sys/time.h>
\r
103 #if defined(_amigados) && !defined(__GNUC__)
\r
105 int tz_minuteswest;
\r
108 extern int gettimeofday(struct timeval *, struct timezone *);
\r
112 # include <unistd.h>
\r
115 #include "common.h"
\r
116 #include "frontend.h"
\r
117 #include "backend.h"
\r
118 #include "parser.h"
\r
121 # include "zippy.h"
\r
123 #include "backendz.h"
\r
125 /* A point in time */
\r
127 long sec; /* Assuming this is >= 32 bits */
\r
128 int ms; /* Assuming this is >= 16 bits */
\r
131 /* Search stats from chessprogram */
\r
133 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
\r
134 int depth; /* Current search depth */
\r
135 int nr_moves; /* Total nr of root moves */
\r
136 int moves_left; /* Moves remaining to be searched */
\r
137 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
\r
138 unsigned long nodes; /* # of nodes searched */
\r
139 int time; /* Search time (centiseconds) */
\r
140 int score; /* Score (centipawns) */
\r
141 int got_only_move; /* If last msg was "(only move)" */
\r
142 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
\r
143 int ok_to_send; /* handshaking between send & recv */
\r
144 int line_is_book; /* 1 if movelist is book moves */
\r
145 int seen_stat; /* 1 if we've seen the stat01: line */
\r
146 } ChessProgramStats;
\r
148 int establish P((void));
\r
149 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
150 char *buf, int count, int error));
\r
151 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
152 char *buf, int count, int error));
\r
153 void SendToICS P((char *s));
\r
154 void SendToICSDelayed P((char *s, long msdelay));
\r
155 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
156 int toX, int toY));
\r
157 void InitPosition P((int redraw));
\r
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
159 int AutoPlayOneMove P((void));
\r
160 int LoadGameOneMove P((ChessMove readAhead));
\r
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
162 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
163 int SavePositionToFile P((char *filename));
\r
164 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
166 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
167 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
168 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
169 /*char*/int promoChar));
\r
170 void BackwardInner P((int target));
\r
171 void ForwardInner P((int target));
\r
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
173 void EditPositionDone P((void));
\r
174 void PrintOpponents P((FILE *fp));
\r
175 void PrintPosition P((FILE *fp, int move));
\r
176 void StartChessProgram P((ChessProgramState *cps));
\r
177 void SendToProgram P((char *message, ChessProgramState *cps));
\r
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
180 char *buf, int count, int error));
\r
181 void SendTimeControl P((ChessProgramState *cps,
\r
182 int mps, long tc, int inc, int sd, int st));
\r
183 char *TimeControlTagValue P((void));
\r
184 void Attention P((ChessProgramState *cps));
\r
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
186 void ResurrectChessProgram P((void));
\r
187 void DisplayComment P((int moveNumber, char *text));
\r
188 void DisplayMove P((int moveNumber));
\r
189 void DisplayAnalysis P((void));
\r
191 void ParseGameHistory P((char *game));
\r
192 void ParseBoard12 P((char *string));
\r
193 void StartClocks P((void));
\r
194 void SwitchClocks P((void));
\r
195 void StopClocks P((void));
\r
196 void ResetClocks P((void));
\r
197 char *PGNDate P((void));
\r
198 void SetGameInfo P((void));
\r
199 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
200 int RegisterMove P((void));
\r
201 void MakeRegisteredMove P((void));
\r
202 void TruncateGame P((void));
\r
203 int looking_at P((char *, int *, char *));
\r
204 void CopyPlayerNameIntoFileName P((char **, char *));
\r
205 char *SavePart P((char *));
\r
206 int SaveGameOldStyle P((FILE *));
\r
207 int SaveGamePGN P((FILE *));
\r
208 void GetTimeMark P((TimeMark *));
\r
209 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
210 int CheckFlags P((void));
\r
211 long NextTickLength P((long));
\r
212 void CheckTimeControl P((void));
\r
213 void show_bytes P((FILE *, char *, int));
\r
214 int string_to_rating P((char *str));
\r
215 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
216 void InitBackEnd3 P((void));
\r
217 void FeatureDone P((ChessProgramState* cps, int val));
\r
218 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
222 extern int tinyLayout, smallLayout;
\r
223 static ChessProgramStats programStats;
\r
224 static int exiting = 0; /* [HGM] moved to top */
\r
225 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;
\r
226 extern int startedFromPositionFile;
\r
227 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
228 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
229 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
230 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
232 /* States for ics_getting_history */
\r
234 #define H_REQUESTED 1
\r
235 #define H_GOT_REQ_HEADER 2
\r
236 #define H_GOT_UNREQ_HEADER 3
\r
237 #define H_GETTING_MOVES 4
\r
238 #define H_GOT_UNWANTED_HEADER 5
\r
240 /* whosays values for GameEnds */
\r
242 #define GE_ENGINE 1
\r
243 #define GE_PLAYER 2
\r
245 #define GE_XBOARD 4
\r
246 #define GE_ENGINE1 5
\r
247 #define GE_ENGINE2 6
\r
249 /* Maximum number of games in a cmail message */
\r
250 #define CMAIL_MAX_GAMES 20
\r
252 /* Different types of move when calling RegisterMove */
\r
253 #define CMAIL_MOVE 0
\r
254 #define CMAIL_RESIGN 1
\r
255 #define CMAIL_DRAW 2
\r
256 #define CMAIL_ACCEPT 3
\r
258 /* Different types of result to remember for each game */
\r
259 #define CMAIL_NOT_RESULT 0
\r
260 #define CMAIL_OLD_RESULT 1
\r
261 #define CMAIL_NEW_RESULT 2
\r
263 /* Telnet protocol constants */
\r
264 #define TN_WILL 0373
\r
265 #define TN_WONT 0374
\r
267 #define TN_DONT 0376
\r
268 #define TN_IAC 0377
\r
269 #define TN_ECHO 0001
\r
270 #define TN_SGA 0003
\r
274 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
276 assert( dst != NULL );
\r
277 assert( src != NULL );
\r
278 assert( count > 0 );
\r
280 strncpy( dst, src, count );
\r
281 dst[ count-1 ] = '\0';
\r
285 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
289 assert( dst != NULL );
\r
290 assert( src != NULL );
\r
291 assert( count > 0 );
\r
293 dst_len = strlen(dst);
\r
295 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
297 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
302 /* Fake up flags for now, as we aren't keeping track of castling
\r
303 availability yet. [HGM] Change of logic: the flag now only
\r
304 indicates the type of castlings allowed by the rule of the game.
\r
305 The actual rights themselves are maintained in the array
\r
306 castlingRights, as part of the game history, and are not probed
\r
312 int flags = F_ALL_CASTLE_OK;
\r
313 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
314 switch (gameInfo.variant) {
\r
315 case VariantSuicide:
\r
316 case VariantGiveaway:
\r
317 flags |= F_IGNORE_CHECK;
\r
318 flags &= ~F_ALL_CASTLE_OK;
\r
320 case VariantAtomic:
\r
321 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
323 case VariantKriegspiel:
\r
324 flags |= F_KRIEGSPIEL_CAPTURE;
\r
326 case VariantCapaRandom:
\r
327 case VariantFischeRandom:
\r
328 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
329 case VariantNoCastle:
\r
330 case VariantShatranj:
\r
331 case VariantCourier:
\r
332 flags &= ~F_ALL_CASTLE_OK;
\r
340 FILE *gameFileFP, *debugFP;
\r
343 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
344 into a fixed-size buffer. Because of this, we must be prepared to
\r
345 receive strings as long as the size of the input buffer, which is currently
\r
346 set to 4K for Windows and 8K for the rest.
\r
347 So, we must either allocate sufficiently large buffers here, or
\r
348 reduce the size of the input buffer in the input reading part.
\r
351 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
352 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
353 char thinkOutput1[MSG_SIZ*10];
\r
355 ChessProgramState first, second;
\r
357 /* premove variables */
\r
358 int premoveToX = 0;
\r
359 int premoveToY = 0;
\r
360 int premoveFromX = 0;
\r
361 int premoveFromY = 0;
\r
362 int premovePromoChar = 0;
\r
363 int gotPremove = 0;
\r
364 Boolean alarmSounded;
\r
365 /* end premove variables */
\r
367 #define ICS_GENERIC 0
\r
370 #define ICS_CHESSNET 3 /* not really supported */
\r
371 int ics_type = ICS_GENERIC;
\r
372 char *ics_prefix = "$";
\r
374 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
375 int pauseExamForwardMostMove = 0;
\r
376 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
377 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
378 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
379 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
380 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
381 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
382 int whiteFlag = FALSE, blackFlag = FALSE;
\r
383 int userOfferedDraw = FALSE;
\r
384 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
385 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
386 int cmailMoveType[CMAIL_MAX_GAMES];
\r
387 long ics_clock_paused = 0;
\r
388 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
389 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
390 GameMode gameMode = BeginningOfGame;
\r
391 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
392 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
393 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
394 int hiddenThinkOutputState = 0; /* [AS] */
\r
395 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
396 int adjudicateLossPlies = 6;
\r
397 char white_holding[64], black_holding[64];
\r
398 TimeMark lastNodeCountTime;
\r
399 long lastNodeCount=0;
\r
400 int have_sent_ICS_logon = 0;
\r
401 int movesPerSession;
\r
402 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
403 long timeControl_2; /* [AS] Allow separate time controls */
\r
404 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
405 long timeRemaining[2][MAX_MOVES];
\r
407 TimeMark programStartTime;
\r
408 char ics_handle[MSG_SIZ];
\r
409 int have_set_title = 0;
\r
411 /* animateTraining preserves the state of appData.animate
\r
412 * when Training mode is activated. This allows the
\r
413 * response to be animated when appData.animate == TRUE and
\r
414 * appData.animateDragging == TRUE.
\r
416 Boolean animateTraining;
\r
422 Board boards[MAX_MOVES];
\r
423 /* [HGM] Following 7 needed for accurate legality tests: */
\r
424 char epStatus[MAX_MOVES];
\r
425 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
426 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
427 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
428 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
429 int initialRulePlies, FENrulePlies;
\r
431 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
433 int shuffleOpenings;
\r
435 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
436 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
437 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
438 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
439 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
442 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
443 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
444 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
445 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
446 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
449 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
450 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
451 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
452 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
453 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
456 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
457 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
459 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
463 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
464 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
465 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
466 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
467 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
471 #if (BOARD_SIZE>=10)
\r
472 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
473 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
474 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
475 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
476 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
479 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
480 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
481 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
482 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
483 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
486 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
487 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
488 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
489 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
490 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
493 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
494 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
495 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
496 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
497 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
501 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
503 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
505 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
508 #define GothicArray CapablancaArray
\r
512 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
514 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
516 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
519 #define FalconArray CapablancaArray
\r
522 #else // !(BOARD_SIZE>=10)
\r
523 #define XiangqiPosition FIDEArray
\r
524 #define CapablancaArray FIDEArray
\r
525 #define GothicArray FIDEArray
\r
526 #endif // !(BOARD_SIZE>=10)
\r
528 #if (BOARD_SIZE>=12)
\r
529 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
530 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
531 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
532 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
533 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
535 #else // !(BOARD_SIZE>=12)
\r
536 #define CourierArray CapablancaArray
\r
537 #endif // !(BOARD_SIZE>=12)
\r
540 Board initialPosition;
\r
543 /* Convert str to a rating. Checks for special cases of "----",
\r
545 "++++", etc. Also strips ()'s */
\r
547 string_to_rating(str)
\r
550 while(*str && !isdigit(*str)) ++str;
\r
552 return 0; /* One of the special "no rating" cases */
\r
558 ClearProgramStats()
\r
560 /* Init programStats */
\r
561 programStats.movelist[0] = 0;
\r
562 programStats.depth = 0;
\r
563 programStats.nr_moves = 0;
\r
564 programStats.moves_left = 0;
\r
565 programStats.nodes = 0;
\r
566 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
567 programStats.score = 0;
\r
568 programStats.got_only_move = 0;
\r
569 programStats.got_fail = 0;
\r
570 programStats.line_is_book = 0;
\r
576 int matched, min, sec;
\r
578 GetTimeMark(&programStartTime);
\r
580 ClearProgramStats();
\r
581 programStats.ok_to_send = 1;
\r
582 programStats.seen_stat = 0;
\r
585 * Initialize game list
\r
587 ListNew(&gameList);
\r
591 * Internet chess server status
\r
593 if (appData.icsActive) {
\r
594 appData.matchMode = FALSE;
\r
595 appData.matchGames = 0;
\r
597 appData.noChessProgram = !appData.zippyPlay;
\r
599 appData.zippyPlay = FALSE;
\r
600 appData.zippyTalk = FALSE;
\r
601 appData.noChessProgram = TRUE;
\r
603 if (*appData.icsHelper != NULLCHAR) {
\r
604 appData.useTelnet = TRUE;
\r
605 appData.telnetProgram = appData.icsHelper;
\r
608 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
611 /* [AS] Initialize pv info list [HGM] and game state */
\r
615 for( i=0; i<MAX_MOVES; i++ ) {
\r
616 pvInfoList[i].depth = -1;
\r
617 epStatus[i]=EP_NONE;
\r
618 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
623 * Parse timeControl resource
\r
625 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
626 appData.movesPerSession)) {
\r
628 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
629 DisplayFatalError(buf, 0, 2);
\r
633 * Parse searchTime resource
\r
635 if (*appData.searchTime != NULLCHAR) {
\r
636 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
637 if (matched == 1) {
\r
638 searchTime = min * 60;
\r
639 } else if (matched == 2) {
\r
640 searchTime = min * 60 + sec;
\r
643 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
644 DisplayFatalError(buf, 0, 2);
\r
648 /* [AS] Adjudication threshold */
\r
649 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
651 first.which = "first";
\r
652 second.which = "second";
\r
653 first.maybeThinking = second.maybeThinking = FALSE;
\r
654 first.pr = second.pr = NoProc;
\r
655 first.isr = second.isr = NULL;
\r
656 first.sendTime = second.sendTime = 2;
\r
657 first.sendDrawOffers = 1;
\r
658 if (appData.firstPlaysBlack) {
\r
659 first.twoMachinesColor = "black\n";
\r
660 second.twoMachinesColor = "white\n";
\r
662 first.twoMachinesColor = "white\n";
\r
663 second.twoMachinesColor = "black\n";
\r
665 first.program = appData.firstChessProgram;
\r
666 second.program = appData.secondChessProgram;
\r
667 first.host = appData.firstHost;
\r
668 second.host = appData.secondHost;
\r
669 first.dir = appData.firstDirectory;
\r
670 second.dir = appData.secondDirectory;
\r
671 first.other = &second;
\r
672 second.other = &first;
\r
673 first.initString = appData.initString;
\r
674 second.initString = appData.secondInitString;
\r
675 first.computerString = appData.firstComputerString;
\r
676 second.computerString = appData.secondComputerString;
\r
677 first.useSigint = second.useSigint = TRUE;
\r
678 first.useSigterm = second.useSigterm = TRUE;
\r
679 first.reuse = appData.reuseFirst;
\r
680 second.reuse = appData.reuseSecond;
\r
681 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
682 second.nps = appData.secondNPS;
\r
683 first.useSetboard = second.useSetboard = FALSE;
\r
684 first.useSAN = second.useSAN = FALSE;
\r
685 first.usePing = second.usePing = FALSE;
\r
686 first.lastPing = second.lastPing = 0;
\r
687 first.lastPong = second.lastPong = 0;
\r
688 first.usePlayother = second.usePlayother = FALSE;
\r
689 first.useColors = second.useColors = TRUE;
\r
690 first.useUsermove = second.useUsermove = FALSE;
\r
691 first.sendICS = second.sendICS = FALSE;
\r
692 first.sendName = second.sendName = appData.icsActive;
\r
693 first.sdKludge = second.sdKludge = FALSE;
\r
694 first.stKludge = second.stKludge = FALSE;
\r
695 TidyProgramName(first.program, first.host, first.tidy);
\r
696 TidyProgramName(second.program, second.host, second.tidy);
\r
697 first.matchWins = second.matchWins = 0;
\r
698 strcpy(first.variants, appData.variant);
\r
699 strcpy(second.variants, appData.variant);
\r
700 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
701 first.analyzing = second.analyzing = FALSE;
\r
702 first.initDone = second.initDone = FALSE;
\r
704 /* New features added by Tord: */
\r
705 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
706 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
707 /* End of new features added by Tord. */
\r
709 /* [HGM] time odds: set factor for each machine */
\r
710 first.timeOdds = appData.firstTimeOdds;
\r
711 second.timeOdds = appData.secondTimeOdds;
\r
713 if(appData.timeOddsMode) {
\r
714 norm = first.timeOdds;
\r
715 if(norm > second.timeOdds) norm = second.timeOdds;
\r
717 first.timeOdds /= norm;
\r
718 second.timeOdds /= norm;
\r
721 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
722 first.accumulateTC = appData.firstAccumulateTC;
\r
723 second.accumulateTC = appData.secondAccumulateTC;
\r
724 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
727 first.debug = second.debug = FALSE;
\r
728 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
730 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
731 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
732 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
733 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
734 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
735 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
737 if (appData.firstProtocolVersion > PROTOVER ||
\r
738 appData.firstProtocolVersion < 1) {
\r
740 sprintf(buf, "protocol version %d not supported",
\r
741 appData.firstProtocolVersion);
\r
742 DisplayFatalError(buf, 0, 2);
\r
744 first.protocolVersion = appData.firstProtocolVersion;
\r
747 if (appData.secondProtocolVersion > PROTOVER ||
\r
748 appData.secondProtocolVersion < 1) {
\r
750 sprintf(buf, "protocol version %d not supported",
\r
751 appData.secondProtocolVersion);
\r
752 DisplayFatalError(buf, 0, 2);
\r
754 second.protocolVersion = appData.secondProtocolVersion;
\r
757 if (appData.icsActive) {
\r
758 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
759 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
760 appData.clockMode = FALSE;
\r
761 first.sendTime = second.sendTime = 0;
\r
765 /* Override some settings from environment variables, for backward
\r
766 compatibility. Unfortunately it's not feasible to have the env
\r
767 vars just set defaults, at least in xboard. Ugh.
\r
769 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
774 if (appData.noChessProgram) {
\r
775 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
776 + strlen(PATCHLEVEL));
\r
777 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
781 while (*q != ' ' && *q != NULLCHAR) q++;
\r
783 while (p > first.program && *(p-1) != '/') p--;
\r
784 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
785 + strlen(PATCHLEVEL) + (q - p));
\r
786 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
787 strncat(programVersion, p, q - p);
\r
790 if (!appData.icsActive) {
\r
792 /* Check for variants that are supported only in ICS mode,
\r
793 or not at all. Some that are accepted here nevertheless
\r
794 have bugs; see comments below.
\r
796 VariantClass variant = StringToVariant(appData.variant);
\r
798 case VariantBughouse: /* need four players and two boards */
\r
799 case VariantKriegspiel: /* need to hide pieces and move details */
\r
800 /* case VariantFischeRandom: (Fabien: moved below) */
\r
801 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
802 DisplayFatalError(buf, 0, 2);
\r
805 case VariantUnknown:
\r
806 case VariantLoadable:
\r
816 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
817 DisplayFatalError(buf, 0, 2);
\r
820 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
821 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
822 case VariantGothic: /* [HGM] should work */
\r
823 case VariantCapablanca: /* [HGM] should work */
\r
824 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
825 case VariantShogi: /* [HGM] drops not tested for legality */
\r
826 case VariantKnightmate: /* [HGM] should work */
\r
827 case VariantCylinder: /* [HGM] untested */
\r
828 case VariantFalcon: /* [HGM] untested */
\r
829 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
830 offboard interposition not understood */
\r
831 case VariantNormal: /* definitely works! */
\r
832 case VariantWildCastle: /* pieces not automatically shuffled */
\r
833 case VariantNoCastle: /* pieces not automatically shuffled */
\r
834 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
835 case VariantLosers: /* should work except for win condition,
\r
836 and doesn't know captures are mandatory */
\r
837 case VariantSuicide: /* should work except for win condition,
\r
838 and doesn't know captures are mandatory */
\r
839 case VariantGiveaway: /* should work except for win condition,
\r
840 and doesn't know captures are mandatory */
\r
841 case VariantTwoKings: /* should work */
\r
842 case VariantAtomic: /* should work except for win condition */
\r
843 case Variant3Check: /* should work except for win condition */
\r
844 case VariantShatranj: /* should work except for all win conditions */
\r
845 case VariantBerolina: /* might work if TestLegality is off */
\r
846 case VariantCapaRandom: /* should work */
\r
852 int NextIntegerFromString( char ** str, long * value )
\r
857 while( *s == ' ' || *s == '\t' ) {
\r
863 if( *s >= '0' && *s <= '9' ) {
\r
864 while( *s >= '0' && *s <= '9' ) {
\r
865 *value = *value * 10 + (*s - '0');
\r
877 int NextTimeControlFromString( char ** str, long * value )
\r
880 int result = NextIntegerFromString( str, &temp );
\r
882 if( result == 0 ) {
\r
883 *value = temp * 60; /* Minutes */
\r
884 if( **str == ':' ) {
\r
886 result = NextIntegerFromString( str, &temp );
\r
887 *value += temp; /* Seconds */
\r
894 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
895 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
896 int result = -1; long temp, temp2;
\r
898 if(**str != '+') return -1; // old params remain in force!
\r
900 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
903 /* time only: incremental or sudden-death time control */
\r
904 if(**str == '+') { /* increment follows; read it */
\r
906 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
907 *inc = temp2 * 1000;
\r
909 *moves = 0; *tc = temp * 1000;
\r
911 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
913 (*str)++; /* classical time control */
\r
914 result = NextTimeControlFromString( str, &temp2);
\r
917 *tc = temp2 * 1000;
\r
923 int GetTimeQuota(int movenr)
\r
924 { /* [HGM] get time to add from the multi-session time-control string */
\r
925 int moves=1; /* kludge to force reading of first session */
\r
926 long time, increment;
\r
927 char *s = fullTimeControlString;
\r
929 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
931 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
932 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
933 if(movenr == -1) return time; /* last move before new session */
\r
934 if(!moves) return increment; /* current session is incremental */
\r
935 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
936 } while(movenr >= -1); /* try again for next session */
\r
938 return 0; // no new time quota on this move
\r
942 ParseTimeControl(tc, ti, mps)
\r
948 int matched, min, sec;
\r
950 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
951 if (matched == 1) {
\r
952 timeControl = min * 60 * 1000;
\r
953 } else if (matched == 2) {
\r
954 timeControl = (min * 60 + sec) * 1000;
\r
963 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
966 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
967 else sprintf(buf, "+%s+%d", tc, ti);
\r
970 sprintf(buf, "+%d/%s", mps, tc);
\r
971 else sprintf(buf, "+%s", tc);
\r
973 fullTimeControlString = StrSave(buf);
\r
975 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
980 /* Parse second time control */
\r
983 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
991 timeControl_2 = tc2 * 1000;
\r
1001 timeControl = tc1 * 1000;
\r
1005 timeIncrement = ti * 1000; /* convert to ms */
\r
1006 movesPerSession = 0;
\r
1008 timeIncrement = 0;
\r
1009 movesPerSession = mps;
\r
1017 if (appData.debugMode) {
\r
1018 fprintf(debugFP, "%s\n", programVersion);
\r
1021 if (appData.matchGames > 0) {
\r
1022 appData.matchMode = TRUE;
\r
1023 } else if (appData.matchMode) {
\r
1024 appData.matchGames = 1;
\r
1026 Reset(TRUE, FALSE);
\r
1027 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1030 /* kludge: allow timeout for initial "feature" commands */
\r
1032 DisplayMessage("", "Starting chess program");
\r
1033 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1038 InitBackEnd3 P((void))
\r
1040 GameMode initialMode;
\r
1041 char buf[MSG_SIZ];
\r
1044 InitChessProgram(&first, startedFromSetupPosition);
\r
1046 if (appData.icsActive) {
\r
1047 err = establish();
\r
1049 if (*appData.icsCommPort != NULLCHAR) {
\r
1050 sprintf(buf, "Could not open comm port %s",
\r
1051 appData.icsCommPort);
\r
1053 sprintf(buf, "Could not connect to host %s, port %s",
\r
1054 appData.icsHost, appData.icsPort);
\r
1056 DisplayFatalError(buf, err, 1);
\r
1061 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1063 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1064 } else if (appData.noChessProgram) {
\r
1070 if (*appData.cmailGameName != NULLCHAR) {
\r
1072 OpenLoopback(&cmailPR);
\r
1074 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1078 DisplayMessage("", "");
\r
1079 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1080 initialMode = BeginningOfGame;
\r
1081 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1082 initialMode = TwoMachinesPlay;
\r
1083 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1084 initialMode = AnalyzeFile;
\r
1085 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1086 initialMode = AnalyzeMode;
\r
1087 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1088 initialMode = MachinePlaysWhite;
\r
1089 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1090 initialMode = MachinePlaysBlack;
\r
1091 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1092 initialMode = EditGame;
\r
1093 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1094 initialMode = EditPosition;
\r
1095 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1096 initialMode = Training;
\r
1098 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
1099 DisplayFatalError(buf, 0, 2);
\r
1103 if (appData.matchMode) {
\r
1104 /* Set up machine vs. machine match */
\r
1105 if (appData.noChessProgram) {
\r
1106 DisplayFatalError("Can't have a match with no chess programs",
\r
1112 if (*appData.loadGameFile != NULLCHAR) {
\r
1113 if (!LoadGameFromFile(appData.loadGameFile,
\r
1114 appData.loadGameIndex,
\r
1115 appData.loadGameFile, FALSE)) {
\r
1116 DisplayFatalError("Bad game file", 0, 1);
\r
1119 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1120 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1121 appData.loadPositionIndex,
\r
1122 appData.loadPositionFile)) {
\r
1123 DisplayFatalError("Bad position file", 0, 1);
\r
1127 TwoMachinesEvent();
\r
1128 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1129 /* Set up cmail mode */
\r
1130 ReloadCmailMsgEvent(TRUE);
\r
1132 /* Set up other modes */
\r
1133 if (initialMode == AnalyzeFile) {
\r
1134 if (*appData.loadGameFile == NULLCHAR) {
\r
1135 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1139 if (*appData.loadGameFile != NULLCHAR) {
\r
1140 (void) LoadGameFromFile(appData.loadGameFile,
\r
1141 appData.loadGameIndex,
\r
1142 appData.loadGameFile, TRUE);
\r
1143 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1144 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1145 appData.loadPositionIndex,
\r
1146 appData.loadPositionFile);
\r
1147 /* [HGM] try to make self-starting even after FEN load */
\r
1148 /* to allow automatic setup of fairy variants with wtm */
\r
1149 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1150 gameMode = BeginningOfGame;
\r
1151 setboardSpoiledMachineBlack = 1;
\r
1153 /* [HGM] loadPos: make that every new game uses the setup */
\r
1154 /* from file as long as we do not switch variant */
\r
1155 if(!blackPlaysFirst) { int i;
\r
1156 startedFromPositionFile = TRUE;
\r
1157 CopyBoard(filePosition, boards[0]);
\r
1158 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1161 if (initialMode == AnalyzeMode) {
\r
1162 if (appData.noChessProgram) {
\r
1163 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1166 if (appData.icsActive) {
\r
1167 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1170 AnalyzeModeEvent();
\r
1171 } else if (initialMode == AnalyzeFile) {
\r
1172 ShowThinkingEvent(TRUE);
\r
1173 AnalyzeFileEvent();
\r
1174 AnalysisPeriodicEvent(1);
\r
1175 } else if (initialMode == MachinePlaysWhite) {
\r
1176 if (appData.noChessProgram) {
\r
1177 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1181 if (appData.icsActive) {
\r
1182 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1186 MachineWhiteEvent();
\r
1187 } else if (initialMode == MachinePlaysBlack) {
\r
1188 if (appData.noChessProgram) {
\r
1189 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1193 if (appData.icsActive) {
\r
1194 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1198 MachineBlackEvent();
\r
1199 } else if (initialMode == TwoMachinesPlay) {
\r
1200 if (appData.noChessProgram) {
\r
1201 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1205 if (appData.icsActive) {
\r
1206 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1210 TwoMachinesEvent();
\r
1211 } else if (initialMode == EditGame) {
\r
1213 } else if (initialMode == EditPosition) {
\r
1214 EditPositionEvent();
\r
1215 } else if (initialMode == Training) {
\r
1216 if (*appData.loadGameFile == NULLCHAR) {
\r
1217 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1226 * Establish will establish a contact to a remote host.port.
\r
1227 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1228 * used to talk to the host.
\r
1229 * Returns 0 if okay, error code if not.
\r
1234 char buf[MSG_SIZ];
\r
1236 if (*appData.icsCommPort != NULLCHAR) {
\r
1237 /* Talk to the host through a serial comm port */
\r
1238 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1240 } else if (*appData.gateway != NULLCHAR) {
\r
1241 if (*appData.remoteShell == NULLCHAR) {
\r
1242 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1243 sprintf(buf, "%s %s %s",
\r
1244 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1245 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1248 /* Use the rsh program to run telnet program on a gateway host */
\r
1249 if (*appData.remoteUser == NULLCHAR) {
\r
1250 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1251 appData.gateway, appData.telnetProgram,
\r
1252 appData.icsHost, appData.icsPort);
\r
1254 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1255 appData.remoteShell, appData.gateway,
\r
1256 appData.remoteUser, appData.telnetProgram,
\r
1257 appData.icsHost, appData.icsPort);
\r
1259 return StartChildProcess(buf, "", &icsPR);
\r
1262 } else if (appData.useTelnet) {
\r
1263 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1266 /* TCP socket interface differs somewhat between
\r
1267 Unix and NT; handle details in the front end.
\r
1269 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1274 show_bytes(fp, buf, count)
\r
1280 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1281 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1290 /* Returns an errno value */
\r
1292 OutputMaybeTelnet(pr, message, count, outError)
\r
1298 char buf[8192], *p, *q, *buflim;
\r
1299 int left, newcount, outcount;
\r
1301 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1302 *appData.gateway != NULLCHAR) {
\r
1303 if (appData.debugMode) {
\r
1304 fprintf(debugFP, ">ICS: ");
\r
1305 show_bytes(debugFP, message, count);
\r
1306 fprintf(debugFP, "\n");
\r
1308 return OutputToProcess(pr, message, count, outError);
\r
1311 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1317 if (q >= buflim) {
\r
1318 if (appData.debugMode) {
\r
1319 fprintf(debugFP, ">ICS: ");
\r
1320 show_bytes(debugFP, buf, newcount);
\r
1321 fprintf(debugFP, "\n");
\r
1323 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1324 if (outcount < newcount) return -1; /* to be sure */
\r
1331 } else if (((unsigned char) *p) == TN_IAC) {
\r
1332 *q++ = (char) TN_IAC;
\r
1339 if (appData.debugMode) {
\r
1340 fprintf(debugFP, ">ICS: ");
\r
1341 show_bytes(debugFP, buf, newcount);
\r
1342 fprintf(debugFP, "\n");
\r
1344 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1345 if (outcount < newcount) return -1; /* to be sure */
\r
1350 read_from_player(isr, closure, message, count, error)
\r
1351 InputSourceRef isr;
\r
1357 int outError, outCount;
\r
1358 static int gotEof = 0;
\r
1360 /* Pass data read from player on to ICS */
\r
1363 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1364 if (outCount < count) {
\r
1365 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1367 } else if (count < 0) {
\r
1368 RemoveInputSource(isr);
\r
1369 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1370 } else if (gotEof++ > 0) {
\r
1371 RemoveInputSource(isr);
\r
1372 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1380 int count, outCount, outError;
\r
1382 if (icsPR == NULL) return;
\r
1384 count = strlen(s);
\r
1385 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1386 if (outCount < count) {
\r
1387 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1391 /* This is used for sending logon scripts to the ICS. Sending
\r
1392 without a delay causes problems when using timestamp on ICC
\r
1393 (at least on my machine). */
\r
1395 SendToICSDelayed(s,msdelay)
\r
1399 int count, outCount, outError;
\r
1401 if (icsPR == NULL) return;
\r
1403 count = strlen(s);
\r
1404 if (appData.debugMode) {
\r
1405 fprintf(debugFP, ">ICS: ");
\r
1406 show_bytes(debugFP, s, count);
\r
1407 fprintf(debugFP, "\n");
\r
1409 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1411 if (outCount < count) {
\r
1412 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1417 /* Remove all highlighting escape sequences in s
\r
1418 Also deletes any suffix starting with '('
\r
1421 StripHighlightAndTitle(s)
\r
1424 static char retbuf[MSG_SIZ];
\r
1427 while (*s != NULLCHAR) {
\r
1428 while (*s == '\033') {
\r
1429 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1430 if (*s != NULLCHAR) s++;
\r
1432 while (*s != NULLCHAR && *s != '\033') {
\r
1433 if (*s == '(' || *s == '[') {
\r
1444 /* Remove all highlighting escape sequences in s */
\r
1449 static char retbuf[MSG_SIZ];
\r
1452 while (*s != NULLCHAR) {
\r
1453 while (*s == '\033') {
\r
1454 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1455 if (*s != NULLCHAR) s++;
\r
1457 while (*s != NULLCHAR && *s != '\033') {
\r
1465 char *variantNames[] = VARIANT_NAMES;
\r
1470 return variantNames[v];
\r
1474 /* Identify a variant from the strings the chess servers use or the
\r
1475 PGN Variant tag names we use. */
\r
1477 StringToVariant(e)
\r
1482 VariantClass v = VariantNormal;
\r
1483 int i, found = FALSE;
\r
1484 char buf[MSG_SIZ];
\r
1488 /* [HGM] skip over optional board-size prefixes */
\r
1489 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1490 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1491 while( *e++ != '_');
\r
1494 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1495 if (StrCaseStr(e, variantNames[i])) {
\r
1496 v = (VariantClass) i;
\r
1503 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1504 || StrCaseStr(e, "wild/fr")) {
\r
1505 v = VariantFischeRandom;
\r
1506 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1507 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1509 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1510 if (isdigit(*p)) {
\r
1516 case 0: /* FICS only, actually */
\r
1518 /* Castling legal even if K starts on d-file */
\r
1519 v = VariantWildCastle;
\r
1524 /* Castling illegal even if K & R happen to start in
\r
1525 normal positions. */
\r
1526 v = VariantNoCastle;
\r
1539 /* Castling legal iff K & R start in normal positions */
\r
1540 v = VariantNormal;
\r
1545 /* Special wilds for position setup; unclear what to do here */
\r
1546 v = VariantLoadable;
\r
1549 /* Bizarre ICC game */
\r
1550 v = VariantTwoKings;
\r
1553 v = VariantKriegspiel;
\r
1556 v = VariantLosers;
\r
1559 v = VariantFischeRandom;
\r
1562 v = VariantCrazyhouse;
\r
1565 v = VariantBughouse;
\r
1568 v = Variant3Check;
\r
1571 /* Not quite the same as FICS suicide! */
\r
1572 v = VariantGiveaway;
\r
1575 v = VariantAtomic;
\r
1578 v = VariantShatranj;
\r
1581 /* Temporary names for future ICC types. The name *will* change in
\r
1582 the next xboard/WinBoard release after ICC defines it. */
\r
1611 v = VariantXiangqi;
\r
1614 v = VariantCourier;
\r
1617 v = VariantGothic;
\r
1620 v = VariantCapablanca;
\r
1623 v = VariantKnightmate;
\r
1629 v = VariantCylinder;
\r
1632 v = VariantFalcon;
\r
1635 v = VariantCapaRandom;
\r
1638 v = VariantBerolina;
\r
1644 /* Found "wild" or "w" in the string but no number;
\r
1645 must assume it's normal chess. */
\r
1646 v = VariantNormal;
\r
1649 sprintf(buf, "Unknown wild type %d", wnum);
\r
1650 DisplayError(buf, 0);
\r
1651 v = VariantUnknown;
\r
1656 if (appData.debugMode) {
\r
1657 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1658 e, wnum, VariantName(v));
\r
1663 static int leftover_start = 0, leftover_len = 0;
\r
1664 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1666 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1667 advance *index beyond it, and set leftover_start to the new value of
\r
1668 *index; else return FALSE. If pattern contains the character '*', it
\r
1669 matches any sequence of characters not containing '\r', '\n', or the
\r
1670 character following the '*' (if any), and the matched sequence(s) are
\r
1671 copied into star_match.
\r
1674 looking_at(buf, index, pattern)
\r
1679 char *bufp = &buf[*index], *patternp = pattern;
\r
1680 int star_count = 0;
\r
1681 char *matchp = star_match[0];
\r
1684 if (*patternp == NULLCHAR) {
\r
1685 *index = leftover_start = bufp - buf;
\r
1686 *matchp = NULLCHAR;
\r
1689 if (*bufp == NULLCHAR) return FALSE;
\r
1690 if (*patternp == '*') {
\r
1691 if (*bufp == *(patternp + 1)) {
\r
1692 *matchp = NULLCHAR;
\r
1693 matchp = star_match[++star_count];
\r
1697 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1699 if (*patternp == NULLCHAR)
\r
1704 *matchp++ = *bufp++;
\r
1708 if (*patternp != *bufp) return FALSE;
\r
1715 SendToPlayer(data, length)
\r
1719 int error, outCount;
\r
1720 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1721 if (outCount < length) {
\r
1722 DisplayFatalError("Error writing to display", error, 1);
\r
1727 PackHolding(packed, holding)
\r
1731 char *p = holding;
\r
1733 int runlength = 0;
\r
1739 switch (runlength) {
\r
1750 sprintf(q, "%d", runlength);
\r
1762 /* Telnet protocol requests from the front end */
\r
1764 TelnetRequest(ddww, option)
\r
1765 unsigned char ddww, option;
\r
1767 unsigned char msg[3];
\r
1768 int outCount, outError;
\r
1770 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1772 if (appData.debugMode) {
\r
1773 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1789 sprintf(buf1, "%d", ddww);
\r
1794 optionStr = "ECHO";
\r
1798 sprintf(buf2, "%d", option);
\r
1801 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1806 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1807 if (outCount < 3) {
\r
1808 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1815 if (!appData.icsActive) return;
\r
1816 TelnetRequest(TN_DO, TN_ECHO);
\r
1822 if (!appData.icsActive) return;
\r
1823 TelnetRequest(TN_DONT, TN_ECHO);
\r
1827 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1829 /* put the holdings sent to us by the server on the board holdings area */
\r
1830 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1832 ChessSquare piece;
\r
1834 if(gameInfo.holdingsWidth < 2) return;
\r
1836 if( (int)lowestPiece >= BlackPawn ) {
\r
1837 holdingsColumn = 0;
\r
1839 holdingsStartRow = BOARD_HEIGHT-1;
\r
1842 holdingsColumn = BOARD_WIDTH-1;
\r
1843 countsColumn = BOARD_WIDTH-2;
\r
1844 holdingsStartRow = 0;
\r
1848 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1849 board[i][holdingsColumn] = EmptySquare;
\r
1850 board[i][countsColumn] = (ChessSquare) 0;
\r
1852 while( (p=*holdings++) != NULLCHAR ) {
\r
1853 piece = CharToPiece( ToUpper(p) );
\r
1854 if(piece == EmptySquare) continue;
\r
1855 /*j = (int) piece - (int) WhitePawn;*/
\r
1856 j = PieceToNumber(piece);
\r
1857 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1858 if(j < 0) continue; /* should not happen */
\r
1859 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1860 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1861 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1868 VariantSwitch(Board board, VariantClass newVariant)
\r
1870 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1871 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1872 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1874 startedFromPositionFile = FALSE;
\r
1875 if(gameInfo.variant == newVariant) return;
\r
1877 /* [HGM] This routine is called each time an assignment is made to
\r
1878 * gameInfo.variant during a game, to make sure the board sizes
\r
1879 * are set to match the new variant. If that means adding or deleting
\r
1880 * holdings, we shift the playing board accordingly
\r
1881 * This kludge is needed because in ICS observe mode, we get boards
\r
1882 * of an ongoing game without knowing the variant, and learn about the
\r
1883 * latter only later. This can be because of the move list we requested,
\r
1884 * in which case the game history is refilled from the beginning anyway,
\r
1885 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1886 * case we want to add those holdings to the already received position.
\r
1890 if (appData.debugMode) {
\r
1891 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1892 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1893 setbuf(debugFP, NULL);
\r
1895 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1896 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1897 switch(newVariant) {
\r
1898 case VariantShogi:
\r
1899 newWidth = 9; newHeight = 9;
\r
1900 gameInfo.holdingsSize = 7;
\r
1901 case VariantBughouse:
\r
1902 case VariantCrazyhouse:
\r
1903 newHoldingsWidth = 2; break;
\r
1905 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1908 if(newWidth != gameInfo.boardWidth ||
\r
1909 newHeight != gameInfo.boardHeight ||
\r
1910 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1912 /* shift position to new playing area, if needed */
\r
1913 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1914 for(i=0; i<BOARD_HEIGHT; i++)
\r
1915 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1916 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1918 for(i=0; i<newHeight; i++) {
\r
1919 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1920 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1922 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1923 for(i=0; i<BOARD_HEIGHT; i++)
\r
1924 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1925 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1929 gameInfo.boardWidth = newWidth;
\r
1930 gameInfo.boardHeight = newHeight;
\r
1931 gameInfo.holdingsWidth = newHoldingsWidth;
\r
1932 gameInfo.variant = newVariant;
\r
1933 InitDrawingSizes(-2, 0);
\r
1935 /* [HGM] The following should definitely be solved in a better way */
\r
1937 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
1938 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
1939 saveEP = epStatus[0];
\r
1941 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
1943 epStatus[0] = saveEP;
\r
1944 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
1945 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
1947 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
1949 forwardMostMove = oldForwardMostMove;
\r
1950 backwardMostMove = oldBackwardMostMove;
\r
1951 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
1954 static int loggedOn = FALSE;
\r
1956 /*-- Game start info cache: --*/
\r
1958 char gs_kind[MSG_SIZ];
\r
1959 static char player1Name[128] = "";
\r
1960 static char player2Name[128] = "";
\r
1961 static int player1Rating = -1;
\r
1962 static int player2Rating = -1;
\r
1963 /*----------------------------*/
\r
1965 ColorClass curColor = ColorNormal;
\r
1968 read_from_ics(isr, closure, data, count, error)
\r
1969 InputSourceRef isr;
\r
1975 #define BUF_SIZE 8192
\r
1976 #define STARTED_NONE 0
\r
1977 #define STARTED_MOVES 1
\r
1978 #define STARTED_BOARD 2
\r
1979 #define STARTED_OBSERVE 3
\r
1980 #define STARTED_HOLDINGS 4
\r
1981 #define STARTED_CHATTER 5
\r
1982 #define STARTED_COMMENT 6
\r
1983 #define STARTED_MOVES_NOHIDE 7
\r
1985 static int started = STARTED_NONE;
\r
1986 static char parse[20000];
\r
1987 static int parse_pos = 0;
\r
1988 static char buf[BUF_SIZE + 1];
\r
1989 static int firstTime = TRUE, intfSet = FALSE;
\r
1990 static ColorClass prevColor = ColorNormal;
\r
1991 static int savingComment = FALSE;
\r
2000 if (appData.debugMode) {
\r
2002 fprintf(debugFP, "<ICS: ");
\r
2003 show_bytes(debugFP, data, count);
\r
2004 fprintf(debugFP, "\n");
\r
2009 if (appData.debugMode) { int f = forwardMostMove;
\r
2010 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2011 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2014 /* If last read ended with a partial line that we couldn't parse,
\r
2015 prepend it to the new read and try again. */
\r
2016 if (leftover_len > 0) {
\r
2017 for (i=0; i<leftover_len; i++)
\r
2018 buf[i] = buf[leftover_start + i];
\r
2021 /* Copy in new characters, removing nulls and \r's */
\r
2022 buf_len = leftover_len;
\r
2023 for (i = 0; i < count; i++) {
\r
2024 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2025 buf[buf_len++] = data[i];
\r
2028 buf[buf_len] = NULLCHAR;
\r
2029 next_out = leftover_len;
\r
2030 leftover_start = 0;
\r
2033 while (i < buf_len) {
\r
2034 /* Deal with part of the TELNET option negotiation
\r
2035 protocol. We refuse to do anything beyond the
\r
2036 defaults, except that we allow the WILL ECHO option,
\r
2037 which ICS uses to turn off password echoing when we are
\r
2038 directly connected to it. We reject this option
\r
2039 if localLineEditing mode is on (always on in xboard)
\r
2040 and we are talking to port 23, which might be a real
\r
2041 telnet server that will try to keep WILL ECHO on permanently.
\r
2043 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2044 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2045 unsigned char option;
\r
2047 switch ((unsigned char) buf[++i]) {
\r
2049 if (appData.debugMode)
\r
2050 fprintf(debugFP, "\n<WILL ");
\r
2051 switch (option = (unsigned char) buf[++i]) {
\r
2053 if (appData.debugMode)
\r
2054 fprintf(debugFP, "ECHO ");
\r
2055 /* Reply only if this is a change, according
\r
2056 to the protocol rules. */
\r
2057 if (remoteEchoOption) break;
\r
2058 if (appData.localLineEditing &&
\r
2059 atoi(appData.icsPort) == TN_PORT) {
\r
2060 TelnetRequest(TN_DONT, TN_ECHO);
\r
2063 TelnetRequest(TN_DO, TN_ECHO);
\r
2064 remoteEchoOption = TRUE;
\r
2068 if (appData.debugMode)
\r
2069 fprintf(debugFP, "%d ", option);
\r
2070 /* Whatever this is, we don't want it. */
\r
2071 TelnetRequest(TN_DONT, option);
\r
2076 if (appData.debugMode)
\r
2077 fprintf(debugFP, "\n<WONT ");
\r
2078 switch (option = (unsigned char) buf[++i]) {
\r
2080 if (appData.debugMode)
\r
2081 fprintf(debugFP, "ECHO ");
\r
2082 /* Reply only if this is a change, according
\r
2083 to the protocol rules. */
\r
2084 if (!remoteEchoOption) break;
\r
2086 TelnetRequest(TN_DONT, TN_ECHO);
\r
2087 remoteEchoOption = FALSE;
\r
2090 if (appData.debugMode)
\r
2091 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2092 /* Whatever this is, it must already be turned
\r
2093 off, because we never agree to turn on
\r
2094 anything non-default, so according to the
\r
2095 protocol rules, we don't reply. */
\r
2100 if (appData.debugMode)
\r
2101 fprintf(debugFP, "\n<DO ");
\r
2102 switch (option = (unsigned char) buf[++i]) {
\r
2104 /* Whatever this is, we refuse to do it. */
\r
2105 if (appData.debugMode)
\r
2106 fprintf(debugFP, "%d ", option);
\r
2107 TelnetRequest(TN_WONT, option);
\r
2112 if (appData.debugMode)
\r
2113 fprintf(debugFP, "\n<DONT ");
\r
2114 switch (option = (unsigned char) buf[++i]) {
\r
2116 if (appData.debugMode)
\r
2117 fprintf(debugFP, "%d ", option);
\r
2118 /* Whatever this is, we are already not doing
\r
2119 it, because we never agree to do anything
\r
2120 non-default, so according to the protocol
\r
2121 rules, we don't reply. */
\r
2126 if (appData.debugMode)
\r
2127 fprintf(debugFP, "\n<IAC ");
\r
2128 /* Doubled IAC; pass it through */
\r
2132 if (appData.debugMode)
\r
2133 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2134 /* Drop all other telnet commands on the floor */
\r
2137 if (oldi > next_out)
\r
2138 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2139 if (++i > next_out)
\r
2144 /* OK, this at least will *usually* work */
\r
2145 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2149 if (loggedOn && !intfSet) {
\r
2150 if (ics_type == ICS_ICC) {
\r
2152 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2155 } else if (ics_type == ICS_CHESSNET) {
\r
2156 sprintf(str, "/style 12\n");
\r
2158 strcpy(str, "alias $ @\n$set interface ");
\r
2159 strcat(str, programVersion);
\r
2160 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2162 strcat(str, "$iset nohighlight 1\n");
\r
2164 strcat(str, "$iset lock 1\n$style 12\n");
\r
2170 if (started == STARTED_COMMENT) {
\r
2171 /* Accumulate characters in comment */
\r
2172 parse[parse_pos++] = buf[i];
\r
2173 if (buf[i] == '\n') {
\r
2174 parse[parse_pos] = NULLCHAR;
\r
2175 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2176 started = STARTED_NONE;
\r
2178 /* Don't match patterns against characters in chatter */
\r
2183 if (started == STARTED_CHATTER) {
\r
2184 if (buf[i] != '\n') {
\r
2185 /* Don't match patterns against characters in chatter */
\r
2189 started = STARTED_NONE;
\r
2192 /* Kludge to deal with rcmd protocol */
\r
2193 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2194 DisplayFatalError(&buf[1], 0, 1);
\r
2197 firstTime = FALSE;
\r
2200 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2201 ics_type = ICS_ICC;
\r
2203 if (appData.debugMode)
\r
2204 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2207 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2208 ics_type = ICS_FICS;
\r
2210 if (appData.debugMode)
\r
2211 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2214 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2215 ics_type = ICS_CHESSNET;
\r
2217 if (appData.debugMode)
\r
2218 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2223 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2224 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2225 looking_at(buf, &i, "will be \"*\""))) {
\r
2226 strcpy(ics_handle, star_match[0]);
\r
2230 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2231 char buf[MSG_SIZ];
\r
2232 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2233 DisplayIcsInteractionTitle(buf);
\r
2234 have_set_title = TRUE;
\r
2237 /* skip finger notes */
\r
2238 if (started == STARTED_NONE &&
\r
2239 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2240 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2241 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2242 started = STARTED_CHATTER;
\r
2247 /* skip formula vars */
\r
2248 if (started == STARTED_NONE &&
\r
2249 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2250 started = STARTED_CHATTER;
\r
2256 if (appData.zippyTalk || appData.zippyPlay) {
\r
2258 if (ZippyControl(buf, &i) ||
\r
2259 ZippyConverse(buf, &i) ||
\r
2260 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2266 if (/* Don't color "message" or "messages" output */
\r
2267 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2268 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2269 looking_at(buf, &i, "--* (*:*): ") ||
\r
2270 /* Regular tells and says */
\r
2271 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2272 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2273 looking_at(buf, &i, "* says: ") ||
\r
2274 /* Message notifications (same color as tells) */
\r
2275 looking_at(buf, &i, "* has left a message ") ||
\r
2276 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2277 /* Whispers and kibitzes */
\r
2278 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2279 looking_at(buf, &i, "* kibitzes: ") ||
\r
2280 /* Channel tells */
\r
2281 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2283 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2284 /* Avoid "tells you:" spoofs in channels */
\r
2287 if (star_match[0][0] == NULLCHAR ||
\r
2288 strchr(star_match[0], ' ') ||
\r
2289 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2290 /* Reject bogus matches */
\r
2293 if (appData.colorize) {
\r
2294 if (oldi > next_out) {
\r
2295 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2300 Colorize(ColorTell, FALSE);
\r
2301 curColor = ColorTell;
\r
2304 Colorize(ColorKibitz, FALSE);
\r
2305 curColor = ColorKibitz;
\r
2308 p = strrchr(star_match[1], '(');
\r
2310 p = star_match[1];
\r
2314 if (atoi(p) == 1) {
\r
2315 Colorize(ColorChannel1, FALSE);
\r
2316 curColor = ColorChannel1;
\r
2318 Colorize(ColorChannel, FALSE);
\r
2319 curColor = ColorChannel;
\r
2323 curColor = ColorNormal;
\r
2327 if (started == STARTED_NONE && appData.autoComment &&
\r
2328 (gameMode == IcsObserving ||
\r
2329 gameMode == IcsPlayingWhite ||
\r
2330 gameMode == IcsPlayingBlack)) {
\r
2331 parse_pos = i - oldi;
\r
2332 memcpy(parse, &buf[oldi], parse_pos);
\r
2333 parse[parse_pos] = NULLCHAR;
\r
2334 started = STARTED_COMMENT;
\r
2335 savingComment = TRUE;
\r
2337 started = STARTED_CHATTER;
\r
2338 savingComment = FALSE;
\r
2345 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2346 looking_at(buf, &i, "* c-shouts: ")) {
\r
2347 if (appData.colorize) {
\r
2348 if (oldi > next_out) {
\r
2349 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2352 Colorize(ColorSShout, FALSE);
\r
2353 curColor = ColorSShout;
\r
2356 started = STARTED_CHATTER;
\r
2360 if (looking_at(buf, &i, "--->")) {
\r
2365 if (looking_at(buf, &i, "* shouts: ") ||
\r
2366 looking_at(buf, &i, "--> ")) {
\r
2367 if (appData.colorize) {
\r
2368 if (oldi > next_out) {
\r
2369 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2372 Colorize(ColorShout, FALSE);
\r
2373 curColor = ColorShout;
\r
2376 started = STARTED_CHATTER;
\r
2380 if (looking_at( buf, &i, "Challenge:")) {
\r
2381 if (appData.colorize) {
\r
2382 if (oldi > next_out) {
\r
2383 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2386 Colorize(ColorChallenge, FALSE);
\r
2387 curColor = ColorChallenge;
\r
2393 if (looking_at(buf, &i, "* offers you") ||
\r
2394 looking_at(buf, &i, "* offers to be") ||
\r
2395 looking_at(buf, &i, "* would like to") ||
\r
2396 looking_at(buf, &i, "* requests to") ||
\r
2397 looking_at(buf, &i, "Your opponent offers") ||
\r
2398 looking_at(buf, &i, "Your opponent requests")) {
\r
2400 if (appData.colorize) {
\r
2401 if (oldi > next_out) {
\r
2402 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2405 Colorize(ColorRequest, FALSE);
\r
2406 curColor = ColorRequest;
\r
2411 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2412 if (appData.colorize) {
\r
2413 if (oldi > next_out) {
\r
2414 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2417 Colorize(ColorSeek, FALSE);
\r
2418 curColor = ColorSeek;
\r
2424 if (looking_at(buf, &i, "\\ ")) {
\r
2425 if (prevColor != ColorNormal) {
\r
2426 if (oldi > next_out) {
\r
2427 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2430 Colorize(prevColor, TRUE);
\r
2431 curColor = prevColor;
\r
2433 if (savingComment) {
\r
2434 parse_pos = i - oldi;
\r
2435 memcpy(parse, &buf[oldi], parse_pos);
\r
2436 parse[parse_pos] = NULLCHAR;
\r
2437 started = STARTED_COMMENT;
\r
2439 started = STARTED_CHATTER;
\r
2444 if (looking_at(buf, &i, "Black Strength :") ||
\r
2445 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2446 looking_at(buf, &i, "<10>") ||
\r
2447 looking_at(buf, &i, "#@#")) {
\r
2448 /* Wrong board style */
\r
2450 SendToICS(ics_prefix);
\r
2451 SendToICS("set style 12\n");
\r
2452 SendToICS(ics_prefix);
\r
2453 SendToICS("refresh\n");
\r
2457 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2459 have_sent_ICS_logon = 1;
\r
2463 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2464 (looking_at(buf, &i, "\n<12> ") ||
\r
2465 looking_at(buf, &i, "<12> "))) {
\r
2467 if (oldi > next_out) {
\r
2468 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2471 started = STARTED_BOARD;
\r
2476 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2477 looking_at(buf, &i, "<b1> ")) {
\r
2478 if (oldi > next_out) {
\r
2479 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2482 started = STARTED_HOLDINGS;
\r
2487 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2489 /* Header for a move list -- first line */
\r
2491 switch (ics_getting_history) {
\r
2493 switch (gameMode) {
\r
2495 case BeginningOfGame:
\r
2496 /* User typed "moves" or "oldmoves" while we
\r
2497 were idle. Pretend we asked for these
\r
2498 moves and soak them up so user can step
\r
2499 through them and/or save them.
\r
2501 Reset(FALSE, TRUE);
\r
2502 gameMode = IcsObserving;
\r
2505 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2507 case EditGame: /*?*/
\r
2508 case EditPosition: /*?*/
\r
2509 /* Should above feature work in these modes too? */
\r
2510 /* For now it doesn't */
\r
2511 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2514 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2519 /* Is this the right one? */
\r
2520 if (gameInfo.white && gameInfo.black &&
\r
2521 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2522 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2524 ics_getting_history = H_GOT_REQ_HEADER;
\r
2527 case H_GOT_REQ_HEADER:
\r
2528 case H_GOT_UNREQ_HEADER:
\r
2529 case H_GOT_UNWANTED_HEADER:
\r
2530 case H_GETTING_MOVES:
\r
2531 /* Should not happen */
\r
2532 DisplayError("Error gathering move list: two headers", 0);
\r
2533 ics_getting_history = H_FALSE;
\r
2537 /* Save player ratings into gameInfo if needed */
\r
2538 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2539 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2540 (gameInfo.whiteRating == -1 ||
\r
2541 gameInfo.blackRating == -1)) {
\r
2543 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2544 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2545 if (appData.debugMode)
\r
2546 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2547 gameInfo.whiteRating, gameInfo.blackRating);
\r
2552 if (looking_at(buf, &i,
\r
2553 "* * match, initial time: * minute*, increment: * second")) {
\r
2554 /* Header for a move list -- second line */
\r
2555 /* Initial board will follow if this is a wild game */
\r
2556 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2557 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2558 gameInfo.event = StrSave(str);
\r
2559 /* [HGM] we switched variant. Translate boards if needed. */
\r
2560 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2564 if (looking_at(buf, &i, "Move ")) {
\r
2565 /* Beginning of a move list */
\r
2566 switch (ics_getting_history) {
\r
2568 /* Normally should not happen */
\r
2569 /* Maybe user hit reset while we were parsing */
\r
2572 /* Happens if we are ignoring a move list that is not
\r
2573 * the one we just requested. Common if the user
\r
2574 * tries to observe two games without turning off
\r
2577 case H_GETTING_MOVES:
\r
2578 /* Should not happen */
\r
2579 DisplayError("Error gathering move list: nested", 0);
\r
2580 ics_getting_history = H_FALSE;
\r
2582 case H_GOT_REQ_HEADER:
\r
2583 ics_getting_history = H_GETTING_MOVES;
\r
2584 started = STARTED_MOVES;
\r
2586 if (oldi > next_out) {
\r
2587 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2590 case H_GOT_UNREQ_HEADER:
\r
2591 ics_getting_history = H_GETTING_MOVES;
\r
2592 started = STARTED_MOVES_NOHIDE;
\r
2595 case H_GOT_UNWANTED_HEADER:
\r
2596 ics_getting_history = H_FALSE;
\r
2602 if (looking_at(buf, &i, "% ") ||
\r
2603 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2604 && looking_at(buf, &i, "}*"))) {
\r
2605 savingComment = FALSE;
\r
2606 switch (started) {
\r
2607 case STARTED_MOVES:
\r
2608 case STARTED_MOVES_NOHIDE:
\r
2609 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2610 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2611 ParseGameHistory(parse);
\r
2613 if (appData.zippyPlay && first.initDone) {
\r
2614 FeedMovesToProgram(&first, forwardMostMove);
\r
2615 if (gameMode == IcsPlayingWhite) {
\r
2616 if (WhiteOnMove(forwardMostMove)) {
\r
2617 if (first.sendTime) {
\r
2618 if (first.useColors) {
\r
2619 SendToProgram("black\n", &first);
\r
2621 SendTimeRemaining(&first, TRUE);
\r
2623 if (first.useColors) {
\r
2624 SendToProgram("white\ngo\n", &first);
\r
2626 SendToProgram("go\n", &first);
\r
2628 first.maybeThinking = TRUE;
\r
2630 if (first.usePlayother) {
\r
2631 if (first.sendTime) {
\r
2632 SendTimeRemaining(&first, TRUE);
\r
2634 SendToProgram("playother\n", &first);
\r
2635 firstMove = FALSE;
\r
2640 } else if (gameMode == IcsPlayingBlack) {
\r
2641 if (!WhiteOnMove(forwardMostMove)) {
\r
2642 if (first.sendTime) {
\r
2643 if (first.useColors) {
\r
2644 SendToProgram("white\n", &first);
\r
2646 SendTimeRemaining(&first, FALSE);
\r
2648 if (first.useColors) {
\r
2649 SendToProgram("black\ngo\n", &first);
\r
2651 SendToProgram("go\n", &first);
\r
2653 first.maybeThinking = TRUE;
\r
2655 if (first.usePlayother) {
\r
2656 if (first.sendTime) {
\r
2657 SendTimeRemaining(&first, FALSE);
\r
2659 SendToProgram("playother\n", &first);
\r
2660 firstMove = FALSE;
\r
2668 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2669 /* Moves came from oldmoves or moves command
\r
2670 while we weren't doing anything else.
\r
2672 currentMove = forwardMostMove;
\r
2673 ClearHighlights();/*!!could figure this out*/
\r
2674 flipView = appData.flipView;
\r
2675 DrawPosition(FALSE, boards[currentMove]);
\r
2676 DisplayBothClocks();
\r
2677 sprintf(str, "%s vs. %s",
\r
2678 gameInfo.white, gameInfo.black);
\r
2679 DisplayTitle(str);
\r
2680 gameMode = IcsIdle;
\r
2682 /* Moves were history of an active game */
\r
2683 if (gameInfo.resultDetails != NULL) {
\r
2684 free(gameInfo.resultDetails);
\r
2685 gameInfo.resultDetails = NULL;
\r
2688 HistorySet(parseList, backwardMostMove,
\r
2689 forwardMostMove, currentMove-1);
\r
2690 DisplayMove(currentMove - 1);
\r
2691 if (started == STARTED_MOVES) next_out = i;
\r
2692 started = STARTED_NONE;
\r
2693 ics_getting_history = H_FALSE;
\r
2696 case STARTED_OBSERVE:
\r
2697 started = STARTED_NONE;
\r
2698 SendToICS(ics_prefix);
\r
2699 SendToICS("refresh\n");
\r
2708 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2709 started == STARTED_HOLDINGS ||
\r
2710 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2711 /* Accumulate characters in move list or board */
\r
2712 parse[parse_pos++] = buf[i];
\r
2715 /* Start of game messages. Mostly we detect start of game
\r
2716 when the first board image arrives. On some versions
\r
2717 of the ICS, though, we need to do a "refresh" after starting
\r
2718 to observe in order to get the current board right away. */
\r
2719 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2720 started = STARTED_OBSERVE;
\r
2724 /* Handle auto-observe */
\r
2725 if (appData.autoObserve &&
\r
2726 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2727 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2729 /* Choose the player that was highlighted, if any. */
\r
2730 if (star_match[0][0] == '\033' ||
\r
2731 star_match[1][0] != '\033') {
\r
2732 player = star_match[0];
\r
2734 player = star_match[2];
\r
2736 sprintf(str, "%sobserve %s\n",
\r
2737 ics_prefix, StripHighlightAndTitle(player));
\r
2740 /* Save ratings from notify string */
\r
2741 strcpy(player1Name, star_match[0]);
\r
2742 player1Rating = string_to_rating(star_match[1]);
\r
2743 strcpy(player2Name, star_match[2]);
\r
2744 player2Rating = string_to_rating(star_match[3]);
\r
2746 if (appData.debugMode)
\r
2748 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2749 player1Name, player1Rating,
\r
2750 player2Name, player2Rating);
\r
2755 /* Deal with automatic examine mode after a game,
\r
2756 and with IcsObserving -> IcsExamining transition */
\r
2757 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2758 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2760 int gamenum = atoi(star_match[0]);
\r
2761 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2762 gamenum == ics_gamenum) {
\r
2763 /* We were already playing or observing this game;
\r
2764 no need to refetch history */
\r
2765 gameMode = IcsExamining;
\r
2767 pauseExamForwardMostMove = forwardMostMove;
\r
2768 } else if (currentMove < forwardMostMove) {
\r
2769 ForwardInner(forwardMostMove);
\r
2772 /* I don't think this case really can happen */
\r
2773 SendToICS(ics_prefix);
\r
2774 SendToICS("refresh\n");
\r
2779 /* Error messages */
\r
2780 if (ics_user_moved) {
\r
2781 if (looking_at(buf, &i, "Illegal move") ||
\r
2782 looking_at(buf, &i, "Not a legal move") ||
\r
2783 looking_at(buf, &i, "Your king is in check") ||
\r
2784 looking_at(buf, &i, "It isn't your turn") ||
\r
2785 looking_at(buf, &i, "It is not your move")) {
\r
2786 /* Illegal move */
\r
2787 ics_user_moved = 0;
\r
2788 if (forwardMostMove > backwardMostMove) {
\r
2789 currentMove = --forwardMostMove;
\r
2790 DisplayMove(currentMove - 1); /* before DMError */
\r
2791 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2792 DrawPosition(FALSE, boards[currentMove]);
\r
2794 DisplayBothClocks();
\r
2800 if (looking_at(buf, &i, "still have time") ||
\r
2801 looking_at(buf, &i, "not out of time") ||
\r
2802 looking_at(buf, &i, "either player is out of time") ||
\r
2803 looking_at(buf, &i, "has timeseal; checking")) {
\r
2804 /* We must have called his flag a little too soon */
\r
2805 whiteFlag = blackFlag = FALSE;
\r
2809 if (looking_at(buf, &i, "added * seconds to") ||
\r
2810 looking_at(buf, &i, "seconds were added to")) {
\r
2811 /* Update the clocks */
\r
2812 SendToICS(ics_prefix);
\r
2813 SendToICS("refresh\n");
\r
2817 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2818 ics_clock_paused = TRUE;
\r
2823 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2824 ics_clock_paused = FALSE;
\r
2829 /* Grab player ratings from the Creating: message.
\r
2830 Note we have to check for the special case when
\r
2831 the ICS inserts things like [white] or [black]. */
\r
2832 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2833 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2835 0 player 1 name (not necessarily white)
\r
2837 2 empty, white, or black (IGNORED)
\r
2838 3 player 2 name (not necessarily black)
\r
2841 The names/ratings are sorted out when the game
\r
2842 actually starts (below).
\r
2844 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2845 player1Rating = string_to_rating(star_match[1]);
\r
2846 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2847 player2Rating = string_to_rating(star_match[4]);
\r
2849 if (appData.debugMode)
\r
2851 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2852 player1Name, player1Rating,
\r
2853 player2Name, player2Rating);
\r
2858 /* Improved generic start/end-of-game messages */
\r
2859 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2860 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2861 /* If tkind == 0: */
\r
2862 /* star_match[0] is the game number */
\r
2863 /* [1] is the white player's name */
\r
2864 /* [2] is the black player's name */
\r
2865 /* For end-of-game: */
\r
2866 /* [3] is the reason for the game end */
\r
2867 /* [4] is a PGN end game-token, preceded by " " */
\r
2868 /* For start-of-game: */
\r
2869 /* [3] begins with "Creating" or "Continuing" */
\r
2870 /* [4] is " *" or empty (don't care). */
\r
2871 int gamenum = atoi(star_match[0]);
\r
2872 char *whitename, *blackname, *why, *endtoken;
\r
2873 ChessMove endtype = (ChessMove) 0;
\r
2876 whitename = star_match[1];
\r
2877 blackname = star_match[2];
\r
2878 why = star_match[3];
\r
2879 endtoken = star_match[4];
\r
2881 whitename = star_match[1];
\r
2882 blackname = star_match[3];
\r
2883 why = star_match[5];
\r
2884 endtoken = star_match[6];
\r
2887 /* Game start messages */
\r
2888 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2889 strncmp(why, "Continuing ", 11) == 0) {
\r
2890 gs_gamenum = gamenum;
\r
2891 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2893 if (appData.zippyPlay) {
\r
2894 ZippyGameStart(whitename, blackname);
\r
2900 /* Game end messages */
\r
2901 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2902 ics_gamenum != gamenum) {
\r
2905 while (endtoken[0] == ' ') endtoken++;
\r
2906 switch (endtoken[0]) {
\r
2909 endtype = GameUnfinished;
\r
2912 endtype = BlackWins;
\r
2915 if (endtoken[1] == '/')
\r
2916 endtype = GameIsDrawn;
\r
2918 endtype = WhiteWins;
\r
2921 GameEnds(endtype, why, GE_ICS);
\r
2923 if (appData.zippyPlay && first.initDone) {
\r
2924 ZippyGameEnd(endtype, why);
\r
2925 if (first.pr == NULL) {
\r
2926 /* Start the next process early so that we'll
\r
2927 be ready for the next challenge */
\r
2928 StartChessProgram(&first);
\r
2930 /* Send "new" early, in case this command takes
\r
2931 a long time to finish, so that we'll be ready
\r
2932 for the next challenge. */
\r
2933 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
2934 Reset(TRUE, TRUE);
\r
2940 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2941 looking_at(buf, &i, "no longer observing game *") ||
\r
2942 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2943 if (gameMode == IcsObserving &&
\r
2944 atoi(star_match[0]) == ics_gamenum)
\r
2947 gameMode = IcsIdle;
\r
2949 ics_user_moved = FALSE;
\r
2954 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2955 if (gameMode == IcsExamining &&
\r
2956 atoi(star_match[0]) == ics_gamenum)
\r
2958 gameMode = IcsIdle;
\r
2960 ics_user_moved = FALSE;
\r
2965 /* Advance leftover_start past any newlines we find,
\r
2966 so only partial lines can get reparsed */
\r
2967 if (looking_at(buf, &i, "\n")) {
\r
2968 prevColor = curColor;
\r
2969 if (curColor != ColorNormal) {
\r
2970 if (oldi > next_out) {
\r
2971 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2974 Colorize(ColorNormal, FALSE);
\r
2975 curColor = ColorNormal;
\r
2977 if (started == STARTED_BOARD) {
\r
2978 started = STARTED_NONE;
\r
2979 parse[parse_pos] = NULLCHAR;
\r
2980 ParseBoard12(parse);
\r
2981 ics_user_moved = 0;
\r
2983 /* Send premove here */
\r
2984 if (appData.premove) {
\r
2985 char str[MSG_SIZ];
\r
2986 if (currentMove == 0 &&
\r
2987 gameMode == IcsPlayingWhite &&
\r
2988 appData.premoveWhite) {
\r
2989 sprintf(str, "%s%s\n", ics_prefix,
\r
2990 appData.premoveWhiteText);
\r
2991 if (appData.debugMode)
\r
2992 fprintf(debugFP, "Sending premove:\n");
\r
2994 } else if (currentMove == 1 &&
\r
2995 gameMode == IcsPlayingBlack &&
\r
2996 appData.premoveBlack) {
\r
2997 sprintf(str, "%s%s\n", ics_prefix,
\r
2998 appData.premoveBlackText);
\r
2999 if (appData.debugMode)
\r
3000 fprintf(debugFP, "Sending premove:\n");
\r
3002 } else if (gotPremove) {
\r
3004 ClearPremoveHighlights();
\r
3005 if (appData.debugMode)
\r
3006 fprintf(debugFP, "Sending premove:\n");
\r
3007 UserMoveEvent(premoveFromX, premoveFromY,
\r
3008 premoveToX, premoveToY,
\r
3009 premovePromoChar);
\r
3013 /* Usually suppress following prompt */
\r
3014 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3015 if (looking_at(buf, &i, "*% ")) {
\r
3016 savingComment = FALSE;
\r
3020 } else if (started == STARTED_HOLDINGS) {
\r
3022 char new_piece[MSG_SIZ];
\r
3023 started = STARTED_NONE;
\r
3024 parse[parse_pos] = NULLCHAR;
\r
3025 if (appData.debugMode)
\r
3026 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3027 parse, currentMove);
\r
3028 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3029 gamenum == ics_gamenum) {
\r
3030 if (gameInfo.variant == VariantNormal) {
\r
3031 /* [HGM] We seem to switch variant during a game!
\r
3032 * Presumably no holdings were displayed, so we have
\r
3033 * to move the position two files to the right to
\r
3034 * create room for them!
\r
3036 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3037 /* Get a move list just to see the header, which
\r
3038 will tell us whether this is really bug or zh */
\r
3039 if (ics_getting_history == H_FALSE) {
\r
3040 ics_getting_history = H_REQUESTED;
\r
3041 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3045 new_piece[0] = NULLCHAR;
\r
3046 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3047 &gamenum, white_holding, black_holding,
\r
3049 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3050 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3051 /* [HGM] copy holdings to board holdings area */
\r
3052 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3053 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3055 if (appData.zippyPlay && first.initDone) {
\r
3056 ZippyHoldings(white_holding, black_holding,
\r
3060 if (tinyLayout || smallLayout) {
\r
3061 char wh[16], bh[16];
\r
3062 PackHolding(wh, white_holding);
\r
3063 PackHolding(bh, black_holding);
\r
3064 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3065 gameInfo.white, gameInfo.black);
\r
3067 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3068 gameInfo.white, white_holding,
\r
3069 gameInfo.black, black_holding);
\r
3072 DrawPosition(FALSE, boards[currentMove]);
\r
3073 DisplayTitle(str);
\r
3075 /* Suppress following prompt */
\r
3076 if (looking_at(buf, &i, "*% ")) {
\r
3077 savingComment = FALSE;
\r
3084 i++; /* skip unparsed character and loop back */
\r
3087 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
3088 started != STARTED_HOLDINGS && i > next_out) {
\r
3089 SendToPlayer(&buf[next_out], i - next_out);
\r
3093 leftover_len = buf_len - leftover_start;
\r
3094 /* if buffer ends with something we couldn't parse,
\r
3095 reparse it after appending the next read */
\r
3097 } else if (count == 0) {
\r
3098 RemoveInputSource(isr);
\r
3099 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
3101 DisplayFatalError("Error reading from ICS", error, 1);
\r
3106 /* Board style 12 looks like this:
\r
3108 <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
3110 * The "<12> " is stripped before it gets to this routine. The two
\r
3111 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3112 * some chess servers may not have them, or may have only the first.
\r
3113 * Additional trailing fields may be added in the future.
\r
3116 #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
3118 #define RELATION_OBSERVING_PLAYED 0
\r
3119 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3120 #define RELATION_PLAYING_MYMOVE 1
\r
3121 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3122 #define RELATION_EXAMINING 2
\r
3123 #define RELATION_ISOLATED_BOARD -3
\r
3124 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3127 ParseBoard12(string)
\r
3130 GameMode newGameMode;
\r
3131 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
3132 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
3133 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3134 char to_play, board_chars[72];
\r
3135 char move_str[500], str[500], elapsed_time[500];
\r
3136 char black[32], white[32];
\r
3138 int prevMove = currentMove;
\r
3140 ChessMove moveType;
\r
3141 int fromX, fromY, toX, toY;
\r
3144 fromX = fromY = toX = toY = -1;
\r
3148 if (appData.debugMode)
\r
3149 fprintf(debugFP, "Parsing board: %s\n", string);
\r
3151 move_str[0] = NULLCHAR;
\r
3152 elapsed_time[0] = NULLCHAR;
\r
3153 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
3154 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3155 &gamenum, white, black, &relation, &basetime, &increment,
\r
3156 &white_stren, &black_stren, &white_time, &black_time,
\r
3157 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3161 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
3162 DisplayError(str, 0);
\r
3166 /* Convert the move number to internal form */
\r
3167 moveNum = (moveNum - 1) * 2;
\r
3168 if (to_play == 'B') moveNum++;
\r
3169 if (moveNum >= MAX_MOVES) {
\r
3170 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3175 switch (relation) {
\r
3176 case RELATION_OBSERVING_PLAYED:
\r
3177 case RELATION_OBSERVING_STATIC:
\r
3178 if (gamenum == -1) {
\r
3179 /* Old ICC buglet */
\r
3180 relation = RELATION_OBSERVING_STATIC;
\r
3182 newGameMode = IcsObserving;
\r
3184 case RELATION_PLAYING_MYMOVE:
\r
3185 case RELATION_PLAYING_NOTMYMOVE:
\r
3187 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3188 IcsPlayingWhite : IcsPlayingBlack;
\r
3190 case RELATION_EXAMINING:
\r
3191 newGameMode = IcsExamining;
\r
3193 case RELATION_ISOLATED_BOARD:
\r
3195 /* Just display this board. If user was doing something else,
\r
3196 we will forget about it until the next board comes. */
\r
3197 newGameMode = IcsIdle;
\r
3199 case RELATION_STARTING_POSITION:
\r
3200 newGameMode = gameMode;
\r
3204 /* Modify behavior for initial board display on move listing
\r
3207 switch (ics_getting_history) {
\r
3211 case H_GOT_REQ_HEADER:
\r
3212 case H_GOT_UNREQ_HEADER:
\r
3213 /* This is the initial position of the current game */
\r
3214 gamenum = ics_gamenum;
\r
3215 moveNum = 0; /* old ICS bug workaround */
\r
3216 if (to_play == 'B') {
\r
3217 startedFromSetupPosition = TRUE;
\r
3218 blackPlaysFirst = TRUE;
\r
3220 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3221 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3222 if (currentMove == 0) currentMove = 1;
\r
3224 newGameMode = gameMode;
\r
3225 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3227 case H_GOT_UNWANTED_HEADER:
\r
3228 /* This is an initial board that we don't want */
\r
3230 case H_GETTING_MOVES:
\r
3231 /* Should not happen */
\r
3232 DisplayError("Error gathering move list: extra board", 0);
\r
3233 ics_getting_history = H_FALSE;
\r
3237 /* Take action if this is the first board of a new game, or of a
\r
3238 different game than is currently being displayed. */
\r
3239 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3240 relation == RELATION_ISOLATED_BOARD) {
\r
3242 /* Forget the old game and get the history (if any) of the new one */
\r
3243 if (gameMode != BeginningOfGame) {
\r
3244 Reset(FALSE, TRUE);
\r
3247 if (appData.autoRaiseBoard) BoardToTop();
\r
3249 if (gamenum == -1) {
\r
3250 newGameMode = IcsIdle;
\r
3251 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3252 appData.getMoveList) {
\r
3253 /* Need to get game history */
\r
3254 ics_getting_history = H_REQUESTED;
\r
3255 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3259 /* Initially flip the board to have black on the bottom if playing
\r
3260 black or if the ICS flip flag is set, but let the user change
\r
3261 it with the Flip View button. */
\r
3262 flipView = appData.autoFlipView ?
\r
3263 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3266 /* Done with values from previous mode; copy in new ones */
\r
3267 gameMode = newGameMode;
\r
3269 ics_gamenum = gamenum;
\r
3270 if (gamenum == gs_gamenum) {
\r
3271 int klen = strlen(gs_kind);
\r
3272 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3273 sprintf(str, "ICS %s", gs_kind);
\r
3274 gameInfo.event = StrSave(str);
\r
3276 gameInfo.event = StrSave("ICS game");
\r
3278 gameInfo.site = StrSave(appData.icsHost);
\r
3279 gameInfo.date = PGNDate();
\r
3280 gameInfo.round = StrSave("-");
\r
3281 gameInfo.white = StrSave(white);
\r
3282 gameInfo.black = StrSave(black);
\r
3283 timeControl = basetime * 60 * 1000;
\r
3284 timeControl_2 = 0;
\r
3285 timeIncrement = increment * 1000;
\r
3286 movesPerSession = 0;
\r
3287 gameInfo.timeControl = TimeControlTagValue();
\r
3288 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3289 if (appData.debugMode) {
\r
3290 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3291 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3292 setbuf(debugFP, NULL);
\r
3295 gameInfo.outOfBook = NULL;
\r
3297 /* Do we have the ratings? */
\r
3298 if (strcmp(player1Name, white) == 0 &&
\r
3299 strcmp(player2Name, black) == 0) {
\r
3300 if (appData.debugMode)
\r
3301 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3302 player1Rating, player2Rating);
\r
3303 gameInfo.whiteRating = player1Rating;
\r
3304 gameInfo.blackRating = player2Rating;
\r
3305 } else if (strcmp(player2Name, white) == 0 &&
\r
3306 strcmp(player1Name, black) == 0) {
\r
3307 if (appData.debugMode)
\r
3308 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3309 player2Rating, player1Rating);
\r
3310 gameInfo.whiteRating = player2Rating;
\r
3311 gameInfo.blackRating = player1Rating;
\r
3313 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3315 /* Silence shouts if requested */
\r
3316 if (appData.quietPlay &&
\r
3317 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3318 SendToICS(ics_prefix);
\r
3319 SendToICS("set shout 0\n");
\r
3323 /* Deal with midgame name changes */
\r
3325 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3326 if (gameInfo.white) free(gameInfo.white);
\r
3327 gameInfo.white = StrSave(white);
\r
3329 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3330 if (gameInfo.black) free(gameInfo.black);
\r
3331 gameInfo.black = StrSave(black);
\r
3335 /* Throw away game result if anything actually changes in examine mode */
\r
3336 if (gameMode == IcsExamining && !newGame) {
\r
3337 gameInfo.result = GameUnfinished;
\r
3338 if (gameInfo.resultDetails != NULL) {
\r
3339 free(gameInfo.resultDetails);
\r
3340 gameInfo.resultDetails = NULL;
\r
3344 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3345 in if they are in a different variation than we are. */
\r
3346 if (pauseExamInvalid) return;
\r
3347 if (pausing && gameMode == IcsExamining) {
\r
3348 if (moveNum <= pauseExamForwardMostMove) {
\r
3349 pauseExamInvalid = TRUE;
\r
3350 forwardMostMove = pauseExamForwardMostMove;
\r
3355 /* Parse the board */
\r
3356 for (k = 0; k < 8; k++) {
\r
3357 for (j = 0; j < 8; j++)
\r
3358 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3359 if(gameInfo.holdingsWidth > 1) {
\r
3360 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3361 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3364 CopyBoard(boards[moveNum], board);
\r
3365 if (moveNum == 0) {
\r
3366 startedFromSetupPosition =
\r
3367 !CompareBoards(board, initialPosition);
\r
3368 if(startedFromSetupPosition)
\r
3369 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3372 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3373 to make it also work for FRC opening positions. Note that board12
\r
3374 is really defective for later FRC positions, as it has no way to
\r
3375 indicate which Rook can castle if they are on the same side of King.
\r
3376 For the initial position we grant rights to the outermost Rooks,
\r
3377 and remember thos rights, and we then copy them on positions
\r
3378 later in an FRC game. This means WB might not recognize castlings with
\r
3379 Rooks that have moved back to their original position as illegal,
\r
3380 but in ICS mode that is not its job anyway.
\r
3382 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3385 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3386 if(board[0][i] == WhiteRook) j = i;
\r
3387 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3388 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3389 if(board[0][i] == WhiteRook) j = i;
\r
3390 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3391 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3392 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3393 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3394 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3395 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3396 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3398 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3399 if(board[0][k] == WhiteKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3400 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3401 if(board[BOARD_HEIGHT-1][k] == BlackKing)
\r
3402 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3404 r = castlingRights[moveNum][0] = initialRights[0];
\r
3405 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3406 r = castlingRights[moveNum][1] = initialRights[1];
\r
3407 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3408 r = castlingRights[moveNum][3] = initialRights[3];
\r
3409 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3410 r = castlingRights[moveNum][4] = initialRights[4];
\r
3411 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3412 /* wildcastle kludge: always assume King has rights */
\r
3413 r = castlingRights[moveNum][2] = initialRights[2];
\r
3414 r = castlingRights[moveNum][5] = initialRights[5];
\r
3416 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3417 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3420 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3421 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3422 /* This was an initial position from a move list, not
\r
3423 the current position */
\r
3427 /* Update currentMove and known move number limits */
\r
3428 newMove = newGame || moveNum > forwardMostMove;
\r
3430 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3431 if (gameMode == IcsExamining && moveNum == 0) {
\r
3432 /* Workaround for ICS limitation: we are not told the wild
\r
3433 type when starting to examine a game. But if we ask for
\r
3434 the move list, the move list header will tell us */
\r
3435 ics_getting_history = H_REQUESTED;
\r
3436 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3439 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3440 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3441 forwardMostMove = moveNum;
\r
3442 if (!pausing || currentMove > forwardMostMove)
\r
3443 currentMove = forwardMostMove;
\r
3445 /* New part of history that is not contiguous with old part */
\r
3446 if (pausing && gameMode == IcsExamining) {
\r
3447 pauseExamInvalid = TRUE;
\r
3448 forwardMostMove = pauseExamForwardMostMove;
\r
3451 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3452 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3453 ics_getting_history = H_REQUESTED;
\r
3454 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3459 /* Update the clocks */
\r
3460 if (strchr(elapsed_time, '.')) {
\r
3461 /* Time is in ms */
\r
3462 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3463 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3465 /* Time is in seconds */
\r
3466 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3467 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3472 if (appData.zippyPlay && newGame &&
\r
3473 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3474 gameMode != IcsExamining)
\r
3475 ZippyFirstBoard(moveNum, basetime, increment);
\r
3478 /* Put the move on the move list, first converting
\r
3479 to canonical algebraic form. */
\r
3480 if (moveNum > 0) {
\r
3481 if (appData.debugMode) {
\r
3482 if (appData.debugMode) { int f = forwardMostMove;
\r
3483 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3484 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3486 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3487 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3488 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3489 setbuf(debugFP, NULL);
\r
3491 if (moveNum <= backwardMostMove) {
\r
3492 /* We don't know what the board looked like before
\r
3493 this move. Punt. */
\r
3494 strcpy(parseList[moveNum - 1], move_str);
\r
3495 strcat(parseList[moveNum - 1], " ");
\r
3496 strcat(parseList[moveNum - 1], elapsed_time);
\r
3497 moveList[moveNum - 1][0] = NULLCHAR;
\r
3498 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3499 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3500 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3501 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3502 fromY, fromX, toY, toX, promoChar,
\r
3503 parseList[moveNum-1]);
\r
3504 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3505 castlingRights[moveNum]) ) {
\r
3507 case MT_STALEMATE:
\r
3511 if(gameInfo.variant != VariantShogi)
\r
3512 strcat(parseList[moveNum - 1], "+");
\r
3514 case MT_CHECKMATE:
\r
3515 strcat(parseList[moveNum - 1], "#");
\r
3518 strcat(parseList[moveNum - 1], " ");
\r
3519 strcat(parseList[moveNum - 1], elapsed_time);
\r
3520 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3521 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3522 strcat(moveList[moveNum - 1], "\n");
\r
3523 } else if (strcmp(move_str, "none") == 0) {
\r
3524 /* Again, we don't know what the board looked like;
\r
3525 this is really the start of the game. */
\r
3526 parseList[moveNum - 1][0] = NULLCHAR;
\r
3527 moveList[moveNum - 1][0] = NULLCHAR;
\r
3528 backwardMostMove = moveNum;
\r
3529 startedFromSetupPosition = TRUE;
\r
3530 fromX = fromY = toX = toY = -1;
\r
3532 /* Move from ICS was illegal!? Punt. */
\r
3533 if (appData.debugMode) {
\r
3534 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3535 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3538 if (appData.testLegality && appData.debugMode) {
\r
3539 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3540 DisplayError(str, 0);
\r
3543 strcpy(parseList[moveNum - 1], move_str);
\r
3544 strcat(parseList[moveNum - 1], " ");
\r
3545 strcat(parseList[moveNum - 1], elapsed_time);
\r
3546 moveList[moveNum - 1][0] = NULLCHAR;
\r
3547 fromX = fromY = toX = toY = -1;
\r
3549 if (appData.debugMode) {
\r
3550 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3551 setbuf(debugFP, NULL);
\r
3555 /* Send move to chess program (BEFORE animating it). */
\r
3556 if (appData.zippyPlay && !newGame && newMove &&
\r
3557 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3559 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3560 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3561 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3562 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3564 DisplayError(str, 0);
\r
3566 if (first.sendTime) {
\r
3567 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3569 SendMoveToProgram(moveNum - 1, &first);
\r
3571 firstMove = FALSE;
\r
3572 if (first.useColors) {
\r
3573 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3575 "black\ngo\n", &first);
\r
3577 SendToProgram("go\n", &first);
\r
3579 first.maybeThinking = TRUE;
\r
3582 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3583 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3584 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3585 DisplayError(str, 0);
\r
3587 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3588 SendMoveToProgram(moveNum - 1, &first);
\r
3595 if (moveNum > 0 && !gotPremove) {
\r
3596 /* If move comes from a remote source, animate it. If it
\r
3597 isn't remote, it will have already been animated. */
\r
3598 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3599 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3601 if (!pausing && appData.highlightLastMove) {
\r
3602 SetHighlights(fromX, fromY, toX, toY);
\r
3606 /* Start the clocks */
\r
3607 whiteFlag = blackFlag = FALSE;
\r
3608 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3609 if (ticking == 0) {
\r
3610 ics_clock_paused = TRUE;
\r
3612 } else if (ticking == 1) {
\r
3613 ics_clock_paused = FALSE;
\r
3615 if (gameMode == IcsIdle ||
\r
3616 relation == RELATION_OBSERVING_STATIC ||
\r
3617 relation == RELATION_EXAMINING ||
\r
3619 DisplayBothClocks();
\r
3623 /* Display opponents and material strengths */
\r
3624 if (gameInfo.variant != VariantBughouse &&
\r
3625 gameInfo.variant != VariantCrazyhouse) {
\r
3626 if (tinyLayout || smallLayout) {
\r
3627 if(gameInfo.variant == VariantNormal)
\r
3628 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3629 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3630 basetime, increment);
\r
3632 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3633 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3634 basetime, increment, (int) gameInfo.variant);
\r
3636 if(gameInfo.variant == VariantNormal)
\r
3637 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3638 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3639 basetime, increment);
\r
3641 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3642 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3643 basetime, increment, VariantName(gameInfo.variant));
\r
3645 DisplayTitle(str);
\r
3646 if (appData.debugMode) {
\r
3647 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3652 /* Display the board */
\r
3655 if (appData.premove)
\r
3656 if (!gotPremove ||
\r
3657 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3658 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3659 ClearPremoveHighlights();
\r
3661 DrawPosition(FALSE, boards[currentMove]);
\r
3662 DisplayMove(moveNum - 1);
\r
3663 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3667 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3671 GetMoveListEvent()
\r
3673 char buf[MSG_SIZ];
\r
3674 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3675 ics_getting_history = H_REQUESTED;
\r
3676 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3682 AnalysisPeriodicEvent(force)
\r
3685 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3686 && !force) || !appData.periodicUpdates)
\r
3689 /* Send . command to Crafty to collect stats */
\r
3690 SendToProgram(".\n", &first);
\r
3692 /* Don't send another until we get a response (this makes
\r
3693 us stop sending to old Crafty's which don't understand
\r
3694 the "." command (sending illegal cmds resets node count & time,
\r
3695 which looks bad)) */
\r
3696 programStats.ok_to_send = 0;
\r
3700 SendMoveToProgram(moveNum, cps)
\r
3702 ChessProgramState *cps;
\r
3704 char buf[MSG_SIZ];
\r
3706 if (cps->useUsermove) {
\r
3707 SendToProgram("usermove ", cps);
\r
3709 if (cps->useSAN) {
\r
3711 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3712 int len = space - parseList[moveNum];
\r
3713 memcpy(buf, parseList[moveNum], len);
\r
3714 buf[len++] = '\n';
\r
3715 buf[len] = NULLCHAR;
\r
3717 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3719 SendToProgram(buf, cps);
\r
3721 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3722 * the engine. It would be nice to have a better way to identify castle
\r
3724 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3725 && cps->useOOCastle) {
\r
3726 if (appData.debugMode) {
\r
3727 fprintf(debugFP, "Tord's FRC castling code\n");
\r
3729 int fromX = moveList[moveNum][0] - AAA;
\r
3730 int fromY = moveList[moveNum][1] - ONE;
\r
3731 int toX = moveList[moveNum][2] - AAA;
\r
3732 int toY = moveList[moveNum][3] - ONE;
\r
3733 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3734 && boards[moveNum][toY][toX] == WhiteRook)
\r
3735 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3736 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3737 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3738 else SendToProgram("O-O-O\n", cps);
\r
3740 else SendToProgram(moveList[moveNum], cps);
\r
3742 else SendToProgram(moveList[moveNum], cps);
\r
3743 /* End of additions by Tord */
\r
3746 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3747 /* Send 'go' if we are in a mode where machine should play. */
\r
3748 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3749 (gameMode == TwoMachinesPlay ||
\r
3751 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3753 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3754 SendToProgram("go\n", cps);
\r
3755 if (appData.debugMode) {
\r
3756 fprintf(debugFP, "(extra)\n");
\r
3759 setboardSpoiledMachineBlack = 0;
\r
3763 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3764 ChessMove moveType;
\r
3765 int fromX, fromY, toX, toY;
\r
3767 char user_move[MSG_SIZ];
\r
3769 switch (moveType) {
\r
3771 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3772 (int)moveType, fromX, fromY, toX, toY);
\r
3773 DisplayError(user_move + strlen("say "), 0);
\r
3775 case WhiteKingSideCastle:
\r
3776 case BlackKingSideCastle:
\r
3777 case WhiteQueenSideCastleWild:
\r
3778 case BlackQueenSideCastleWild:
\r
3780 case WhiteHSideCastleFR:
\r
3781 case BlackHSideCastleFR:
\r
3783 sprintf(user_move, "o-o\n");
\r
3785 case WhiteQueenSideCastle:
\r
3786 case BlackQueenSideCastle:
\r
3787 case WhiteKingSideCastleWild:
\r
3788 case BlackKingSideCastleWild:
\r
3790 case WhiteASideCastleFR:
\r
3791 case BlackASideCastleFR:
\r
3793 sprintf(user_move, "o-o-o\n");
\r
3795 case WhitePromotionQueen:
\r
3796 case BlackPromotionQueen:
\r
3797 case WhitePromotionRook:
\r
3798 case BlackPromotionRook:
\r
3799 case WhitePromotionBishop:
\r
3800 case BlackPromotionBishop:
\r
3801 case WhitePromotionKnight:
\r
3802 case BlackPromotionKnight:
\r
3803 case WhitePromotionKing:
\r
3804 case BlackPromotionKing:
\r
3805 case WhitePromotionChancellor:
\r
3806 case BlackPromotionChancellor:
\r
3807 case WhitePromotionArchbishop:
\r
3808 case BlackPromotionArchbishop:
\r
3809 if(gameInfo.variant == VariantShatranj)
\r
3810 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3811 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3812 PieceToChar(WhiteFerz));
\r
3814 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3815 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3816 PieceToChar(PromoPiece(moveType)));
\r
3820 sprintf(user_move, "%c@%c%c\n",
\r
3821 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3822 AAA + toX, ONE + toY);
\r
3825 case WhiteCapturesEnPassant:
\r
3826 case BlackCapturesEnPassant:
\r
3827 case IllegalMove: /* could be a variant we don't quite understand */
\r
3828 sprintf(user_move, "%c%c%c%c\n",
\r
3829 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3832 SendToICS(user_move);
\r
3836 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3837 int rf, ff, rt, ft;
\r
3841 if (rf == DROP_RANK) {
\r
3842 sprintf(move, "%c@%c%c\n",
\r
3843 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3845 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3846 sprintf(move, "%c%c%c%c\n",
\r
3847 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3849 sprintf(move, "%c%c%c%c%c\n",
\r
3850 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3853 AlphaRank(move, 4);
\r
3857 ProcessICSInitScript(f)
\r
3860 char buf[MSG_SIZ];
\r
3862 while (fgets(buf, MSG_SIZ, f)) {
\r
3863 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3870 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3872 AlphaRank(char *move, int n)
\r
3874 char *p = move, c; int x, y;
\r
3876 if( !appData.alphaRank ) return;
\r
3878 if (appData.debugMode) {
\r
3879 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
3882 if(move[1]=='*' &&
\r
3883 move[2]>='0' && move[2]<='9' &&
\r
3884 move[3]>='a' && move[3]<='x' ) {
\r
3885 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3886 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3888 if(move[0]>='0' && move[0]<='9' &&
\r
3889 move[1]>='a' && move[1]<='x' &&
\r
3890 move[2]>='0' && move[2]<='9' &&
\r
3891 move[3]>='a' && move[3]<='x' ) {
\r
3892 /* input move, Shogi -> normal */
\r
3893 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
3894 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
3895 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3896 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3898 if(move[1]=='@' &&
\r
3899 move[3]>='0' && move[3]<='9' &&
\r
3900 move[2]>='a' && move[2]<='x' ) {
\r
3902 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3903 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3906 move[0]>='a' && move[0]<='x' &&
\r
3907 move[3]>='0' && move[3]<='9' &&
\r
3908 move[2]>='a' && move[2]<='x' ) {
\r
3909 /* output move, normal -> Shogi */
\r
3910 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
3911 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
3912 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3913 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3914 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
3916 if (appData.debugMode) {
\r
3917 fprintf(debugFP, " out = '%s'\n", move);
\r
3921 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3923 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3926 ChessMove *moveType;
\r
3927 int *fromX, *fromY, *toX, *toY;
\r
3930 if (appData.debugMode) {
\r
3931 fprintf(debugFP, "move to parse: %s\n", move);
\r
3933 *moveType = yylexstr(moveNum, move);
\r
3935 switch (*moveType) {
\r
3936 case WhitePromotionChancellor:
\r
3937 case BlackPromotionChancellor:
\r
3938 case WhitePromotionArchbishop:
\r
3939 case BlackPromotionArchbishop:
\r
3940 case WhitePromotionQueen:
\r
3941 case BlackPromotionQueen:
\r
3942 case WhitePromotionRook:
\r
3943 case BlackPromotionRook:
\r
3944 case WhitePromotionBishop:
\r
3945 case BlackPromotionBishop:
\r
3946 case WhitePromotionKnight:
\r
3947 case BlackPromotionKnight:
\r
3948 case WhitePromotionKing:
\r
3949 case BlackPromotionKing:
\r
3951 case WhiteCapturesEnPassant:
\r
3952 case BlackCapturesEnPassant:
\r
3953 case WhiteKingSideCastle:
\r
3954 case WhiteQueenSideCastle:
\r
3955 case BlackKingSideCastle:
\r
3956 case BlackQueenSideCastle:
\r
3957 case WhiteKingSideCastleWild:
\r
3958 case WhiteQueenSideCastleWild:
\r
3959 case BlackKingSideCastleWild:
\r
3960 case BlackQueenSideCastleWild:
\r
3961 /* Code added by Tord: */
\r
3962 case WhiteHSideCastleFR:
\r
3963 case WhiteASideCastleFR:
\r
3964 case BlackHSideCastleFR:
\r
3965 case BlackASideCastleFR:
\r
3966 /* End of code added by Tord */
\r
3967 case IllegalMove: /* bug or odd chess variant */
\r
3968 *fromX = currentMoveString[0] - AAA;
\r
3969 *fromY = currentMoveString[1] - ONE;
\r
3970 *toX = currentMoveString[2] - AAA;
\r
3971 *toY = currentMoveString[3] - ONE;
\r
3972 *promoChar = currentMoveString[4];
\r
3973 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3974 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3975 if (appData.debugMode) {
\r
3976 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
3978 *fromX = *fromY = *toX = *toY = 0;
\r
3981 if (appData.testLegality) {
\r
3982 return (*moveType != IllegalMove);
\r
3984 return !(fromX == fromY && toX == toY);
\r
3989 *fromX = *moveType == WhiteDrop ?
\r
3990 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3991 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3992 *fromY = DROP_RANK;
\r
3993 *toX = currentMoveString[2] - AAA;
\r
3994 *toY = currentMoveString[3] - ONE;
\r
3995 *promoChar = NULLCHAR;
\r
3998 case AmbiguousMove:
\r
3999 case ImpossibleMove:
\r
4000 case (ChessMove) 0: /* end of file */
\r
4009 if (appData.debugMode) {
\r
4010 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4013 *fromX = *fromY = *toX = *toY = 0;
\r
4014 *promoChar = NULLCHAR;
\r
4019 /* [AS] FRC game initialization */
\r
4020 static int FindEmptySquare( Board board, int n )
\r
4025 while( board[0][i] != EmptySquare ) i++;
\r
4036 static void ShuffleFRC( Board board )
\r
4042 for( i=0; i<8; i++ ) {
\r
4043 board[0][i] = EmptySquare;
\r
4046 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4047 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4048 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4049 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4050 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4051 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4052 initialRights[1] = initialRights[4] =
\r
4053 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4054 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4055 initialRights[2] = initialRights[5] =
\r
4056 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4057 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4058 initialRights[0] = initialRights[3] =
\r
4059 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4061 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4062 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4066 static unsigned char FRC_KnightTable[10] = {
\r
4067 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4070 static void SetupFRC( Board board, int pos_index )
\r
4073 unsigned char knights;
\r
4075 /* Bring the position index into a safe range (just in case...) */
\r
4076 if( pos_index < 0 ) pos_index = 0;
\r
4080 /* Clear the board */
\r
4081 for( i=0; i<8; i++ ) {
\r
4082 board[0][i] = EmptySquare;
\r
4085 /* Place bishops and queen */
\r
4086 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4089 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4092 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4095 /* Place knigths */
\r
4096 knights = FRC_KnightTable[ pos_index ];
\r
4098 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4099 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4101 /* Place rooks and king */
\r
4102 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4103 initialRights[1] = initialRights[4] =
\r
4104 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4105 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4106 initialRights[2] = initialRights[5] =
\r
4107 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4108 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4109 initialRights[0] = initialRights[3] =
\r
4110 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4112 /* Mirror piece placement for black */
\r
4113 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4114 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4118 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4119 // All positions will have equal probability, but the current method will not provide a unique
\r
4120 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4125 int squaresLeft[4];
\r
4126 int piecesLeft[(int)BlackPawn];
\r
4127 long long int seed, nrOfShuffles;
\r
4129 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4130 // put the piece on the (n-1)-th empty squares of the given shade
\r
4134 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4135 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4136 board[rank][i] = (ChessSquare) pieceType;
\r
4137 squaresLeft[(i-BOARD_LEFT&1) + 1]--;
\r
4138 squaresLeft[ANY]--;
\r
4139 piecesLeft[pieceType]--;
\r
4147 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4148 // calculate where the next piece goes, (any empty square), and put it there
\r
4152 i = seed % squaresLeft[shade];
\r
4153 nrOfShuffles *= squaresLeft[shade];
\r
4154 seed /= squaresLeft[shade];
\r
4155 put(board, pieceType, rank, i, shade);
\r
4158 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4159 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4161 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4163 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4164 i = seed % k; // pick one
\r
4165 nrOfShuffles *= k;
\r
4167 while(i >= j) i -= j--;
\r
4168 j = n - 1 - j; i += j;
\r
4169 put(board, pieceType, rank, j, ANY);
\r
4170 put(board, pieceType, rank, i, ANY);
\r
4173 void SetUpShuffle(Board board, int number)
\r
4175 int i, p, first=1;
\r
4177 seed = number, nrOfShuffles = 1;
\r
4180 for(i=0; i<50; i++) seed += rand();
\r
4181 seed = rand() ^ rand()<<16;
\r
4184 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4185 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4186 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4188 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4190 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4191 p = (int) board[0][i];
\r
4192 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4193 board[0][i] = EmptySquare;
\r
4196 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4197 // shuffles restricted to allow normal castling put KRR first
\r
4198 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4199 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4200 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4201 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4202 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4203 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4204 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4205 put(board, WhiteRook, 0, 0, ANY);
\r
4206 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4209 if((BOARD_RGHT-BOARD_LEFT & 1) == 0)
\r
4210 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4211 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4212 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4213 while(piecesLeft[p] >= 2) {
\r
4214 AddOnePiece(board, p, 0, LITE);
\r
4215 AddOnePiece(board, p, 0, DARK);
\r
4217 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4220 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4221 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4222 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4223 if(p == (int)WhiteRook) continue;
\r
4224 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4225 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4228 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4230 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4231 // Last King gets castling rights
\r
4232 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4233 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4234 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4237 while(piecesLeft[(int)WhiteKing]) {
\r
4238 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4239 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4244 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4245 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4248 // Only Rooks can be left; simply place them all
\r
4249 while(piecesLeft[(int)WhiteRook]) {
\r
4250 i = put(board, WhiteRook, 0, 0, ANY);
\r
4251 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4254 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4256 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4259 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4260 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4263 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4268 BOOL SetCharTable( char *table, const char * map )
\r
4269 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4270 /* Basically a safe strcpy that uses the last character as King */
\r
4272 BOOL result = FALSE; int NrPieces;
\r
4274 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4275 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4276 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4278 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4279 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4280 table[i] = map[i];
\r
4281 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4283 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4284 table[(int) BlackKing] = map[NrPieces-1];
\r
4293 InitPosition(redraw)
\r
4296 ChessSquare (* pieces)[BOARD_SIZE];
\r
4297 int i, j, pawnRow, overrule,
\r
4298 oldx = gameInfo.boardWidth,
\r
4299 oldy = gameInfo.boardHeight,
\r
4300 oldh = gameInfo.holdingsWidth,
\r
4301 oldv = gameInfo.variant;
\r
4303 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4304 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4306 /* [AS] Initialize pv info list [HGM] and game status */
\r
4308 for( i=0; i<MAX_MOVES; i++ ) {
\r
4309 pvInfoList[i].depth = 0;
\r
4310 epStatus[i]=EP_NONE;
\r
4311 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4314 initialRulePlies = 0; /* 50-move counter start */
\r
4316 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4317 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4321 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4322 /* the initialized data only consist of the two backranks. The switch */
\r
4323 /* selects which one we will use, which is than copied to the Board */
\r
4324 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4325 /* empty squares. This initial position is then copied to boards[0], */
\r
4326 /* possibly after shuffling, so that it remains available. */
\r
4328 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4329 gameInfo.boardWidth = 8;
\r
4330 gameInfo.boardHeight = 8;
\r
4331 gameInfo.holdingsSize = 0;
\r
4332 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4333 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4334 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4336 switch (gameInfo.variant) {
\r
4337 case VariantFischeRandom:
\r
4338 shuffleOpenings = TRUE;
\r
4340 pieces = FIDEArray;
\r
4342 case VariantShatranj:
\r
4343 pieces = ShatranjArray;
\r
4344 nrCastlingRights = 0;
\r
4345 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4347 case VariantTwoKings:
\r
4348 pieces = twoKingsArray;
\r
4349 nrCastlingRights = 8; /* add rights for second King */
\r
4350 castlingRights[0][6] = initialRights[2] = 5;
\r
4351 castlingRights[0][7] = initialRights[5] = 5;
\r
4352 castlingRank[6] = 0;
\r
4353 castlingRank[7] = BOARD_HEIGHT-1;
\r
4355 case VariantCapaRandom:
\r
4356 shuffleOpenings = TRUE;
\r
4357 case VariantCapablanca:
\r
4358 pieces = CapablancaArray;
\r
4359 gameInfo.boardWidth = 10;
\r
4360 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4362 case VariantGothic:
\r
4363 pieces = GothicArray;
\r
4364 gameInfo.boardWidth = 10;
\r
4365 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4367 case VariantJanus:
\r
4368 pieces = JanusArray;
\r
4369 gameInfo.boardWidth = 10;
\r
4370 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4371 nrCastlingRights = 6;
\r
4372 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4373 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4374 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;
\r
4375 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4376 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4377 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;
\r
4379 case VariantFalcon:
\r
4380 pieces = FalconArray;
\r
4381 gameInfo.boardWidth = 10;
\r
4382 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4384 case VariantXiangqi:
\r
4385 pieces = XiangqiArray;
\r
4386 gameInfo.boardWidth = 9;
\r
4387 gameInfo.boardHeight = 10;
\r
4388 nrCastlingRights = 0;
\r
4389 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4391 case VariantShogi:
\r
4392 pieces = ShogiArray;
\r
4393 gameInfo.boardWidth = 9;
\r
4394 gameInfo.boardHeight = 9;
\r
4395 gameInfo.holdingsSize = 7;
\r
4396 nrCastlingRights = 0;
\r
4397 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4399 case VariantCourier:
\r
4400 pieces = CourierArray;
\r
4401 gameInfo.boardWidth = 12;
\r
4402 nrCastlingRights = 0;
\r
4403 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4404 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4406 case VariantKnightmate:
\r
4407 pieces = KnightmateArray;
\r
4408 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4410 case VariantFairy:
\r
4411 pieces = fairyArray;
\r
4412 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4414 case VariantCrazyhouse:
\r
4415 case VariantBughouse:
\r
4416 pieces = FIDEArray;
\r
4417 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4418 gameInfo.holdingsSize = 5;
\r
4420 case VariantWildCastle:
\r
4421 pieces = FIDEArray;
\r
4422 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4423 shuffleOpenings = 1;
\r
4425 case VariantNoCastle:
\r
4426 pieces = FIDEArray;
\r
4427 nrCastlingRights = 0;
\r
4428 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4429 /* !!?unconstrained back-rank shuffle */
\r
4430 shuffleOpenings = 1;
\r
4435 if(appData.NrFiles >= 0) {
\r
4436 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4437 gameInfo.boardWidth = appData.NrFiles;
\r
4439 if(appData.NrRanks >= 0) {
\r
4440 gameInfo.boardHeight = appData.NrRanks;
\r
4442 if(appData.holdingsSize >= 0) {
\r
4443 i = appData.holdingsSize;
\r
4444 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4445 gameInfo.holdingsSize = i;
\r
4447 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4448 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4449 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
4451 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4452 if(pawnRow < 1) pawnRow = 1;
\r
4454 /* User pieceToChar list overrules defaults */
\r
4455 if(appData.pieceToCharTable != NULL)
\r
4456 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4458 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4460 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4461 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4462 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4463 initialPosition[i][j] = s;
\r
4465 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4466 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4467 initialPosition[pawnRow][j] = WhitePawn;
\r
4468 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4469 if(gameInfo.variant == VariantXiangqi) {
\r
4471 initialPosition[pawnRow][j] =
\r
4472 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4473 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4474 initialPosition[2][j] = WhiteCannon;
\r
4475 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4479 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4481 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4484 initialPosition[1][j] = WhiteBishop;
\r
4485 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4487 initialPosition[1][j] = WhiteRook;
\r
4488 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4491 if( nrCastlingRights == -1) {
\r
4492 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4493 /* This sets default castling rights from none to normal corners */
\r
4494 /* Variants with other castling rights must set them themselves above */
\r
4495 nrCastlingRights = 6;
\r
4497 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4498 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4499 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4500 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4501 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4502 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4506 if(gameInfo.variant == VariantFischeRandom) {
\r
4507 if( appData.defaultFrcPosition < 0 ) {
\r
4508 ShuffleFRC( initialPosition );
\r
4511 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4513 startedFromSetupPosition = TRUE;
\r
4516 if (appData.debugMode) {
\r
4517 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4519 if(shuffleOpenings) {
\r
4520 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4521 startedFromSetupPosition = TRUE;
\r
4524 if(startedFromPositionFile) {
\r
4525 /* [HGM] loadPos: use PositionFile for every new game */
\r
4526 CopyBoard(initialPosition, filePosition);
\r
4527 for(i=0; i<nrCastlingRights; i++)
\r
4528 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4529 startedFromSetupPosition = TRUE;
\r
4532 CopyBoard(boards[0], initialPosition);
\r
4534 if(oldx != gameInfo.boardWidth ||
\r
4535 oldy != gameInfo.boardHeight ||
\r
4536 oldh != gameInfo.holdingsWidth
\r
4538 || oldv == VariantGothic || // For licensing popups
\r
4539 gameInfo.variant == VariantGothic
\r
4542 || oldv == VariantFalcon ||
\r
4543 gameInfo.variant == VariantFalcon
\r
4546 InitDrawingSizes(-2 ,0);
\r
4549 DrawPosition(TRUE, boards[currentMove]);
\r
4553 SendBoard(cps, moveNum)
\r
4554 ChessProgramState *cps;
\r
4557 char message[MSG_SIZ];
\r
4559 if (cps->useSetboard) {
\r
4560 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4561 sprintf(message, "setboard %s\n", fen);
\r
4562 SendToProgram(message, cps);
\r
4568 /* Kludge to set black to move, avoiding the troublesome and now
\r
4569 * deprecated "black" command.
\r
4571 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4573 SendToProgram("edit\n", cps);
\r
4574 SendToProgram("#\n", cps);
\r
4575 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4576 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4577 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4578 if ((int) *bp < (int) BlackPawn) {
\r
4579 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4580 AAA + j, ONE + i);
\r
4581 if(message[0] == '+' || message[0] == '~') {
\r
4582 sprintf(message, "%c%c%c+\n",
\r
4583 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4584 AAA + j, ONE + i);
\r
4586 if(appData.alphaRank) {
\r
4587 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4588 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4590 SendToProgram(message, cps);
\r
4595 SendToProgram("c\n", cps);
\r
4596 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4597 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4598 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4599 if (((int) *bp != (int) EmptySquare)
\r
4600 && ((int) *bp >= (int) BlackPawn)) {
\r
4601 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4602 AAA + j, ONE + i);
\r
4603 if(message[0] == '+' || message[0] == '~') {
\r
4604 sprintf(message, "%c%c%c+\n",
\r
4605 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4606 AAA + j, ONE + i);
\r
4608 if(appData.alphaRank) {
\r
4609 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4610 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4612 SendToProgram(message, cps);
\r
4617 SendToProgram(".\n", cps);
\r
4619 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4623 IsPromotion(fromX, fromY, toX, toY)
\r
4624 int fromX, fromY, toX, toY;
\r
4626 /* [HGM] add Shogi promotions */
\r
4627 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4628 ChessSquare piece;
\r
4630 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4631 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4632 /* [HGM] Note to self: line above also weeds out drops */
\r
4633 piece = boards[currentMove][fromY][fromX];
\r
4634 if(gameInfo.variant == VariantShogi) {
\r
4635 promotionZoneSize = 3;
\r
4636 highestPromotingPiece = (int)WhiteKing;
\r
4637 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4638 and if in normal chess we then allow promotion to King, why not
\r
4639 allow promotion of other piece in Shogi? */
\r
4641 if((int)piece >= BlackPawn) {
\r
4642 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4644 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4646 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4647 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4649 return ( (int)piece <= highestPromotingPiece );
\r
4653 InPalace(row, column)
\r
4655 { /* [HGM] for Xiangqi */
\r
4656 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4657 column < (BOARD_WIDTH + 4)/2 &&
\r
4658 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4663 PieceForSquare (x, y)
\r
4667 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4670 return boards[currentMove][y][x];
\r
4674 OKToStartUserMove(x, y)
\r
4677 ChessSquare from_piece;
\r
4680 if (matchMode) return FALSE;
\r
4681 if (gameMode == EditPosition) return TRUE;
\r
4683 if (x >= 0 && y >= 0)
\r
4684 from_piece = boards[currentMove][y][x];
\r
4686 from_piece = EmptySquare;
\r
4688 if (from_piece == EmptySquare) return FALSE;
\r
4690 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4691 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4693 switch (gameMode) {
\r
4694 case PlayFromGameFile:
\r
4696 case TwoMachinesPlay:
\r
4700 case IcsObserving:
\r
4704 case MachinePlaysWhite:
\r
4705 case IcsPlayingBlack:
\r
4706 if (appData.zippyPlay) return FALSE;
\r
4707 if (white_piece) {
\r
4708 DisplayMoveError("You are playing Black");
\r
4713 case MachinePlaysBlack:
\r
4714 case IcsPlayingWhite:
\r
4715 if (appData.zippyPlay) return FALSE;
\r
4716 if (!white_piece) {
\r
4717 DisplayMoveError("You are playing White");
\r
4723 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4724 DisplayMoveError("It is White's turn");
\r
4727 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4728 DisplayMoveError("It is Black's turn");
\r
4731 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4732 /* Editing correspondence game history */
\r
4733 /* Could disallow this or prompt for confirmation */
\r
4734 cmailOldMove = -1;
\r
4736 if (currentMove < forwardMostMove) {
\r
4737 /* Discarding moves */
\r
4738 /* Could prompt for confirmation here,
\r
4739 but I don't think that's such a good idea */
\r
4740 forwardMostMove = currentMove;
\r
4744 case BeginningOfGame:
\r
4745 if (appData.icsActive) return FALSE;
\r
4746 if (!appData.noChessProgram) {
\r
4747 if (!white_piece) {
\r
4748 DisplayMoveError("You are playing White");
\r
4755 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4756 DisplayMoveError("It is White's turn");
\r
4759 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4760 DisplayMoveError("It is Black's turn");
\r
4766 case IcsExamining:
\r
4769 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4770 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4771 DisplayMoveError("Displayed position is not current");
\r
4777 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4778 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4779 int lastLoadGameUseList = FALSE;
\r
4780 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4781 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4785 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4786 int fromX, fromY, toX, toY;
\r
4789 ChessMove moveType;
\r
4790 ChessSquare pdown, pup;
\r
4792 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4793 if ((fromX == toX) && (fromY == toY)) {
\r
4794 return ImpossibleMove;
\r
4797 /* [HGM] suppress all moves into holdings area and guard band */
\r
4798 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4799 return ImpossibleMove;
\r
4801 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4802 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
4803 pdown = boards[currentMove][fromY][fromX];
\r
4804 pup = boards[currentMove][toY][toX];
\r
4805 if ( gameMode != EditPosition &&
\r
4806 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4807 WhitePawn <= pup && pup < BlackPawn ||
\r
4808 BlackPawn <= pdown && pdown < EmptySquare &&
\r
4809 BlackPawn <= pup && pup < EmptySquare
\r
4810 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
4811 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
4812 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
4814 return ImpossibleMove;
\r
4816 /* Check if the user is playing in turn. This is complicated because we
\r
4817 let the user "pick up" a piece before it is his turn. So the piece he
\r
4818 tried to pick up may have been captured by the time he puts it down!
\r
4819 Therefore we use the color the user is supposed to be playing in this
\r
4820 test, not the color of the piece that is currently on the starting
\r
4821 square---except in EditGame mode, where the user is playing both
\r
4822 sides; fortunately there the capture race can't happen. (It can
\r
4823 now happen in IcsExamining mode, but that's just too bad. The user
\r
4824 will get a somewhat confusing message in that case.)
\r
4827 switch (gameMode) {
\r
4828 case PlayFromGameFile:
\r
4830 case TwoMachinesPlay:
\r
4832 case IcsObserving:
\r
4834 /* We switched into a game mode where moves are not accepted,
\r
4835 perhaps while the mouse button was down. */
\r
4836 return ImpossibleMove;
\r
4838 case MachinePlaysWhite:
\r
4839 /* User is moving for Black */
\r
4840 if (WhiteOnMove(currentMove)) {
\r
4841 DisplayMoveError("It is White's turn");
\r
4842 return ImpossibleMove;
\r
4846 case MachinePlaysBlack:
\r
4847 /* User is moving for White */
\r
4848 if (!WhiteOnMove(currentMove)) {
\r
4849 DisplayMoveError("It is Black's turn");
\r
4850 return ImpossibleMove;
\r
4855 case IcsExamining:
\r
4856 case BeginningOfGame:
\r
4859 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4860 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4861 /* User is moving for Black */
\r
4862 if (WhiteOnMove(currentMove)) {
\r
4863 DisplayMoveError("It is White's turn");
\r
4864 return ImpossibleMove;
\r
4867 /* User is moving for White */
\r
4868 if (!WhiteOnMove(currentMove)) {
\r
4869 DisplayMoveError("It is Black's turn");
\r
4870 return ImpossibleMove;
\r
4875 case IcsPlayingBlack:
\r
4876 /* User is moving for Black */
\r
4877 if (WhiteOnMove(currentMove)) {
\r
4878 if (!appData.premove) {
\r
4879 DisplayMoveError("It is White's turn");
\r
4880 } else if (toX >= 0 && toY >= 0) {
\r
4883 premoveFromX = fromX;
\r
4884 premoveFromY = fromY;
\r
4885 premovePromoChar = promoChar;
\r
4887 if (appData.debugMode)
\r
4888 fprintf(debugFP, "Got premove: fromX %d,"
\r
4889 "fromY %d, toX %d, toY %d\n",
\r
4890 fromX, fromY, toX, toY);
\r
4892 return ImpossibleMove;
\r
4896 case IcsPlayingWhite:
\r
4897 /* User is moving for White */
\r
4898 if (!WhiteOnMove(currentMove)) {
\r
4899 if (!appData.premove) {
\r
4900 DisplayMoveError("It is Black's turn");
\r
4901 } else if (toX >= 0 && toY >= 0) {
\r
4904 premoveFromX = fromX;
\r
4905 premoveFromY = fromY;
\r
4906 premovePromoChar = promoChar;
\r
4908 if (appData.debugMode)
\r
4909 fprintf(debugFP, "Got premove: fromX %d,"
\r
4910 "fromY %d, toX %d, toY %d\n",
\r
4911 fromX, fromY, toX, toY);
\r
4913 return ImpossibleMove;
\r
4920 case EditPosition:
\r
4921 /* EditPosition, empty square, or different color piece;
\r
4922 click-click move is possible */
\r
4923 if (toX == -2 || toY == -2) {
\r
4924 boards[0][fromY][fromX] = EmptySquare;
\r
4925 DrawPosition(FALSE, boards[currentMove]);
\r
4926 } else if (toX >= 0 && toY >= 0) {
\r
4927 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4928 boards[0][fromY][fromX] = EmptySquare;
\r
4929 DrawPosition(FALSE, boards[currentMove]);
\r
4931 return ImpossibleMove;
\r
4934 /* [HGM] If move started in holdings, it means a drop */
\r
4935 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4936 if( pup != EmptySquare ) return ImpossibleMove;
\r
4937 if(appData.testLegality) {
\r
4938 /* it would be more logical if LegalityTest() also figured out
\r
4939 * which drops are legal. For now we forbid pawns on back rank.
\r
4940 * Shogi is on its own here...
\r
4942 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
4943 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
4944 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
4946 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4949 userOfferedDraw = FALSE;
\r
4951 /* [HGM] always test for legality, to get promotion info */
\r
4952 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4953 epStatus[currentMove], castlingRights[currentMove],
\r
4954 fromY, fromX, toY, toX, promoChar);
\r
4956 /* [HGM] but possibly ignore an IllegalMove result */
\r
4957 if (appData.testLegality) {
\r
4958 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4959 DisplayMoveError("Illegal move");
\r
4960 return ImpossibleMove;
\r
4965 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4966 function is made into one that returns an OK move type if FinishMove
\r
4967 should be called. This to give the calling driver routine the
\r
4968 opportunity to finish the userMove input with a promotion popup,
\r
4969 without bothering the user with this for invalid or illegal moves */
\r
4971 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4974 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4976 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4977 ChessMove moveType;
\r
4978 int fromX, fromY, toX, toY;
\r
4979 /*char*/int promoChar;
\r
4981 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4982 move type in caller when we know the move is a legal promotion */
\r
4983 if(moveType == NormalMove)
\r
4984 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4986 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4987 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4988 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4989 fromX = boards[currentMove][fromY][fromX];
\r
4990 fromY = DROP_RANK;
\r
4993 /* [HGM] <popupFix> The following if has been moved here from
\r
4994 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
4995 piece drops in training games?), and because it can only be
\r
4996 performed after it is known to what we promote. */
\r
4997 if (gameMode == Training) {
\r
4998 /* compare the move played on the board to the next move in the
\r
4999 * game. If they match, display the move and the opponent's response.
\r
5000 * If they don't match, display an error message.
\r
5004 CopyBoard(testBoard, boards[currentMove]);
\r
5005 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5007 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5008 ForwardInner(currentMove+1);
\r
5010 /* Autoplay the opponent's response.
\r
5011 * if appData.animate was TRUE when Training mode was entered,
\r
5012 * the response will be animated.
\r
5014 saveAnimate = appData.animate;
\r
5015 appData.animate = animateTraining;
\r
5016 ForwardInner(currentMove+1);
\r
5017 appData.animate = saveAnimate;
\r
5019 /* check for the end of the game */
\r
5020 if (currentMove >= forwardMostMove) {
\r
5021 gameMode = PlayFromGameFile;
\r
5023 SetTrainingModeOff();
\r
5024 DisplayInformation("End of game");
\r
5027 DisplayError("Incorrect move", 0);
\r
5032 /* Ok, now we know that the move is good, so we can kill
\r
5033 the previous line in Analysis Mode */
\r
5034 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5035 forwardMostMove = currentMove;
\r
5038 /* If we need the chess program but it's dead, restart it */
\r
5039 ResurrectChessProgram();
\r
5041 /* A user move restarts a paused game*/
\r
5045 thinkOutput[0] = NULLCHAR;
\r
5047 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5049 if (gameMode == BeginningOfGame) {
\r
5050 if (appData.noChessProgram) {
\r
5051 gameMode = EditGame;
\r
5054 char buf[MSG_SIZ];
\r
5055 gameMode = MachinePlaysBlack;
\r
5057 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5058 DisplayTitle(buf);
\r
5059 if (first.sendName) {
\r
5060 sprintf(buf, "name %s\n", gameInfo.white);
\r
5061 SendToProgram(buf, &first);
\r
5068 /* Relay move to ICS or chess engine */
\r
5069 if (appData.icsActive) {
\r
5070 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5071 gameMode == IcsExamining) {
\r
5072 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5073 ics_user_moved = 1;
\r
5076 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5077 gameMode == MachinePlaysWhite ||
\r
5078 gameMode == MachinePlaysBlack)) {
\r
5079 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5081 SendMoveToProgram(forwardMostMove-1, &first);
\r
5082 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5083 first.maybeThinking = TRUE;
\r
5085 if (currentMove == cmailOldMove + 1) {
\r
5086 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5090 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5092 switch (gameMode) {
\r
5094 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5095 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5099 case MT_CHECKMATE:
\r
5100 if (WhiteOnMove(currentMove)) {
\r
5101 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5103 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5106 case MT_STALEMATE:
\r
5107 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5112 case MachinePlaysBlack:
\r
5113 case MachinePlaysWhite:
\r
5114 /* disable certain menu options while machine is thinking */
\r
5115 SetMachineThinkingEnables();
\r
5124 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5125 int fromX, fromY, toX, toY;
\r
5128 /* [HGM] This routine was added to allow calling of its two logical
\r
5129 parts from other modules in the old way. Before, UserMoveEvent()
\r
5130 automatically called FinishMove() if the move was OK, and returned
\r
5131 otherwise. I separated the two, in order to make it possible to
\r
5132 slip a promotion popup in between. But that it always needs two
\r
5133 calls, to the first part, (now called UserMoveTest() ), and to
\r
5134 FinishMove if the first part succeeded. Calls that do not need
\r
5135 to do anything in between, can call this routine the old way.
\r
5137 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5139 if(moveType != ImpossibleMove)
\r
5140 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5143 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5145 char * hint = lastHint;
\r
5146 FrontEndProgramStats stats;
\r
5148 stats.which = cps == &first ? 0 : 1;
\r
5149 stats.depth = cpstats->depth;
\r
5150 stats.nodes = cpstats->nodes;
\r
5151 stats.score = cpstats->score;
\r
5152 stats.time = cpstats->time;
\r
5153 stats.pv = cpstats->movelist;
\r
5154 stats.hint = lastHint;
\r
5155 stats.an_move_index = 0;
\r
5156 stats.an_move_count = 0;
\r
5158 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5159 stats.hint = cpstats->move_name;
\r
5160 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5161 stats.an_move_count = cpstats->nr_moves;
\r
5164 SetProgramStats( &stats );
\r
5168 HandleMachineMove(message, cps)
\r
5170 ChessProgramState *cps;
\r
5172 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5173 char realname[MSG_SIZ];
\r
5174 int fromX, fromY, toX, toY;
\r
5175 ChessMove moveType;
\r
5181 * Kludge to ignore BEL characters
\r
5183 while (*message == '\007') message++;
\r
5186 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5188 if(cps->debug && *message == '#') return;
\r
5191 * Look for book output
\r
5193 if (cps == &first && bookRequested) {
\r
5194 if (message[0] == '\t' || message[0] == ' ') {
\r
5195 /* Part of the book output is here; append it */
\r
5196 strcat(bookOutput, message);
\r
5197 strcat(bookOutput, " \n");
\r
5199 } else if (bookOutput[0] != NULLCHAR) {
\r
5200 /* All of book output has arrived; display it */
\r
5201 char *p = bookOutput;
\r
5202 while (*p != NULLCHAR) {
\r
5203 if (*p == '\t') *p = ' ';
\r
5206 DisplayInformation(bookOutput);
\r
5207 bookRequested = FALSE;
\r
5208 /* Fall through to parse the current output */
\r
5213 * Look for machine move.
\r
5215 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5216 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5218 /* This method is only useful on engines that support ping */
\r
5219 if (cps->lastPing != cps->lastPong) {
\r
5220 if (gameMode == BeginningOfGame) {
\r
5221 /* Extra move from before last new; ignore */
\r
5222 if (appData.debugMode) {
\r
5223 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5226 if (appData.debugMode) {
\r
5227 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5228 cps->which, gameMode);
\r
5231 SendToProgram("undo\n", cps);
\r
5236 switch (gameMode) {
\r
5237 case BeginningOfGame:
\r
5238 /* Extra move from before last reset; ignore */
\r
5239 if (appData.debugMode) {
\r
5240 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5247 /* Extra move after we tried to stop. The mode test is
\r
5248 not a reliable way of detecting this problem, but it's
\r
5249 the best we can do on engines that don't support ping.
\r
5251 if (appData.debugMode) {
\r
5252 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5253 cps->which, gameMode);
\r
5255 SendToProgram("undo\n", cps);
\r
5258 case MachinePlaysWhite:
\r
5259 case IcsPlayingWhite:
\r
5260 machineWhite = TRUE;
\r
5263 case MachinePlaysBlack:
\r
5264 case IcsPlayingBlack:
\r
5265 machineWhite = FALSE;
\r
5268 case TwoMachinesPlay:
\r
5269 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5272 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5273 if (appData.debugMode) {
\r
5275 "Ignoring move out of turn by %s, gameMode %d"
\r
5276 ", forwardMost %d\n",
\r
5277 cps->which, gameMode, forwardMostMove);
\r
5282 if (appData.debugMode) { int f = forwardMostMove;
\r
5283 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5284 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5286 AlphaRank(machineMove, 4);
\r
5287 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5288 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5289 /* Machine move could not be parsed; ignore it. */
\r
5290 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
5291 machineMove, cps->which);
\r
5292 DisplayError(buf1, 0);
\r
5293 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5294 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5295 if (gameMode == TwoMachinesPlay) {
\r
5296 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5302 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5303 /* So we have to redo legality test with true e.p. status here, */
\r
5304 /* to make sure an illegal e.p. capture does not slip through, */
\r
5305 /* to cause a forfeit on a justified illegal-move complaint */
\r
5306 /* of the opponent. */
\r
5307 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5308 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5310 ChessMove moveType;
\r
5311 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5312 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5313 fromY, fromX, toY, toX, promoChar);
\r
5314 if (appData.debugMode) {
\r
5316 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5317 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5318 fprintf(debugFP, "castling rights\n");
\r
5320 if(moveType == IllegalMove) {
\r
5321 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5322 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5323 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5325 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5326 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5327 when they shouldn't (like TSCP-Gothic) */
\r
5328 switch(moveType) {
\r
5329 case WhiteASideCastleFR:
\r
5330 case BlackASideCastleFR:
\r
5332 currentMoveString[2]++;
\r
5334 case WhiteHSideCastleFR:
\r
5335 case BlackHSideCastleFR:
\r
5337 currentMoveString[2]--;
\r
5341 hintRequested = FALSE;
\r
5342 lastHint[0] = NULLCHAR;
\r
5343 bookRequested = FALSE;
\r
5344 /* Program may be pondering now */
\r
5345 cps->maybeThinking = TRUE;
\r
5346 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5347 if (cps->offeredDraw) cps->offeredDraw--;
\r
5350 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5352 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5353 ics_user_moved = 1;
\r
5354 if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5355 char buf[3*MSG_SIZ];
\r
5357 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5358 programStats.depth,
\r
5359 programStats.score / 100.,
\r
5360 programStats.time / 100.,
\r
5361 (double) programStats.nodes,
\r
5362 programStats.nodes / (10*abs(programStats.time) + 1.),
\r
5363 programStats.movelist);
\r
5368 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5369 strcpy(machineMove, currentMoveString);
\r
5370 strcat(machineMove, "\n");
\r
5371 strcpy(moveList[forwardMostMove], machineMove);
\r
5373 /* [AS] Save move info and clear stats for next move */
\r
5374 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5375 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5376 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5377 ClearProgramStats();
\r
5378 thinkOutput[0] = NULLCHAR;
\r
5379 hiddenThinkOutputState = 0;
\r
5381 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5383 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5384 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5387 while( count < adjudicateLossPlies ) {
\r
5388 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5391 score = -score; /* Flip score for winning side */
\r
5394 if( score > adjudicateLossThreshold ) {
\r
5401 if( count >= adjudicateLossPlies ) {
\r
5402 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5404 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5405 "Xboard adjudication",
\r
5412 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
5414 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
5415 int count = 0, epFile = epStatus[forwardMostMove];
\r
5417 if(appData.testLegality && appData.checkMates)
\r
5418 // don't wait for engine to announce game end if we can judge ourselves
\r
5419 switch (MateTest(boards[forwardMostMove],
\r
5420 PosFlags(forwardMostMove), epFile,
\r
5421 castlingRights[forwardMostMove]) ) {
\r
5426 case MT_STALEMATE:
\r
5427 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5428 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5431 case MT_CHECKMATE:
\r
5432 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5433 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5434 "Xboard adjudication: Checkmate",
\r
5439 if( appData.testLegality )
\r
5440 { /* [HGM] Some more adjudications for obstinate engines */
\r
5441 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5442 NrWQ=0, NrBQ=0, bishopsColor = 0,
\r
5443 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5444 static int moveCount;
\r
5446 /* First absolutely insufficient mating material. Count what is on board. */
\r
5447 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5448 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5452 { /* count B,N,R and other of each side */
\r
5456 bishopsColor |= 1 << ((i^j)&1);
\r
5461 bishopsColor |= 1 << ((i^j)&1);
\r
5471 case EmptySquare:
\r
5476 PawnAdvance += m; NrPawns++;
\r
5478 NrPieces += (p != EmptySquare);
\r
5481 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2
\r
5482 || NrPieces == 4 && NrBB+NrWB==2 && bishopsColor != 3)
\r
5483 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5485 /* always flag draws, for judging claims */
\r
5486 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5488 if(appData.materialDraws) {
\r
5489 /* but only adjudicate them if adjudication enabled */
\r
5490 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5491 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5496 /* Shatranj baring rule */
\r
5497 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5500 if(--bare < 0 && appData.checkMates) {
\r
5501 /* but only adjudicate them if adjudication enabled */
\r
5502 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5503 GameEnds( NrW > 1 ? WhiteWins : NrPiece - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5504 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5509 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5510 if(NrPieces == 4 &&
\r
5511 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5512 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5513 || NrWN==2 || NrBN==2 /* KNNK */
\r
5514 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5516 if(--moveCount < 0 && appData.trivialDraws)
\r
5517 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5518 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5519 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5522 } else moveCount = 6;
\r
5524 if (appData.debugMode) { int i;
\r
5525 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5526 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5527 appData.drawRepeats);
\r
5528 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5529 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5533 /* Check for rep-draws */
\r
5535 for(k = forwardMostMove-2;
\r
5536 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5537 epStatus[k] < EP_UNKNOWN &&
\r
5538 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5542 if (appData.debugMode) {
\r
5543 fprintf(debugFP, " loop\n");
\r
5546 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5548 if (appData.debugMode) {
\r
5549 fprintf(debugFP, "match\n");
\r
5552 /* compare castling rights */
\r
5553 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5554 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5555 rights++; /* King lost rights, while rook still had them */
\r
5556 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5557 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5558 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5559 rights++; /* but at least one rook lost them */
\r
5561 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5562 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5564 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5565 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5566 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5570 if (appData.debugMode) {
\r
5571 for(i=0; i<nrCastlingRights; i++)
\r
5572 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5575 if (appData.debugMode) {
\r
5576 fprintf(debugFP, " %d %d\n", rights, k);
\r
5579 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5580 && appData.drawRepeats > 1) {
\r
5581 /* adjudicate after user-specified nr of repeats */
\r
5582 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5583 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5586 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5587 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5591 /* Now we test for 50-move draws. Determine ply count */
\r
5592 count = forwardMostMove;
\r
5593 /* look for last irreversble move */
\r
5594 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5596 /* if we hit starting position, add initial plies */
\r
5597 if( count == backwardMostMove )
\r
5598 count -= initialRulePlies;
\r
5599 count = forwardMostMove - count;
\r
5601 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5602 /* this is used to judge if draw claims are legal */
\r
5603 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5604 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5605 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5609 /* if draw offer is pending, treat it as a draw claim
\r
5610 * when draw condition present, to allow engines a way to
\r
5611 * claim draws before making their move to avoid a race
\r
5612 * condition occurring after their move
\r
5614 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
5616 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
5617 p = "Draw claim: 50-move rule";
\r
5618 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
5619 p = "Draw claim: 3-fold repetition";
\r
5620 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
5621 p = "Draw claim: insufficient mating material";
\r
5623 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
5624 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5634 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5635 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5637 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5642 if (gameMode == TwoMachinesPlay) {
\r
5643 /* [HGM] relaying draw offers moved to after reception of move */
\r
5644 /* and interpreting offer as claim if it brings draw condition */
\r
5645 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
5646 SendToProgram("draw\n", cps->other);
\r
5648 if (cps->other->sendTime) {
\r
5649 SendTimeRemaining(cps->other,
\r
5650 cps->other->twoMachinesColor[0] == 'w');
\r
5652 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
5654 firstMove = FALSE;
\r
5655 if (cps->other->useColors) {
\r
5656 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5658 SendToProgram("go\n", cps->other);
\r
5660 cps->other->maybeThinking = TRUE;
\r
5663 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5665 if (!pausing && appData.ringBellAfterMoves) {
\r
5670 * Reenable menu items that were disabled while
\r
5671 * machine was thinking
\r
5673 if (gameMode != TwoMachinesPlay)
\r
5674 SetUserThinkingEnables();
\r
5679 /* Set special modes for chess engines. Later something general
\r
5680 * could be added here; for now there is just one kludge feature,
\r
5681 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5682 * when "xboard" is given as an interactive command.
\r
5684 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5685 cps->useSigint = FALSE;
\r
5686 cps->useSigterm = FALSE;
\r
5689 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5690 * want this, I was asked to put it in, and obliged.
\r
5692 if (!strncmp(message, "setboard ", 9)) {
\r
5693 Board initial_position; int i;
\r
5695 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
5697 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5698 DisplayError("Bad FEN received from engine", 0);
\r
5701 Reset(FALSE, FALSE);
\r
5702 CopyBoard(boards[0], initial_position);
\r
5703 initialRulePlies = FENrulePlies;
\r
5704 epStatus[0] = FENepStatus;
\r
5705 for( i=0; i<nrCastlingRights; i++ )
\r
5706 castlingRights[0][i] = FENcastlingRights[i];
\r
5707 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5708 else gameMode = MachinePlaysBlack;
\r
5709 DrawPosition(FALSE, boards[currentMove]);
\r
5715 * Look for communication commands
\r
5717 if (!strncmp(message, "telluser ", 9)) {
\r
5718 DisplayNote(message + 9);
\r
5721 if (!strncmp(message, "tellusererror ", 14)) {
\r
5722 DisplayError(message + 14, 0);
\r
5725 if (!strncmp(message, "tellopponent ", 13)) {
\r
5726 if (appData.icsActive) {
\r
5728 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5732 DisplayNote(message + 13);
\r
5736 if (!strncmp(message, "tellothers ", 11)) {
\r
5737 if (appData.icsActive) {
\r
5739 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5745 if (!strncmp(message, "tellall ", 8)) {
\r
5746 if (appData.icsActive) {
\r
5748 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5752 DisplayNote(message + 8);
\r
5756 if (strncmp(message, "warning", 7) == 0) {
\r
5757 /* Undocumented feature, use tellusererror in new code */
\r
5758 DisplayError(message, 0);
\r
5761 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5762 strcpy(realname, cps->tidy);
\r
5763 strcat(realname, " query");
\r
5764 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5767 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5768 * sent until we are logged on. Crafty kibitzes have been known to
\r
5769 * interfere with the login process.
\r
5772 if (!strncmp(message, "tellics ", 8)) {
\r
5773 SendToICS(message + 8);
\r
5777 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5778 SendToICS(ics_prefix);
\r
5779 SendToICS(message + 15);
\r
5783 /* The following are for backward compatibility only */
\r
5784 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5785 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5786 SendToICS(ics_prefix);
\r
5787 SendToICS(message);
\r
5792 if (strncmp(message, "feature ", 8) == 0) {
\r
5793 ParseFeatures(message+8, cps);
\r
5795 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5799 * If the move is illegal, cancel it and redraw the board.
\r
5800 * Also deal with other error cases. Matching is rather loose
\r
5801 * here to accommodate engines written before the spec.
\r
5803 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5804 strncmp(message, "Error", 5) == 0) {
\r
5805 if (StrStr(message, "name") ||
\r
5806 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5807 StrStr(message, "result") || StrStr(message, "board") ||
\r
5808 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5809 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5810 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5811 StrStr(message, "accepted")) {
\r
5814 if (StrStr(message, "protover")) {
\r
5815 /* Program is responding to input, so it's apparently done
\r
5816 initializing, and this error message indicates it is
\r
5817 protocol version 1. So we don't need to wait any longer
\r
5818 for it to initialize and send feature commands. */
\r
5819 FeatureDone(cps, 1);
\r
5820 cps->protocolVersion = 1;
\r
5823 cps->maybeThinking = FALSE;
\r
5825 if (StrStr(message, "draw")) {
\r
5826 /* Program doesn't have "draw" command */
\r
5827 cps->sendDrawOffers = 0;
\r
5830 if (cps->sendTime != 1 &&
\r
5831 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5832 /* Program apparently doesn't have "time" or "otim" command */
\r
5833 cps->sendTime = 0;
\r
5836 if (StrStr(message, "analyze")) {
\r
5837 cps->analysisSupport = FALSE;
\r
5838 cps->analyzing = FALSE;
\r
5839 Reset(FALSE, TRUE);
\r
5840 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5841 DisplayError(buf2, 0);
\r
5844 if (StrStr(message, "(no matching move)st")) {
\r
5845 /* Special kludge for GNU Chess 4 only */
\r
5846 cps->stKludge = TRUE;
\r
5847 SendTimeControl(cps, movesPerSession, timeControl,
\r
5848 timeIncrement, appData.searchDepth,
\r
5852 if (StrStr(message, "(no matching move)sd")) {
\r
5853 /* Special kludge for GNU Chess 4 only */
\r
5854 cps->sdKludge = TRUE;
\r
5855 SendTimeControl(cps, movesPerSession, timeControl,
\r
5856 timeIncrement, appData.searchDepth,
\r
5860 if (!StrStr(message, "llegal")) {
\r
5863 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5864 gameMode == IcsIdle) return;
\r
5865 if (forwardMostMove <= backwardMostMove) return;
\r
5867 /* Following removed: it caused a bug where a real illegal move
\r
5868 message in analyze mored would be ignored. */
\r
5869 if (cps == &first && programStats.ok_to_send == 0) {
\r
5870 /* Bogus message from Crafty responding to "." This filtering
\r
5871 can miss some of the bad messages, but fortunately the bug
\r
5872 is fixed in current Crafty versions, so it doesn't matter. */
\r
5876 if (pausing) PauseEvent();
\r
5877 if (gameMode == PlayFromGameFile) {
\r
5878 /* Stop reading this game file */
\r
5879 gameMode = EditGame;
\r
5882 currentMove = --forwardMostMove;
\r
5883 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5885 DisplayBothClocks();
\r
5886 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5887 parseList[currentMove], cps->which);
\r
5888 DisplayMoveError(buf1);
\r
5889 DrawPosition(FALSE, boards[currentMove]);
\r
5891 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5892 /* only passes fully legal moves */
\r
5893 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5894 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5895 "False illegal-move claim", GE_XBOARD );
\r
5899 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5900 /* Program has a broken "time" command that
\r
5901 outputs a string not ending in newline.
\r
5903 cps->sendTime = 0;
\r
5907 * If chess program startup fails, exit with an error message.
\r
5908 * Attempts to recover here are futile.
\r
5910 if ((StrStr(message, "unknown host") != NULL)
\r
5911 || (StrStr(message, "No remote directory") != NULL)
\r
5912 || (StrStr(message, "not found") != NULL)
\r
5913 || (StrStr(message, "No such file") != NULL)
\r
5914 || (StrStr(message, "can't alloc") != NULL)
\r
5915 || (StrStr(message, "Permission denied") != NULL)) {
\r
5917 cps->maybeThinking = FALSE;
\r
5918 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5919 cps->which, cps->program, cps->host, message);
\r
5920 RemoveInputSource(cps->isr);
\r
5921 DisplayFatalError(buf1, 0, 1);
\r
5926 * Look for hint output
\r
5928 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5929 if (cps == &first && hintRequested) {
\r
5930 hintRequested = FALSE;
\r
5931 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5932 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5933 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5934 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5935 fromY, fromX, toY, toX, promoChar, buf1);
\r
5936 sprintf(buf2, "Hint: %s", buf1);
\r
5937 DisplayInformation(buf2);
\r
5939 /* Hint move could not be parsed!? */
\r
5941 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5942 buf1, cps->which);
\r
5943 DisplayError(buf2, 0);
\r
5946 strcpy(lastHint, buf1);
\r
5952 * Ignore other messages if game is not in progress
\r
5954 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5955 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5958 * look for win, lose, draw, or draw offer
\r
5960 if (strncmp(message, "1-0", 3) == 0) {
\r
5961 char *p, *q, *r = "";
\r
5962 p = strchr(message, '{');
\r
5964 q = strchr(p, '}');
\r
5970 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5972 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5973 char *p, *q, *r = "";
\r
5974 p = strchr(message, '{');
\r
5976 q = strchr(p, '}');
\r
5982 /* Kludge for Arasan 4.1 bug */
\r
5983 if (strcmp(r, "Black resigns") == 0) {
\r
5984 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5987 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5989 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5990 char *p, *q, *r = "";
\r
5991 p = strchr(message, '{');
\r
5993 q = strchr(p, '}');
\r
6000 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6003 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6004 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6006 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6007 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6009 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6010 strncmp(message, "Black matches", 13) == 0 ) {
\r
6011 /* [HGM] ignore GNUShogi noises */
\r
6013 } else if (strncmp(message, "White", 5) == 0 &&
\r
6014 message[5] != '(' &&
\r
6015 StrStr(message, "Black") == NULL) {
\r
6016 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6018 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6019 message[5] != '(') {
\r
6020 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6022 } else if (strcmp(message, "resign") == 0 ||
\r
6023 strcmp(message, "computer resigns") == 0) {
\r
6024 switch (gameMode) {
\r
6025 case MachinePlaysBlack:
\r
6026 case IcsPlayingBlack:
\r
6027 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6029 case MachinePlaysWhite:
\r
6030 case IcsPlayingWhite:
\r
6031 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6033 case TwoMachinesPlay:
\r
6034 if (cps->twoMachinesColor[0] == 'w')
\r
6035 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6037 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6040 /* can't happen */
\r
6044 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6045 switch (gameMode) {
\r
6046 case MachinePlaysBlack:
\r
6047 case IcsPlayingBlack:
\r
6048 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6050 case MachinePlaysWhite:
\r
6051 case IcsPlayingWhite:
\r
6052 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6054 case TwoMachinesPlay:
\r
6055 if (cps->twoMachinesColor[0] == 'w')
\r
6056 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6058 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6061 /* can't happen */
\r
6065 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6066 switch (gameMode) {
\r
6067 case MachinePlaysBlack:
\r
6068 case IcsPlayingBlack:
\r
6069 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6071 case MachinePlaysWhite:
\r
6072 case IcsPlayingWhite:
\r
6073 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6075 case TwoMachinesPlay:
\r
6076 if (cps->twoMachinesColor[0] == 'w')
\r
6077 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6079 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6082 /* can't happen */
\r
6086 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6087 if (WhiteOnMove(forwardMostMove)) {
\r
6088 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6090 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6093 } else if (strstr(message, "Draw") != NULL ||
\r
6094 strstr(message, "game is a draw") != NULL) {
\r
6095 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6097 } else if (strstr(message, "offer") != NULL &&
\r
6098 strstr(message, "draw") != NULL) {
\r
6100 if (appData.zippyPlay && first.initDone) {
\r
6101 /* Relay offer to ICS */
\r
6102 SendToICS(ics_prefix);
\r
6103 SendToICS("draw\n");
\r
6106 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6107 if (gameMode == TwoMachinesPlay) {
\r
6108 if (cps->other->offeredDraw) {
\r
6109 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6110 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6111 /* until after we also have move, to see if it is really claim */
\r
6115 if (cps->other->sendDrawOffers) {
\r
6116 SendToProgram("draw\n", cps->other);
\r
6120 } else if (gameMode == MachinePlaysWhite ||
\r
6121 gameMode == MachinePlaysBlack) {
\r
6122 if (userOfferedDraw) {
\r
6123 DisplayInformation("Machine accepts your draw offer");
\r
6124 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6126 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
6133 * Look for thinking output
\r
6135 if ( appData.showThinking) {
\r
6136 int plylev, mvleft, mvtot, curscore, time;
\r
6137 char mvname[MOVE_LEN];
\r
6138 unsigned long nodes;
\r
6140 int ignore = FALSE;
\r
6141 int prefixHint = FALSE;
\r
6142 mvname[0] = NULLCHAR;
\r
6144 switch (gameMode) {
\r
6145 case MachinePlaysBlack:
\r
6146 case IcsPlayingBlack:
\r
6147 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6149 case MachinePlaysWhite:
\r
6150 case IcsPlayingWhite:
\r
6151 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6156 case TwoMachinesPlay:
\r
6157 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6167 buf1[0] = NULLCHAR;
\r
6168 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6169 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6171 if (plyext != ' ' && plyext != '\t') {
\r
6175 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6176 if( cps->scoreIsAbsolute &&
\r
6177 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6179 curscore = -curscore;
\r
6183 programStats.depth = plylev;
\r
6184 programStats.nodes = nodes;
\r
6185 programStats.time = time;
\r
6186 programStats.score = curscore;
\r
6187 programStats.got_only_move = 0;
\r
6189 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6192 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6193 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6194 if(WhiteOnMove(forwardMostMove))
\r
6195 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6196 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6199 /* Buffer overflow protection */
\r
6200 if (buf1[0] != NULLCHAR) {
\r
6201 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6202 && appData.debugMode) {
\r
6204 "PV is too long; using the first %d bytes.\n",
\r
6205 sizeof(programStats.movelist) - 1);
\r
6208 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6210 sprintf(programStats.movelist, " no PV\n");
\r
6213 if (programStats.seen_stat) {
\r
6214 programStats.ok_to_send = 1;
\r
6217 if (strchr(programStats.movelist, '(') != NULL) {
\r
6218 programStats.line_is_book = 1;
\r
6219 programStats.nr_moves = 0;
\r
6220 programStats.moves_left = 0;
\r
6222 programStats.line_is_book = 0;
\r
6225 SendProgramStatsToFrontend( cps, &programStats );
\r
6228 [AS] Protect the thinkOutput buffer from overflow... this
\r
6229 is only useful if buf1 hasn't overflowed first!
\r
6231 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6233 (gameMode == TwoMachinesPlay ?
\r
6234 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6235 ((double) curscore) / 100.0,
\r
6236 prefixHint ? lastHint : "",
\r
6237 prefixHint ? " " : "" );
\r
6239 if( buf1[0] != NULLCHAR ) {
\r
6240 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6242 if( strlen(buf1) > max_len ) {
\r
6243 if( appData.debugMode) {
\r
6244 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6246 buf1[max_len+1] = '\0';
\r
6249 strcat( thinkOutput, buf1 );
\r
6252 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
6253 DisplayMove(currentMove - 1);
\r
6254 DisplayAnalysis();
\r
6258 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6259 /* crafty (9.25+) says "(only move) <move>"
\r
6260 * if there is only 1 legal move
\r
6262 sscanf(p, "(only move) %s", buf1);
\r
6263 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6264 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6265 programStats.depth = 1;
\r
6266 programStats.nr_moves = 1;
\r
6267 programStats.moves_left = 1;
\r
6268 programStats.nodes = 1;
\r
6269 programStats.time = 1;
\r
6270 programStats.got_only_move = 1;
\r
6272 /* Not really, but we also use this member to
\r
6273 mean "line isn't going to change" (Crafty
\r
6274 isn't searching, so stats won't change) */
\r
6275 programStats.line_is_book = 1;
\r
6277 SendProgramStatsToFrontend( cps, &programStats );
\r
6279 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6280 DisplayMove(currentMove - 1);
\r
6281 DisplayAnalysis();
\r
6284 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
6285 &time, &nodes, &plylev, &mvleft,
\r
6286 &mvtot, mvname) >= 5) {
\r
6287 /* The stat01: line is from Crafty (9.29+) in response
\r
6288 to the "." command */
\r
6289 programStats.seen_stat = 1;
\r
6290 cps->maybeThinking = TRUE;
\r
6292 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6295 programStats.depth = plylev;
\r
6296 programStats.time = time;
\r
6297 programStats.nodes = nodes;
\r
6298 programStats.moves_left = mvleft;
\r
6299 programStats.nr_moves = mvtot;
\r
6300 strcpy(programStats.move_name, mvname);
\r
6301 programStats.ok_to_send = 1;
\r
6302 programStats.movelist[0] = '\0';
\r
6304 SendProgramStatsToFrontend( cps, &programStats );
\r
6306 DisplayAnalysis();
\r
6309 } else if (strncmp(message,"++",2) == 0) {
\r
6310 /* Crafty 9.29+ outputs this */
\r
6311 programStats.got_fail = 2;
\r
6314 } else if (strncmp(message,"--",2) == 0) {
\r
6315 /* Crafty 9.29+ outputs this */
\r
6316 programStats.got_fail = 1;
\r
6319 } else if (thinkOutput[0] != NULLCHAR &&
\r
6320 strncmp(message, " ", 4) == 0) {
\r
6321 unsigned message_len;
\r
6324 while (*p && *p == ' ') p++;
\r
6326 message_len = strlen( p );
\r
6328 /* [AS] Avoid buffer overflow */
\r
6329 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6330 strcat(thinkOutput, " ");
\r
6331 strcat(thinkOutput, p);
\r
6334 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6335 strcat(programStats.movelist, " ");
\r
6336 strcat(programStats.movelist, p);
\r
6339 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6340 DisplayMove(currentMove - 1);
\r
6341 DisplayAnalysis();
\r
6347 buf1[0] = NULLCHAR;
\r
6349 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6350 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6352 ChessProgramStats cpstats;
\r
6354 if (plyext != ' ' && plyext != '\t') {
\r
6358 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6359 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6360 curscore = -curscore;
\r
6363 cpstats.depth = plylev;
\r
6364 cpstats.nodes = nodes;
\r
6365 cpstats.time = time;
\r
6366 cpstats.score = curscore;
\r
6367 cpstats.got_only_move = 0;
\r
6368 cpstats.movelist[0] = '\0';
\r
6370 if (buf1[0] != NULLCHAR) {
\r
6371 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6374 cpstats.ok_to_send = 0;
\r
6375 cpstats.line_is_book = 0;
\r
6376 cpstats.nr_moves = 0;
\r
6377 cpstats.moves_left = 0;
\r
6379 SendProgramStatsToFrontend( cps, &cpstats );
\r
6386 /* Parse a game score from the character string "game", and
\r
6387 record it as the history of the current game. The game
\r
6388 score is NOT assumed to start from the standard position.
\r
6389 The display is not updated in any way.
\r
6392 ParseGameHistory(game)
\r
6395 ChessMove moveType;
\r
6396 int fromX, fromY, toX, toY, boardIndex;
\r
6399 char buf[MSG_SIZ];
\r
6401 if (appData.debugMode)
\r
6402 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6404 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6405 gameInfo.site = StrSave(appData.icsHost);
\r
6406 gameInfo.date = PGNDate();
\r
6407 gameInfo.round = StrSave("-");
\r
6409 /* Parse out names of players */
\r
6410 while (*game == ' ') game++;
\r
6412 while (*game != ' ') *p++ = *game++;
\r
6414 gameInfo.white = StrSave(buf);
\r
6415 while (*game == ' ') game++;
\r
6417 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6419 gameInfo.black = StrSave(buf);
\r
6422 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6425 yyboardindex = boardIndex;
\r
6426 moveType = (ChessMove) yylex();
\r
6427 switch (moveType) {
\r
6428 case IllegalMove: /* maybe suicide chess, etc. */
\r
6429 if (appData.debugMode) {
\r
6430 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6431 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6432 setbuf(debugFP, NULL);
\r
6434 case WhitePromotionChancellor:
\r
6435 case BlackPromotionChancellor:
\r
6436 case WhitePromotionArchbishop:
\r
6437 case BlackPromotionArchbishop:
\r
6438 case WhitePromotionQueen:
\r
6439 case BlackPromotionQueen:
\r
6440 case WhitePromotionRook:
\r
6441 case BlackPromotionRook:
\r
6442 case WhitePromotionBishop:
\r
6443 case BlackPromotionBishop:
\r
6444 case WhitePromotionKnight:
\r
6445 case BlackPromotionKnight:
\r
6446 case WhitePromotionKing:
\r
6447 case BlackPromotionKing:
\r
6449 case WhiteCapturesEnPassant:
\r
6450 case BlackCapturesEnPassant:
\r
6451 case WhiteKingSideCastle:
\r
6452 case WhiteQueenSideCastle:
\r
6453 case BlackKingSideCastle:
\r
6454 case BlackQueenSideCastle:
\r
6455 case WhiteKingSideCastleWild:
\r
6456 case WhiteQueenSideCastleWild:
\r
6457 case BlackKingSideCastleWild:
\r
6458 case BlackQueenSideCastleWild:
\r
6460 case WhiteHSideCastleFR:
\r
6461 case WhiteASideCastleFR:
\r
6462 case BlackHSideCastleFR:
\r
6463 case BlackASideCastleFR:
\r
6465 fromX = currentMoveString[0] - AAA;
\r
6466 fromY = currentMoveString[1] - ONE;
\r
6467 toX = currentMoveString[2] - AAA;
\r
6468 toY = currentMoveString[3] - ONE;
\r
6469 promoChar = currentMoveString[4];
\r
6473 fromX = moveType == WhiteDrop ?
\r
6474 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6475 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6476 fromY = DROP_RANK;
\r
6477 toX = currentMoveString[2] - AAA;
\r
6478 toY = currentMoveString[3] - ONE;
\r
6479 promoChar = NULLCHAR;
\r
6481 case AmbiguousMove:
\r
6483 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
6484 if (appData.debugMode) {
\r
6485 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6486 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6487 setbuf(debugFP, NULL);
\r
6489 DisplayError(buf, 0);
\r
6491 case ImpossibleMove:
\r
6493 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
6494 if (appData.debugMode) {
\r
6495 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6496 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6497 setbuf(debugFP, NULL);
\r
6499 DisplayError(buf, 0);
\r
6501 case (ChessMove) 0: /* end of file */
\r
6502 if (boardIndex < backwardMostMove) {
\r
6503 /* Oops, gap. How did that happen? */
\r
6504 DisplayError("Gap in move list", 0);
\r
6507 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6508 if (boardIndex > forwardMostMove) {
\r
6509 forwardMostMove = boardIndex;
\r
6513 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6514 strcat(parseList[boardIndex-1], " ");
\r
6515 strcat(parseList[boardIndex-1], yy_text);
\r
6527 case GameUnfinished:
\r
6528 if (gameMode == IcsExamining) {
\r
6529 if (boardIndex < backwardMostMove) {
\r
6530 /* Oops, gap. How did that happen? */
\r
6533 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6536 gameInfo.result = moveType;
\r
6537 p = strchr(yy_text, '{');
\r
6538 if (p == NULL) p = strchr(yy_text, '(');
\r
6541 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6543 q = strchr(p, *p == '{' ? '}' : ')');
\r
6544 if (q != NULL) *q = NULLCHAR;
\r
6547 gameInfo.resultDetails = StrSave(p);
\r
6550 if (boardIndex >= forwardMostMove &&
\r
6551 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6552 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6555 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6556 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6557 parseList[boardIndex]);
\r
6558 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6559 /* currentMoveString is set as a side-effect of yylex */
\r
6560 strcpy(moveList[boardIndex], currentMoveString);
\r
6561 strcat(moveList[boardIndex], "\n");
\r
6563 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6564 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6565 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6567 case MT_STALEMATE:
\r
6571 if(gameInfo.variant != VariantShogi)
\r
6572 strcat(parseList[boardIndex - 1], "+");
\r
6574 case MT_CHECKMATE:
\r
6575 strcat(parseList[boardIndex - 1], "#");
\r
6582 /* Apply a move to the given board */
\r
6584 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
6585 int fromX, fromY, toX, toY;
\r
6589 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
6591 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6592 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
6593 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
6596 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
6597 oldEP = epStatus[p-1];
\r
6598 epStatus[p] = EP_NONE;
\r
6600 if( board[toY][toX] != EmptySquare )
\r
6601 epStatus[p] = EP_CAPTURE;
\r
6603 if( board[fromY][fromX] == WhitePawn ) {
\r
6604 epStatus[p] = EP_PAWN_MOVE;
\r
6606 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
6607 gameInfo.variant != VariantBerolina || toX < fromX)
\r
6608 epStatus[p] = toX | berolina;
\r
6609 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
6610 gameInfo.variant != VariantBerolina || toX > fromX)
\r
6611 epStatus[p] = toX;
\r
6613 if( board[fromY][fromX] == BlackPawn ) {
\r
6614 epStatus[p] = EP_PAWN_MOVE;
\r
6615 if( toY-fromY== -2)
\r
6616 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
6617 gameInfo.variant != VariantBerolina || toX < fromX)
\r
6618 epStatus[p] = toX | berolina;
\r
6619 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
6620 gameInfo.variant != VariantBerolina || toX > fromX)
\r
6621 epStatus[p] = toX;
\r
6624 for(i=0; i<nrCastlingRights; i++) {
\r
6625 castlingRights[p][i] = castlingRights[p-1][i];
\r
6626 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
6627 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
6628 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
6633 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
6634 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
6635 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
6637 if (fromX == toX && fromY == toY) return;
\r
6639 if (fromY == DROP_RANK) {
\r
6640 /* must be first */
\r
6641 piece = board[toY][toX] = (ChessSquare) fromX;
\r
6643 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
6644 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
6645 if(gameInfo.variant == VariantKnightmate)
\r
6646 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
6648 /* Code added by Tord: */
\r
6649 /* FRC castling assumed when king captures friendly rook. */
\r
6650 if (board[fromY][fromX] == WhiteKing &&
\r
6651 board[toY][toX] == WhiteRook) {
\r
6652 board[fromY][fromX] = EmptySquare;
\r
6653 board[toY][toX] = EmptySquare;
\r
6655 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
6657 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
6659 } else if (board[fromY][fromX] == BlackKing &&
\r
6660 board[toY][toX] == BlackRook) {
\r
6661 board[fromY][fromX] = EmptySquare;
\r
6662 board[toY][toX] = EmptySquare;
\r
6664 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
6666 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
6668 /* End of code added by Tord */
\r
6670 } else if (board[fromY][fromX] == king
\r
6671 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6672 && toY == fromY && toX > fromX+1) {
\r
6673 board[fromY][fromX] = EmptySquare;
\r
6674 board[toY][toX] = king;
\r
6675 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6676 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6677 } else if (board[fromY][fromX] == king
\r
6678 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6679 && toY == fromY && toX < fromX-1) {
\r
6680 board[fromY][fromX] = EmptySquare;
\r
6681 board[toY][toX] = king;
\r
6682 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6683 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6684 } else if (board[fromY][fromX] == WhitePawn
\r
6685 && toY == BOARD_HEIGHT-1
\r
6686 && gameInfo.variant != VariantXiangqi
\r
6688 /* white pawn promotion */
\r
6689 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6690 if (board[toY][toX] == EmptySquare) {
\r
6691 board[toY][toX] = WhiteQueen;
\r
6693 if(gameInfo.variant==VariantBughouse ||
\r
6694 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6695 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6696 board[fromY][fromX] = EmptySquare;
\r
6697 } else if ((fromY == BOARD_HEIGHT-4)
\r
6699 && gameInfo.variant != VariantXiangqi
\r
6700 && gameInfo.variant != VariantBerolina
\r
6701 && (board[fromY][fromX] == WhitePawn)
\r
6702 && (board[toY][toX] == EmptySquare)) {
\r
6703 board[fromY][fromX] = EmptySquare;
\r
6704 board[toY][toX] = WhitePawn;
\r
6705 captured = board[toY - 1][toX];
\r
6706 board[toY - 1][toX] = EmptySquare;
\r
6707 } else if ((fromY == BOARD_HEIGHT-4)
\r
6709 && gameInfo.variant == VariantBerolina
\r
6710 && (board[fromY][fromX] == WhitePawn)
\r
6711 && (board[toY][toX] == EmptySquare)) {
\r
6712 board[fromY][fromX] = EmptySquare;
\r
6713 board[toY][toX] = WhitePawn;
\r
6714 if(oldEP & EP_BEROLIN_A) {
\r
6715 captured = board[fromY][fromX-1];
\r
6716 board[fromY][fromX-1] = EmptySquare;
\r
6717 }else{ captured = board[fromY][fromX+1];
\r
6718 board[fromY][fromX+1] = EmptySquare;
\r
6720 } else if (board[fromY][fromX] == king
\r
6721 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6722 && toY == fromY && toX > fromX+1) {
\r
6723 board[fromY][fromX] = EmptySquare;
\r
6724 board[toY][toX] = king;
\r
6725 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6726 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6727 } else if (board[fromY][fromX] == king
\r
6728 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6729 && toY == fromY && toX < fromX-1) {
\r
6730 board[fromY][fromX] = EmptySquare;
\r
6731 board[toY][toX] = king;
\r
6732 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6733 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6734 } else if (fromY == 7 && fromX == 3
\r
6735 && board[fromY][fromX] == BlackKing
\r
6736 && toY == 7 && toX == 5) {
\r
6737 board[fromY][fromX] = EmptySquare;
\r
6738 board[toY][toX] = BlackKing;
\r
6739 board[fromY][7] = EmptySquare;
\r
6740 board[toY][4] = BlackRook;
\r
6741 } else if (fromY == 7 && fromX == 3
\r
6742 && board[fromY][fromX] == BlackKing
\r
6743 && toY == 7 && toX == 1) {
\r
6744 board[fromY][fromX] = EmptySquare;
\r
6745 board[toY][toX] = BlackKing;
\r
6746 board[fromY][0] = EmptySquare;
\r
6747 board[toY][2] = BlackRook;
\r
6748 } else if (board[fromY][fromX] == BlackPawn
\r
6750 && gameInfo.variant != VariantXiangqi
\r
6752 /* black pawn promotion */
\r
6753 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6754 if (board[0][toX] == EmptySquare) {
\r
6755 board[0][toX] = BlackQueen;
\r
6757 if(gameInfo.variant==VariantBughouse ||
\r
6758 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6759 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6760 board[fromY][fromX] = EmptySquare;
\r
6761 } else if ((fromY == 3)
\r
6763 && gameInfo.variant != VariantXiangqi
\r
6764 && gameInfo.variant != VariantBerolina
\r
6765 && (board[fromY][fromX] == BlackPawn)
\r
6766 && (board[toY][toX] == EmptySquare)) {
\r
6767 board[fromY][fromX] = EmptySquare;
\r
6768 board[toY][toX] = BlackPawn;
\r
6769 captured = board[toY + 1][toX];
\r
6770 board[toY + 1][toX] = EmptySquare;
\r
6771 } else if ((fromY == 3)
\r
6773 && gameInfo.variant == VariantBerolina
\r
6774 && (board[fromY][fromX] == BlackPawn)
\r
6775 && (board[toY][toX] == EmptySquare)) {
\r
6776 board[fromY][fromX] = EmptySquare;
\r
6777 board[toY][toX] = BlackPawn;
\r
6778 if(oldEP & EP_BEROLIN_A) {
\r
6779 captured = board[fromY][fromX-1];
\r
6780 board[fromY][fromX-1] = EmptySquare;
\r
6781 }else{ captured = board[fromY][fromX+1];
\r
6782 board[fromY][fromX+1] = EmptySquare;
\r
6785 board[toY][toX] = board[fromY][fromX];
\r
6786 board[fromY][fromX] = EmptySquare;
\r
6789 /* [HGM] now we promote for Shogi, if needed */
\r
6790 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
6791 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6794 if (gameInfo.holdingsWidth != 0) {
\r
6796 /* !!A lot more code needs to be written to support holdings */
\r
6797 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
6798 /* penultimate board files, so they are automaticlly stored */
\r
6799 /* in the game history. */
\r
6800 if (fromY == DROP_RANK) {
\r
6801 /* Delete from holdings, by decreasing count */
\r
6802 /* and erasing image if necessary */
\r
6804 if(p < (int) BlackPawn) { /* white drop */
\r
6805 p -= (int)WhitePawn;
\r
6806 if(p >= gameInfo.holdingsSize) p = 0;
\r
6807 if(--board[p][BOARD_WIDTH-2] == 0)
\r
6808 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
6809 } else { /* black drop */
\r
6810 p -= (int)BlackPawn;
\r
6811 if(p >= gameInfo.holdingsSize) p = 0;
\r
6812 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
6813 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
6816 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
6817 && gameInfo.variant != VariantBughouse ) {
\r
6818 /* Add to holdings, if holdings exist */
\r
6819 p = (int) captured;
\r
6820 if (p >= (int) BlackPawn) {
\r
6821 p -= (int)BlackPawn;
\r
6822 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6823 /* in Shogi restore piece to its original first */
\r
6824 captured = (ChessSquare) (DEMOTED captured);
\r
6827 p = PieceToNumber((ChessSquare)p);
\r
6828 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
6829 board[p][BOARD_WIDTH-2]++;
\r
6830 board[p][BOARD_WIDTH-1] =
\r
6831 BLACK_TO_WHITE captured;
\r
6833 p -= (int)WhitePawn;
\r
6834 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6835 captured = (ChessSquare) (DEMOTED captured);
\r
6838 p = PieceToNumber((ChessSquare)p);
\r
6839 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
6840 board[BOARD_HEIGHT-1-p][1]++;
\r
6841 board[BOARD_HEIGHT-1-p][0] =
\r
6842 WHITE_TO_BLACK captured;
\r
6846 } else if (gameInfo.variant == VariantAtomic) {
\r
6847 if (captured != EmptySquare) {
\r
6849 for (y = toY-1; y <= toY+1; y++) {
\r
6850 for (x = toX-1; x <= toX+1; x++) {
\r
6851 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
6852 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
6853 board[y][x] = EmptySquare;
\r
6857 board[toY][toX] = EmptySquare;
\r
6860 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
6861 /* [HGM] Shogi promotions */
\r
6862 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6867 /* Updates forwardMostMove */
\r
6869 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6870 int fromX, fromY, toX, toY;
\r
6873 forwardMostMove++;
\r
6875 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */
\r
6876 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
6877 piece = boards[forwardMostMove-1][fromY][fromX];
\r
6878 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
6879 if(gameInfo.variant == VariantKnightmate)
\r
6880 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
6881 if(forwardMostMove == 1) {
\r
6882 if(blackPlaysFirst)
\r
6883 fprintf(serverMoves, "%s;", second.tidy);
\r
6884 fprintf(serverMoves, "%s;", first.tidy);
\r
6885 if(!blackPlaysFirst)
\r
6886 fprintf(serverMoves, "%s;", second.tidy);
\r
6887 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
6888 lastLoadFlag = loadFlag;
\r
6889 // print base move
\r
6890 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
6891 // print castling suffix
\r
6892 if( toY == fromY && piece == king ) {
\r
6894 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
6896 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
6899 if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||
\r
6900 boards[forwardMostMove-1][fromY][fromX] == BlackPawn ) &&
\r
6901 boards[forwardMostMove-1][toY][toX] == EmptySquare
\r
6903 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
6904 // promotion suffix
\r
6905 if(promoChar != NULLCHAR)
\r
6906 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
6908 fprintf(serverMoves, "/%d/%d",
\r
6909 pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);
\r
6910 if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;
\r
6911 else timeLeft = blackTimeRemaining/1000;
\r
6912 fprintf(serverMoves, "/%d", timeLeft);
\r
6914 fflush(serverMoves);
\r
6917 if (forwardMostMove >= MAX_MOVES) {
\r
6918 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6923 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6924 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6925 if (commentList[forwardMostMove] != NULL) {
\r
6926 free(commentList[forwardMostMove]);
\r
6927 commentList[forwardMostMove] = NULL;
\r
6929 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6930 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6931 gameInfo.result = GameUnfinished;
\r
6932 if (gameInfo.resultDetails != NULL) {
\r
6933 free(gameInfo.resultDetails);
\r
6934 gameInfo.resultDetails = NULL;
\r
6936 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6937 moveList[forwardMostMove - 1]);
\r
6938 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6939 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6940 fromY, fromX, toY, toX, promoChar,
\r
6941 parseList[forwardMostMove - 1]);
\r
6942 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6943 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6944 castlingRights[forwardMostMove]) ) {
\r
6946 case MT_STALEMATE:
\r
6950 if(gameInfo.variant != VariantShogi)
\r
6951 strcat(parseList[forwardMostMove - 1], "+");
\r
6953 case MT_CHECKMATE:
\r
6954 strcat(parseList[forwardMostMove - 1], "#");
\r
6957 if (appData.debugMode) {
\r
6958 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
6963 /* Updates currentMove if not pausing */
\r
6965 ShowMove(fromX, fromY, toX, toY)
\r
6967 int instant = (gameMode == PlayFromGameFile) ?
\r
6968 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6969 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6971 if (forwardMostMove == currentMove + 1) {
\r
6972 AnimateMove(boards[forwardMostMove - 1],
\r
6973 fromX, fromY, toX, toY);
\r
6975 if (appData.highlightLastMove) {
\r
6976 SetHighlights(fromX, fromY, toX, toY);
\r
6979 currentMove = forwardMostMove;
\r
6982 if (instant) return;
\r
6984 DisplayMove(currentMove - 1);
\r
6985 DrawPosition(FALSE, boards[currentMove]);
\r
6986 DisplayBothClocks();
\r
6987 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6992 InitChessProgram(cps, setup)
\r
6993 ChessProgramState *cps;
\r
6994 int setup; /* [HGM] needed to setup FRC opening position */
\r
6996 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
6997 if (appData.noChessProgram) return;
\r
6998 hintRequested = FALSE;
\r
6999 bookRequested = FALSE;
\r
7000 SendToProgram(cps->initString, cps);
\r
7001 if (gameInfo.variant != VariantNormal &&
\r
7002 gameInfo.variant != VariantLoadable
\r
7003 /* [HGM] also send variant if board size non-standard */
\r
7004 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7006 char *v = VariantName(gameInfo.variant);
\r
7007 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7008 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7009 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
7010 DisplayFatalError(buf, 0, 1);
\r
7014 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7015 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7016 if( gameInfo.variant == VariantXiangqi )
\r
7017 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7018 if( gameInfo.variant == VariantShogi )
\r
7019 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7020 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7021 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7022 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7023 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7024 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7025 if( gameInfo.variant == VariantCourier )
\r
7026 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7029 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7030 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7031 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7032 if(StrStr(cps->variants, b) == NULL) {
\r
7033 // specific sized variant not known, check if general sizing allowed
\r
7034 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7035 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7036 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7037 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7038 DisplayFatalError(buf, 0, 1);
\r
7041 /* [HGM] here we really should compare with the maximum supported board size */
\r
7044 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7045 sprintf(buf, "variant %s\n", b);
\r
7046 SendToProgram(buf, cps);
\r
7048 currentlyInitializedVariant = gameInfo.variant;
\r
7050 /* [HGM] send opening position in FRC to first engine */
\r
7052 SendToProgram("force\n", cps);
\r
7053 SendBoard(cps, 0);
\r
7054 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7055 setboardSpoiledMachineBlack = 1;
\r
7058 if (cps->sendICS) {
\r
7059 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7060 SendToProgram(buf, cps);
\r
7062 cps->maybeThinking = FALSE;
\r
7063 cps->offeredDraw = 0;
\r
7064 if (!appData.icsActive) {
\r
7065 SendTimeControl(cps, movesPerSession, timeControl,
\r
7066 timeIncrement, appData.searchDepth,
\r
7069 if (appData.showThinking) {
\r
7070 SendToProgram("post\n", cps);
\r
7072 SendToProgram("hard\n", cps);
\r
7073 if (!appData.ponderNextMove) {
\r
7074 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7075 it without being sure what state we are in first. "hard"
\r
7076 is not a toggle, so that one is OK.
\r
7078 SendToProgram("easy\n", cps);
\r
7080 if (cps->usePing) {
\r
7081 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7082 SendToProgram(buf, cps);
\r
7084 cps->initDone = TRUE;
\r
7089 StartChessProgram(cps)
\r
7090 ChessProgramState *cps;
\r
7092 char buf[MSG_SIZ];
\r
7095 if (appData.noChessProgram) return;
\r
7096 cps->initDone = FALSE;
\r
7098 if (strcmp(cps->host, "localhost") == 0) {
\r
7099 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7100 } else if (*appData.remoteShell == NULLCHAR) {
\r
7101 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7103 if (*appData.remoteUser == NULLCHAR) {
\r
7104 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7107 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7108 cps->host, appData.remoteUser, cps->program);
\r
7110 err = StartChildProcess(buf, "", &cps->pr);
\r
7114 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
7115 DisplayFatalError(buf, err, 1);
\r
7121 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7122 if (cps->protocolVersion > 1) {
\r
7123 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7124 SendToProgram(buf, cps);
\r
7126 SendToProgram("xboard\n", cps);
\r
7132 TwoMachinesEventIfReady P((void))
\r
7134 if (first.lastPing != first.lastPong) {
\r
7135 DisplayMessage("", "Waiting for first chess program");
\r
7136 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
7139 if (second.lastPing != second.lastPong) {
\r
7140 DisplayMessage("", "Waiting for second chess program");
\r
7141 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
7145 TwoMachinesEvent();
\r
7149 NextMatchGame P((void))
\r
7151 Reset(FALSE, TRUE);
\r
7152 if (*appData.loadGameFile != NULLCHAR) {
\r
7153 LoadGameFromFile(appData.loadGameFile,
\r
7154 appData.loadGameIndex,
\r
7155 appData.loadGameFile, FALSE);
\r
7156 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7157 LoadPositionFromFile(appData.loadPositionFile,
\r
7158 appData.loadPositionIndex,
\r
7159 appData.loadPositionFile);
\r
7161 TwoMachinesEventIfReady();
\r
7164 void UserAdjudicationEvent( int result )
\r
7166 ChessMove gameResult = GameIsDrawn;
\r
7168 if( result > 0 ) {
\r
7169 gameResult = WhiteWins;
\r
7171 else if( result < 0 ) {
\r
7172 gameResult = BlackWins;
\r
7175 if( gameMode == TwoMachinesPlay ) {
\r
7176 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7182 GameEnds(result, resultDetails, whosays)
\r
7184 char *resultDetails;
\r
7187 GameMode nextGameMode;
\r
7189 char buf[MSG_SIZ];
\r
7191 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7194 if (appData.debugMode) {
\r
7195 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7196 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7199 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7200 /* If we are playing on ICS, the server decides when the
\r
7201 game is over, but the engine can offer to draw, claim
\r
7202 a draw, or resign.
\r
7205 if (appData.zippyPlay && first.initDone) {
\r
7206 if (result == GameIsDrawn) {
\r
7207 /* In case draw still needs to be claimed */
\r
7208 SendToICS(ics_prefix);
\r
7209 SendToICS("draw\n");
\r
7210 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7211 SendToICS(ics_prefix);
\r
7212 SendToICS("resign\n");
\r
7216 endingGame = 0; /* [HGM] crash */
\r
7220 /* If we're loading the game from a file, stop */
\r
7221 if (whosays == GE_FILE) {
\r
7222 (void) StopLoadGameTimer();
\r
7223 gameFileFP = NULL;
\r
7226 /* Cancel draw offers */
\r
7227 first.offeredDraw = second.offeredDraw = 0;
\r
7229 /* If this is an ICS game, only ICS can really say it's done;
\r
7230 if not, anyone can. */
\r
7231 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7232 gameMode == IcsPlayingBlack ||
\r
7233 gameMode == IcsObserving ||
\r
7234 gameMode == IcsExamining);
\r
7236 if (!isIcsGame || whosays == GE_ICS) {
\r
7237 /* OK -- not an ICS game, or ICS said it was done */
\r
7239 if (appData.debugMode) {
\r
7240 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
7241 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7243 if (!isIcsGame && !appData.noChessProgram)
\r
7244 SetUserThinkingEnables();
\r
7246 /* [HGM] if a machine claims the game end we verify this claim */
\r
7247 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
7248 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
7251 if (appData.debugMode) {
\r
7252 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
7253 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7255 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7256 first.twoMachinesColor[0] :
\r
7257 second.twoMachinesColor[0] ;
\r
7258 if( gameInfo.holdingsWidth == 0 &&
\r
7259 (result == WhiteWins && claimer == 'w' ||
\r
7260 result == BlackWins && claimer == 'b' ) ) {
\r
7261 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
7262 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7263 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7264 resultDetails = buf;
\r
7266 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7267 && (forwardMostMove <= backwardMostMove ||
\r
7268 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7269 (claimer=='b')==(forwardMostMove&1))
\r
7271 /* Draw that was not flagged by Xboard is false */
\r
7272 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7273 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7274 resultDetails = buf;
\r
7276 /* (Claiming a loss is accepted no questions asked!) */
\r
7279 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7280 if(result==WhiteWins) c = '+';
\r
7281 if(result==BlackWins) c = '-';
\r
7282 if(resultDetails != NULL)
\r
7283 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7285 if (appData.debugMode) {
\r
7286 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
7287 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7289 if (resultDetails != NULL) {
\r
7290 gameInfo.result = result;
\r
7291 gameInfo.resultDetails = StrSave(resultDetails);
\r
7293 /* display last move only if game was not loaded from file */
\r
7294 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7295 DisplayMove(currentMove - 1);
\r
7297 if (forwardMostMove != 0) {
\r
7298 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7299 if (*appData.saveGameFile != NULLCHAR) {
\r
7300 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7301 } else if (appData.autoSaveGames) {
\r
7304 if (*appData.savePositionFile != NULLCHAR) {
\r
7305 SavePositionToFile(appData.savePositionFile);
\r
7310 /* Tell program how game ended in case it is learning */
\r
7311 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7312 /* engine died and we got here through time loss. In that */
\r
7313 /* case we will get a fatal error writing the pipe, which */
\r
7314 /* would otherwise lose us the PGN. */
\r
7315 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7316 /* output during GameEnds should never be fatal anymore */
\r
7317 if (gameMode == MachinePlaysWhite ||
\r
7318 gameMode == MachinePlaysBlack ||
\r
7319 gameMode == TwoMachinesPlay ||
\r
7320 gameMode == IcsPlayingWhite ||
\r
7321 gameMode == IcsPlayingBlack ||
\r
7322 gameMode == BeginningOfGame) {
\r
7323 char buf[MSG_SIZ];
\r
7324 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7326 if (first.pr != NoProc) {
\r
7327 SendToProgram(buf, &first);
\r
7329 if (second.pr != NoProc &&
\r
7330 gameMode == TwoMachinesPlay) {
\r
7331 SendToProgram(buf, &second);
\r
7336 if (appData.icsActive) {
\r
7337 if (appData.quietPlay &&
\r
7338 (gameMode == IcsPlayingWhite ||
\r
7339 gameMode == IcsPlayingBlack)) {
\r
7340 SendToICS(ics_prefix);
\r
7341 SendToICS("set shout 1\n");
\r
7343 nextGameMode = IcsIdle;
\r
7344 ics_user_moved = FALSE;
\r
7345 /* clean up premove. It's ugly when the game has ended and the
\r
7346 * premove highlights are still on the board.
\r
7349 gotPremove = FALSE;
\r
7350 ClearPremoveHighlights();
\r
7351 DrawPosition(FALSE, boards[currentMove]);
\r
7353 if (whosays == GE_ICS) {
\r
7356 if (gameMode == IcsPlayingWhite)
\r
7357 PlayIcsWinSound();
\r
7358 else if(gameMode == IcsPlayingBlack)
\r
7359 PlayIcsLossSound();
\r
7362 if (gameMode == IcsPlayingBlack)
\r
7363 PlayIcsWinSound();
\r
7364 else if(gameMode == IcsPlayingWhite)
\r
7365 PlayIcsLossSound();
\r
7368 PlayIcsDrawSound();
\r
7371 PlayIcsUnfinishedSound();
\r
7374 } else if (gameMode == EditGame ||
\r
7375 gameMode == PlayFromGameFile ||
\r
7376 gameMode == AnalyzeMode ||
\r
7377 gameMode == AnalyzeFile) {
\r
7378 nextGameMode = gameMode;
\r
7380 nextGameMode = EndOfGame;
\r
7385 nextGameMode = gameMode;
\r
7388 if (appData.noChessProgram) {
\r
7389 gameMode = nextGameMode;
\r
7391 endingGame = 0; /* [HGM] crash */
\r
7395 if (first.reuse) {
\r
7396 /* Put first chess program into idle state */
\r
7397 if (first.pr != NoProc &&
\r
7398 (gameMode == MachinePlaysWhite ||
\r
7399 gameMode == MachinePlaysBlack ||
\r
7400 gameMode == TwoMachinesPlay ||
\r
7401 gameMode == IcsPlayingWhite ||
\r
7402 gameMode == IcsPlayingBlack ||
\r
7403 gameMode == BeginningOfGame)) {
\r
7404 SendToProgram("force\n", &first);
\r
7405 if (first.usePing) {
\r
7406 char buf[MSG_SIZ];
\r
7407 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7408 SendToProgram(buf, &first);
\r
7411 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7412 /* Kill off first chess program */
\r
7413 if (first.isr != NULL)
\r
7414 RemoveInputSource(first.isr);
\r
7417 if (first.pr != NoProc) {
\r
7418 ExitAnalyzeMode();
\r
7419 DoSleep( appData.delayBeforeQuit );
\r
7420 SendToProgram("quit\n", &first);
\r
7421 DoSleep( appData.delayAfterQuit );
\r
7422 DestroyChildProcess(first.pr, first.useSigterm);
\r
7424 first.pr = NoProc;
\r
7426 if (second.reuse) {
\r
7427 /* Put second chess program into idle state */
\r
7428 if (second.pr != NoProc &&
\r
7429 gameMode == TwoMachinesPlay) {
\r
7430 SendToProgram("force\n", &second);
\r
7431 if (second.usePing) {
\r
7432 char buf[MSG_SIZ];
\r
7433 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7434 SendToProgram(buf, &second);
\r
7437 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7438 /* Kill off second chess program */
\r
7439 if (second.isr != NULL)
\r
7440 RemoveInputSource(second.isr);
\r
7441 second.isr = NULL;
\r
7443 if (second.pr != NoProc) {
\r
7444 DoSleep( appData.delayBeforeQuit );
\r
7445 SendToProgram("quit\n", &second);
\r
7446 DoSleep( appData.delayAfterQuit );
\r
7447 DestroyChildProcess(second.pr, second.useSigterm);
\r
7449 second.pr = NoProc;
\r
7452 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7455 if (first.twoMachinesColor[0] == 'w') {
\r
7456 first.matchWins++;
\r
7458 second.matchWins++;
\r
7462 if (first.twoMachinesColor[0] == 'b') {
\r
7463 first.matchWins++;
\r
7465 second.matchWins++;
\r
7471 if (matchGame < appData.matchGames) {
\r
7473 tmp = first.twoMachinesColor;
\r
7474 first.twoMachinesColor = second.twoMachinesColor;
\r
7475 second.twoMachinesColor = tmp;
\r
7476 gameMode = nextGameMode;
\r
7478 if(appData.matchPause>10000 || appData.matchPause<10)
\r
7479 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
7480 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
7481 endingGame = 0; /* [HGM] crash */
\r
7484 char buf[MSG_SIZ];
\r
7485 gameMode = nextGameMode;
\r
7486 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
7487 first.tidy, second.tidy,
\r
7488 first.matchWins, second.matchWins,
\r
7489 appData.matchGames - (first.matchWins + second.matchWins));
\r
7490 DisplayFatalError(buf, 0, 0);
\r
7493 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7494 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
7495 ExitAnalyzeMode();
\r
7496 gameMode = nextGameMode;
\r
7498 endingGame = 0; /* [HGM] crash */
\r
7501 /* Assumes program was just initialized (initString sent).
\r
7502 Leaves program in force mode. */
\r
7504 FeedMovesToProgram(cps, upto)
\r
7505 ChessProgramState *cps;
\r
7510 if (appData.debugMode)
\r
7511 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
7512 startedFromSetupPosition ? "position and " : "",
\r
7513 backwardMostMove, upto, cps->which);
\r
7514 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
7515 // [HGM] variantswitch: make engine aware of new variant
\r
7516 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
7517 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
7518 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
7519 SendToProgram(buf, cps);
\r
7520 currentlyInitializedVariant = gameInfo.variant;
\r
7522 SendToProgram("force\n", cps);
\r
7523 if (startedFromSetupPosition) {
\r
7524 SendBoard(cps, backwardMostMove);
\r
7525 if (appData.debugMode) {
\r
7526 fprintf(debugFP, "feedMoves\n");
\r
7529 for (i = backwardMostMove; i < upto; i++) {
\r
7530 SendMoveToProgram(i, cps);
\r
7536 ResurrectChessProgram()
\r
7538 /* The chess program may have exited.
\r
7539 If so, restart it and feed it all the moves made so far. */
\r
7541 if (appData.noChessProgram || first.pr != NoProc) return;
\r
7543 StartChessProgram(&first);
\r
7544 InitChessProgram(&first, FALSE);
\r
7545 FeedMovesToProgram(&first, currentMove);
\r
7547 if (!first.sendTime) {
\r
7548 /* can't tell gnuchess what its clock should read,
\r
7549 so we bow to its notion. */
\r
7551 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
7552 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
7555 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7556 first.analysisSupport) {
\r
7557 SendToProgram("analyze\n", &first);
\r
7558 first.analyzing = TRUE;
\r
7563 * Button procedures
\r
7566 Reset(redraw, init)
\r
7571 if (appData.debugMode) {
\r
7572 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
7573 redraw, init, gameMode);
\r
7575 pausing = pauseExamInvalid = FALSE;
\r
7576 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
7578 whiteFlag = blackFlag = FALSE;
\r
7579 userOfferedDraw = FALSE;
\r
7580 hintRequested = bookRequested = FALSE;
\r
7581 first.maybeThinking = FALSE;
\r
7582 second.maybeThinking = FALSE;
\r
7583 thinkOutput[0] = NULLCHAR;
\r
7584 lastHint[0] = NULLCHAR;
\r
7585 ClearGameInfo(&gameInfo);
\r
7586 gameInfo.variant = StringToVariant(appData.variant);
\r
7587 ics_user_moved = ics_clock_paused = FALSE;
\r
7588 ics_getting_history = H_FALSE;
\r
7590 white_holding[0] = black_holding[0] = NULLCHAR;
\r
7591 ClearProgramStats();
\r
7594 ClearHighlights();
\r
7595 flipView = appData.flipView;
\r
7596 ClearPremoveHighlights();
\r
7597 gotPremove = FALSE;
\r
7598 alarmSounded = FALSE;
\r
7600 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
7601 if(appData.serverMovesName != NULL) {
\r
7602 /* [HGM] prepare to make moves file for broadcasting */
\r
7603 clock_t t = clock();
\r
7604 if(serverMoves != NULL) fclose(serverMoves);
\r
7605 serverMoves = fopen(appData.serverMovesName, "r");
\r
7606 if(serverMoves != NULL) {
\r
7607 fclose(serverMoves);
\r
7608 /* delay 15 sec before overwriting, so all clients can see end */
\r
7609 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
7611 serverMoves = fopen(appData.serverMovesName, "w");
\r
7614 ExitAnalyzeMode();
\r
7615 gameMode = BeginningOfGame;
\r
7617 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
7618 InitPosition(redraw);
\r
7619 for (i = 0; i < MAX_MOVES; i++) {
\r
7620 if (commentList[i] != NULL) {
\r
7621 free(commentList[i]);
\r
7622 commentList[i] = NULL;
\r
7626 timeRemaining[0][0] = whiteTimeRemaining;
\r
7627 timeRemaining[1][0] = blackTimeRemaining;
\r
7628 if (first.pr == NULL) {
\r
7629 StartChessProgram(&first);
\r
7632 InitChessProgram(&first, startedFromSetupPosition);
\r
7635 DisplayMessage("", "");
\r
7636 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7640 AutoPlayGameLoop()
\r
7643 if (!AutoPlayOneMove())
\r
7645 if (matchMode || appData.timeDelay == 0)
\r
7647 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
7649 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
7658 int fromX, fromY, toX, toY;
\r
7660 if (appData.debugMode) {
\r
7661 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
7664 if (gameMode != PlayFromGameFile)
\r
7667 if (currentMove >= forwardMostMove) {
\r
7668 gameMode = EditGame;
\r
7671 /* [AS] Clear current move marker at the end of a game */
\r
7672 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
7677 toX = moveList[currentMove][2] - AAA;
\r
7678 toY = moveList[currentMove][3] - ONE;
\r
7680 if (moveList[currentMove][1] == '@') {
\r
7681 if (appData.highlightLastMove) {
\r
7682 SetHighlights(-1, -1, toX, toY);
\r
7685 fromX = moveList[currentMove][0] - AAA;
\r
7686 fromY = moveList[currentMove][1] - ONE;
\r
7688 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
7690 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
7692 if (appData.highlightLastMove) {
\r
7693 SetHighlights(fromX, fromY, toX, toY);
\r
7696 DisplayMove(currentMove);
\r
7697 SendMoveToProgram(currentMove++, &first);
\r
7698 DisplayBothClocks();
\r
7699 DrawPosition(FALSE, boards[currentMove]);
\r
7700 // [HGM] PV info: always display, routine tests if empty
\r
7701 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7707 LoadGameOneMove(readAhead)
\r
7708 ChessMove readAhead;
\r
7710 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
7711 char promoChar = NULLCHAR;
\r
7712 ChessMove moveType;
\r
7713 char move[MSG_SIZ];
\r
7716 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
7717 gameMode != AnalyzeMode && gameMode != Training) {
\r
7718 gameFileFP = NULL;
\r
7722 yyboardindex = forwardMostMove;
\r
7723 if (readAhead != (ChessMove)0) {
\r
7724 moveType = readAhead;
\r
7726 if (gameFileFP == NULL)
\r
7728 moveType = (ChessMove) yylex();
\r
7732 switch (moveType) {
\r
7734 if (appData.debugMode)
\r
7735 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7737 if (*p == '{' || *p == '[' || *p == '(') {
\r
7738 p[strlen(p) - 1] = NULLCHAR;
\r
7742 /* append the comment but don't display it */
\r
7743 while (*p == '\n') p++;
\r
7744 AppendComment(currentMove, p);
\r
7747 case WhiteCapturesEnPassant:
\r
7748 case BlackCapturesEnPassant:
\r
7749 case WhitePromotionChancellor:
\r
7750 case BlackPromotionChancellor:
\r
7751 case WhitePromotionArchbishop:
\r
7752 case BlackPromotionArchbishop:
\r
7753 case WhitePromotionQueen:
\r
7754 case BlackPromotionQueen:
\r
7755 case WhitePromotionRook:
\r
7756 case BlackPromotionRook:
\r
7757 case WhitePromotionBishop:
\r
7758 case BlackPromotionBishop:
\r
7759 case WhitePromotionKnight:
\r
7760 case BlackPromotionKnight:
\r
7761 case WhitePromotionKing:
\r
7762 case BlackPromotionKing:
\r
7764 case WhiteKingSideCastle:
\r
7765 case WhiteQueenSideCastle:
\r
7766 case BlackKingSideCastle:
\r
7767 case BlackQueenSideCastle:
\r
7768 case WhiteKingSideCastleWild:
\r
7769 case WhiteQueenSideCastleWild:
\r
7770 case BlackKingSideCastleWild:
\r
7771 case BlackQueenSideCastleWild:
\r
7773 case WhiteHSideCastleFR:
\r
7774 case WhiteASideCastleFR:
\r
7775 case BlackHSideCastleFR:
\r
7776 case BlackASideCastleFR:
\r
7778 if (appData.debugMode)
\r
7779 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7780 fromX = currentMoveString[0] - AAA;
\r
7781 fromY = currentMoveString[1] - ONE;
\r
7782 toX = currentMoveString[2] - AAA;
\r
7783 toY = currentMoveString[3] - ONE;
\r
7784 promoChar = currentMoveString[4];
\r
7789 if (appData.debugMode)
\r
7790 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7791 fromX = moveType == WhiteDrop ?
\r
7792 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
7793 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
7794 fromY = DROP_RANK;
\r
7795 toX = currentMoveString[2] - AAA;
\r
7796 toY = currentMoveString[3] - ONE;
\r
7802 case GameUnfinished:
\r
7803 if (appData.debugMode)
\r
7804 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
7805 p = strchr(yy_text, '{');
\r
7806 if (p == NULL) p = strchr(yy_text, '(');
\r
7809 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7811 q = strchr(p, *p == '{' ? '}' : ')');
\r
7812 if (q != NULL) *q = NULLCHAR;
\r
7815 GameEnds(moveType, p, GE_FILE);
\r
7817 if (cmailMsgLoaded) {
\r
7818 ClearHighlights();
\r
7819 flipView = WhiteOnMove(currentMove);
\r
7820 if (moveType == GameUnfinished) flipView = !flipView;
\r
7821 if (appData.debugMode)
\r
7822 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
7826 case (ChessMove) 0: /* end of file */
\r
7827 if (appData.debugMode)
\r
7828 fprintf(debugFP, "Parser hit end of file\n");
\r
7829 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7830 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7834 case MT_CHECKMATE:
\r
7835 if (WhiteOnMove(currentMove)) {
\r
7836 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7838 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7841 case MT_STALEMATE:
\r
7842 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7848 case MoveNumberOne:
\r
7849 if (lastLoadGameStart == GNUChessGame) {
\r
7850 /* GNUChessGames have numbers, but they aren't move numbers */
\r
7851 if (appData.debugMode)
\r
7852 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7853 yy_text, (int) moveType);
\r
7854 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7856 /* else fall thru */
\r
7859 case GNUChessGame:
\r
7861 /* Reached start of next game in file */
\r
7862 if (appData.debugMode)
\r
7863 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
7864 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7865 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7869 case MT_CHECKMATE:
\r
7870 if (WhiteOnMove(currentMove)) {
\r
7871 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7873 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7876 case MT_STALEMATE:
\r
7877 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7883 case PositionDiagram: /* should not happen; ignore */
\r
7884 case ElapsedTime: /* ignore */
\r
7885 case NAG: /* ignore */
\r
7886 if (appData.debugMode)
\r
7887 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7888 yy_text, (int) moveType);
\r
7889 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7892 if (appData.testLegality) {
\r
7893 if (appData.debugMode)
\r
7894 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
7895 sprintf(move, "Illegal move: %d.%s%s",
\r
7896 (forwardMostMove / 2) + 1,
\r
7897 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7898 DisplayError(move, 0);
\r
7901 if (appData.debugMode)
\r
7902 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
7903 yy_text, currentMoveString);
\r
7904 fromX = currentMoveString[0] - AAA;
\r
7905 fromY = currentMoveString[1] - ONE;
\r
7906 toX = currentMoveString[2] - AAA;
\r
7907 toY = currentMoveString[3] - ONE;
\r
7908 promoChar = currentMoveString[4];
\r
7912 case AmbiguousMove:
\r
7913 if (appData.debugMode)
\r
7914 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
7915 sprintf(move, "Ambiguous move: %d.%s%s",
\r
7916 (forwardMostMove / 2) + 1,
\r
7917 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7918 DisplayError(move, 0);
\r
7923 case ImpossibleMove:
\r
7924 if (appData.debugMode)
\r
7925 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
7926 sprintf(move, "Illegal move: %d.%s%s",
\r
7927 (forwardMostMove / 2) + 1,
\r
7928 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7929 DisplayError(move, 0);
\r
7935 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
7936 DrawPosition(FALSE, boards[currentMove]);
\r
7937 DisplayBothClocks();
\r
7938 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
7939 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7941 (void) StopLoadGameTimer();
\r
7942 gameFileFP = NULL;
\r
7943 cmailOldMove = forwardMostMove;
\r
7946 /* currentMoveString is set as a side-effect of yylex */
\r
7947 strcat(currentMoveString, "\n");
\r
7948 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7950 thinkOutput[0] = NULLCHAR;
\r
7951 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7952 currentMove = forwardMostMove;
\r
7957 /* Load the nth game from the given file */
\r
7959 LoadGameFromFile(filename, n, title, useList)
\r
7963 /*Boolean*/ int useList;
\r
7966 char buf[MSG_SIZ];
\r
7968 if (strcmp(filename, "-") == 0) {
\r
7972 f = fopen(filename, "rb");
\r
7974 sprintf(buf, "Can't open \"%s\"", filename);
\r
7975 DisplayError(buf, errno);
\r
7979 if (fseek(f, 0, 0) == -1) {
\r
7980 /* f is not seekable; probably a pipe */
\r
7983 if (useList && n == 0) {
\r
7984 int error = GameListBuild(f);
\r
7986 DisplayError("Cannot build game list", error);
\r
7987 } else if (!ListEmpty(&gameList) &&
\r
7988 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7989 GameListPopUp(f, title);
\r
7992 GameListDestroy();
\r
7995 if (n == 0) n = 1;
\r
7996 return LoadGame(f, n, title, FALSE);
\r
8001 MakeRegisteredMove()
\r
8003 int fromX, fromY, toX, toY;
\r
8005 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8006 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8009 if (appData.debugMode)
\r
8010 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8011 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8013 thinkOutput[0] = NULLCHAR;
\r
8014 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8015 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8016 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8017 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8018 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8019 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8020 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8021 ShowMove(fromX, fromY, toX, toY);
\r
8023 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8024 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8029 case MT_CHECKMATE:
\r
8030 if (WhiteOnMove(currentMove)) {
\r
8031 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8033 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8037 case MT_STALEMATE:
\r
8038 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8044 case CMAIL_RESIGN:
\r
8045 if (WhiteOnMove(currentMove)) {
\r
8046 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8048 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8052 case CMAIL_ACCEPT:
\r
8053 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8064 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8066 CmailLoadGame(f, gameNumber, title, useList)
\r
8074 if (gameNumber > nCmailGames) {
\r
8075 DisplayError("No more games in this message", 0);
\r
8078 if (f == lastLoadGameFP) {
\r
8079 int offset = gameNumber - lastLoadGameNumber;
\r
8080 if (offset == 0) {
\r
8081 cmailMsg[0] = NULLCHAR;
\r
8082 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8083 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8084 nCmailMovesRegistered--;
\r
8086 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8087 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8088 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8091 if (! RegisterMove()) return FALSE;
\r
8095 retVal = LoadGame(f, gameNumber, title, useList);
\r
8097 /* Make move registered during previous look at this game, if any */
\r
8098 MakeRegisteredMove();
\r
8100 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8101 commentList[currentMove]
\r
8102 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8103 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8109 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8111 ReloadGame(offset)
\r
8114 int gameNumber = lastLoadGameNumber + offset;
\r
8115 if (lastLoadGameFP == NULL) {
\r
8116 DisplayError("No game has been loaded yet", 0);
\r
8119 if (gameNumber <= 0) {
\r
8120 DisplayError("Can't back up any further", 0);
\r
8123 if (cmailMsgLoaded) {
\r
8124 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8125 lastLoadGameTitle, lastLoadGameUseList);
\r
8127 return LoadGame(lastLoadGameFP, gameNumber,
\r
8128 lastLoadGameTitle, lastLoadGameUseList);
\r
8134 /* Load the nth game from open file f */
\r
8136 LoadGame(f, gameNumber, title, useList)
\r
8143 char buf[MSG_SIZ];
\r
8144 int gn = gameNumber;
\r
8145 ListGame *lg = NULL;
\r
8146 int numPGNTags = 0;
\r
8148 GameMode oldGameMode;
\r
8149 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8151 if (appData.debugMode)
\r
8152 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8154 if (gameMode == Training )
\r
8155 SetTrainingModeOff();
\r
8157 oldGameMode = gameMode;
\r
8158 if (gameMode != BeginningOfGame) {
\r
8159 Reset(FALSE, TRUE);
\r
8163 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8164 fclose(lastLoadGameFP);
\r
8168 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8171 fseek(f, lg->offset, 0);
\r
8172 GameListHighlight(gameNumber);
\r
8176 DisplayError("Game number out of range", 0);
\r
8180 GameListDestroy();
\r
8181 if (fseek(f, 0, 0) == -1) {
\r
8182 if (f == lastLoadGameFP ?
\r
8183 gameNumber == lastLoadGameNumber + 1 :
\r
8184 gameNumber == 1) {
\r
8187 DisplayError("Can't seek on game file", 0);
\r
8192 lastLoadGameFP = f;
\r
8193 lastLoadGameNumber = gameNumber;
\r
8194 strcpy(lastLoadGameTitle, title);
\r
8195 lastLoadGameUseList = useList;
\r
8199 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8200 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8201 lg->gameInfo.black);
\r
8202 DisplayTitle(buf);
\r
8203 } else if (*title != NULLCHAR) {
\r
8204 if (gameNumber > 1) {
\r
8205 sprintf(buf, "%s %d", title, gameNumber);
\r
8206 DisplayTitle(buf);
\r
8208 DisplayTitle(title);
\r
8212 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8213 gameMode = PlayFromGameFile;
\r
8217 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8218 CopyBoard(boards[0], initialPosition);
\r
8222 * Skip the first gn-1 games in the file.
\r
8223 * Also skip over anything that precedes an identifiable
\r
8224 * start of game marker, to avoid being confused by
\r
8225 * garbage at the start of the file. Currently
\r
8226 * recognized start of game markers are the move number "1",
\r
8227 * the pattern "gnuchess .* game", the pattern
\r
8228 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8229 * A game that starts with one of the latter two patterns
\r
8230 * will also have a move number 1, possibly
\r
8231 * following a position diagram.
\r
8232 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8233 * start with an unnumbered move. Does that break anything?
\r
8235 cm = lastLoadGameStart = (ChessMove) 0;
\r
8237 yyboardindex = forwardMostMove;
\r
8238 cm = (ChessMove) yylex();
\r
8240 case (ChessMove) 0:
\r
8241 if (cmailMsgLoaded) {
\r
8242 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8244 Reset(TRUE, TRUE);
\r
8245 DisplayError("Game not found in file", 0);
\r
8249 case GNUChessGame:
\r
8252 lastLoadGameStart = cm;
\r
8255 case MoveNumberOne:
\r
8256 switch (lastLoadGameStart) {
\r
8257 case GNUChessGame:
\r
8261 case MoveNumberOne:
\r
8262 case (ChessMove) 0:
\r
8263 gn--; /* count this game */
\r
8264 lastLoadGameStart = cm;
\r
8273 switch (lastLoadGameStart) {
\r
8274 case GNUChessGame:
\r
8276 case MoveNumberOne:
\r
8277 case (ChessMove) 0:
\r
8278 gn--; /* count this game */
\r
8279 lastLoadGameStart = cm;
\r
8282 lastLoadGameStart = cm; /* game counted already */
\r
8290 yyboardindex = forwardMostMove;
\r
8291 cm = (ChessMove) yylex();
\r
8292 } while (cm == PGNTag || cm == Comment);
\r
8299 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8300 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8301 != CMAIL_OLD_RESULT) {
\r
8302 nCmailResults ++ ;
\r
8303 cmailResult[ CMAIL_MAX_GAMES
\r
8304 - gn - 1] = CMAIL_OLD_RESULT;
\r
8310 /* Only a NormalMove can be at the start of a game
\r
8311 * without a position diagram. */
\r
8312 if (lastLoadGameStart == (ChessMove) 0) {
\r
8314 lastLoadGameStart = MoveNumberOne;
\r
8323 if (appData.debugMode)
\r
8324 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8326 if (cm == XBoardGame) {
\r
8327 /* Skip any header junk before position diagram and/or move 1 */
\r
8329 yyboardindex = forwardMostMove;
\r
8330 cm = (ChessMove) yylex();
\r
8332 if (cm == (ChessMove) 0 ||
\r
8333 cm == GNUChessGame || cm == XBoardGame) {
\r
8334 /* Empty game; pretend end-of-file and handle later */
\r
8335 cm = (ChessMove) 0;
\r
8339 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8340 cm == PGNTag || cm == Comment)
\r
8343 } else if (cm == GNUChessGame) {
\r
8344 if (gameInfo.event != NULL) {
\r
8345 free(gameInfo.event);
\r
8347 gameInfo.event = StrSave(yy_text);
\r
8350 startedFromSetupPosition = FALSE;
\r
8351 while (cm == PGNTag) {
\r
8352 if (appData.debugMode)
\r
8353 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8354 err = ParsePGNTag(yy_text, &gameInfo);
\r
8355 if (!err) numPGNTags++;
\r
8357 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8358 if(gameInfo.variant != oldVariant) {
\r
8359 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8360 InitPosition(TRUE);
\r
8361 oldVariant = gameInfo.variant;
\r
8362 if (appData.debugMode)
\r
8363 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8367 if (gameInfo.fen != NULL) {
\r
8368 Board initial_position;
\r
8369 startedFromSetupPosition = TRUE;
\r
8370 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8371 Reset(TRUE, TRUE);
\r
8372 DisplayError("Bad FEN position in file", 0);
\r
8375 CopyBoard(boards[0], initial_position);
\r
8376 /* [HGM] copy FEN attributes as well */
\r
8378 initialRulePlies = FENrulePlies;
\r
8379 epStatus[0] = FENepStatus;
\r
8380 for( i=0; i< nrCastlingRights; i++ )
\r
8381 initialRights[i] = castlingRights[0][i] = FENcastlingRights[i];
\r
8383 if (blackPlaysFirst) {
\r
8384 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8385 CopyBoard(boards[1], initial_position);
\r
8386 strcpy(moveList[0], "");
\r
8387 strcpy(parseList[0], "");
\r
8388 timeRemaining[0][1] = whiteTimeRemaining;
\r
8389 timeRemaining[1][1] = blackTimeRemaining;
\r
8390 if (commentList[0] != NULL) {
\r
8391 commentList[1] = commentList[0];
\r
8392 commentList[0] = NULL;
\r
8395 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8397 yyboardindex = forwardMostMove;
\r
8398 free(gameInfo.fen);
\r
8399 gameInfo.fen = NULL;
\r
8402 yyboardindex = forwardMostMove;
\r
8403 cm = (ChessMove) yylex();
\r
8405 /* Handle comments interspersed among the tags */
\r
8406 while (cm == Comment) {
\r
8408 if (appData.debugMode)
\r
8409 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8411 if (*p == '{' || *p == '[' || *p == '(') {
\r
8412 p[strlen(p) - 1] = NULLCHAR;
\r
8415 while (*p == '\n') p++;
\r
8416 AppendComment(currentMove, p);
\r
8417 yyboardindex = forwardMostMove;
\r
8418 cm = (ChessMove) yylex();
\r
8422 /* don't rely on existence of Event tag since if game was
\r
8423 * pasted from clipboard the Event tag may not exist
\r
8425 if (numPGNTags > 0){
\r
8427 if (gameInfo.variant == VariantNormal) {
\r
8428 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8431 if( appData.autoDisplayTags ) {
\r
8432 tags = PGNTags(&gameInfo);
\r
8433 TagsPopUp(tags, CmailMsg());
\r
8438 /* Make something up, but don't display it now */
\r
8443 if (cm == PositionDiagram) {
\r
8446 Board initial_position;
\r
8448 if (appData.debugMode)
\r
8449 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8451 if (!startedFromSetupPosition) {
\r
8453 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8454 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8464 initial_position[i][j++] = CharToPiece(*p);
\r
8467 while (*p == ' ' || *p == '\t' ||
\r
8468 *p == '\n' || *p == '\r') p++;
\r
8470 if (strncmp(p, "black", strlen("black"))==0)
\r
8471 blackPlaysFirst = TRUE;
\r
8473 blackPlaysFirst = FALSE;
\r
8474 startedFromSetupPosition = TRUE;
\r
8476 CopyBoard(boards[0], initial_position);
\r
8477 if (blackPlaysFirst) {
\r
8478 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8479 CopyBoard(boards[1], initial_position);
\r
8480 strcpy(moveList[0], "");
\r
8481 strcpy(parseList[0], "");
\r
8482 timeRemaining[0][1] = whiteTimeRemaining;
\r
8483 timeRemaining[1][1] = blackTimeRemaining;
\r
8484 if (commentList[0] != NULL) {
\r
8485 commentList[1] = commentList[0];
\r
8486 commentList[0] = NULL;
\r
8489 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8492 yyboardindex = forwardMostMove;
\r
8493 cm = (ChessMove) yylex();
\r
8496 if (first.pr == NoProc) {
\r
8497 StartChessProgram(&first);
\r
8499 InitChessProgram(&first, FALSE);
\r
8500 SendToProgram("force\n", &first);
\r
8501 if (startedFromSetupPosition) {
\r
8502 SendBoard(&first, forwardMostMove);
\r
8503 if (appData.debugMode) {
\r
8504 fprintf(debugFP, "Load Game\n");
\r
8506 DisplayBothClocks();
\r
8509 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
8510 loadFlag = appData.suppressLoadMoves;
\r
8512 while (cm == Comment) {
\r
8514 if (appData.debugMode)
\r
8515 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8517 if (*p == '{' || *p == '[' || *p == '(') {
\r
8518 p[strlen(p) - 1] = NULLCHAR;
\r
8521 while (*p == '\n') p++;
\r
8522 AppendComment(currentMove, p);
\r
8523 yyboardindex = forwardMostMove;
\r
8524 cm = (ChessMove) yylex();
\r
8527 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
8528 cm == WhiteWins || cm == BlackWins ||
\r
8529 cm == GameIsDrawn || cm == GameUnfinished) {
\r
8530 DisplayMessage("", "No moves in game");
\r
8531 if (cmailMsgLoaded) {
\r
8532 if (appData.debugMode)
\r
8533 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
8534 ClearHighlights();
\r
8537 DrawPosition(FALSE, boards[currentMove]);
\r
8538 DisplayBothClocks();
\r
8539 gameMode = EditGame;
\r
8541 gameFileFP = NULL;
\r
8546 // [HGM] PV info: routine tests if comment empty
\r
8547 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
8548 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8550 if (!matchMode && appData.timeDelay != 0)
\r
8551 DrawPosition(FALSE, boards[currentMove]);
\r
8553 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
8554 programStats.ok_to_send = 1;
\r
8557 /* if the first token after the PGN tags is a move
\r
8558 * and not move number 1, retrieve it from the parser
\r
8560 if (cm != MoveNumberOne)
\r
8561 LoadGameOneMove(cm);
\r
8563 /* load the remaining moves from the file */
\r
8564 while (LoadGameOneMove((ChessMove)0)) {
\r
8565 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
8566 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
8569 /* rewind to the start of the game */
\r
8570 currentMove = backwardMostMove;
\r
8572 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8574 if (oldGameMode == AnalyzeFile ||
\r
8575 oldGameMode == AnalyzeMode) {
\r
8576 AnalyzeFileEvent();
\r
8579 if (matchMode || appData.timeDelay == 0) {
\r
8581 gameMode = EditGame;
\r
8583 } else if (appData.timeDelay > 0) {
\r
8584 AutoPlayGameLoop();
\r
8587 if (appData.debugMode)
\r
8588 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
8590 loadFlag = 0; /* [HGM] true game starts */
\r
8594 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
8596 ReloadPosition(offset)
\r
8599 int positionNumber = lastLoadPositionNumber + offset;
\r
8600 if (lastLoadPositionFP == NULL) {
\r
8601 DisplayError("No position has been loaded yet", 0);
\r
8604 if (positionNumber <= 0) {
\r
8605 DisplayError("Can't back up any further", 0);
\r
8608 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
8609 lastLoadPositionTitle);
\r
8612 /* Load the nth position from the given file */
\r
8614 LoadPositionFromFile(filename, n, title)
\r
8620 char buf[MSG_SIZ];
\r
8622 if (strcmp(filename, "-") == 0) {
\r
8623 return LoadPosition(stdin, n, "stdin");
\r
8625 f = fopen(filename, "rb");
\r
8627 sprintf(buf, "Can't open \"%s\"", filename);
\r
8628 DisplayError(buf, errno);
\r
8631 return LoadPosition(f, n, title);
\r
8636 /* Load the nth position from the given open file, and close it */
\r
8638 LoadPosition(f, positionNumber, title)
\r
8640 int positionNumber;
\r
8643 char *p, line[MSG_SIZ];
\r
8644 Board initial_position;
\r
8645 int i, j, fenMode, pn;
\r
8647 if (gameMode == Training )
\r
8648 SetTrainingModeOff();
\r
8650 if (gameMode != BeginningOfGame) {
\r
8651 Reset(FALSE, TRUE);
\r
8653 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
8654 fclose(lastLoadPositionFP);
\r
8656 if (positionNumber == 0) positionNumber = 1;
\r
8657 lastLoadPositionFP = f;
\r
8658 lastLoadPositionNumber = positionNumber;
\r
8659 strcpy(lastLoadPositionTitle, title);
\r
8660 if (first.pr == NoProc) {
\r
8661 StartChessProgram(&first);
\r
8662 InitChessProgram(&first, FALSE);
\r
8664 pn = positionNumber;
\r
8665 if (positionNumber < 0) {
\r
8666 /* Negative position number means to seek to that byte offset */
\r
8667 if (fseek(f, -positionNumber, 0) == -1) {
\r
8668 DisplayError("Can't seek on position file", 0);
\r
8673 if (fseek(f, 0, 0) == -1) {
\r
8674 if (f == lastLoadPositionFP ?
\r
8675 positionNumber == lastLoadPositionNumber + 1 :
\r
8676 positionNumber == 1) {
\r
8679 DisplayError("Can't seek on position file", 0);
\r
8684 /* See if this file is FEN or old-style xboard */
\r
8685 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
8686 DisplayError("Position not found in file", 0);
\r
8690 switch (line[0]) {
\r
8691 case '#': case 'x':
\r
8695 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
8696 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
8697 case '1': case '2': case '3': case '4': case '5': case '6':
\r
8698 case '7': case '8': case '9':
\r
8699 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
8700 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
8701 case 'C': case 'W': case 'c': case 'w':
\r
8706 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
8707 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
8711 if (fenMode || line[0] == '#') pn--;
\r
8713 /* skip postions before number pn */
\r
8714 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
8715 Reset(TRUE, TRUE);
\r
8716 DisplayError("Position not found in file", 0);
\r
8719 if (fenMode || line[0] == '#') pn--;
\r
8724 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
8725 DisplayError("Bad FEN position in file", 0);
\r
8729 (void) fgets(line, MSG_SIZ, f);
\r
8730 (void) fgets(line, MSG_SIZ, f);
\r
8732 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
8733 (void) fgets(line, MSG_SIZ, f);
\r
8734 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
8737 initial_position[i][j++] = CharToPiece(*p);
\r
8741 blackPlaysFirst = FALSE;
\r
8743 (void) fgets(line, MSG_SIZ, f);
\r
8744 if (strncmp(line, "black", strlen("black"))==0)
\r
8745 blackPlaysFirst = TRUE;
\r
8748 startedFromSetupPosition = TRUE;
\r
8750 SendToProgram("force\n", &first);
\r
8751 CopyBoard(boards[0], initial_position);
\r
8752 /* [HGM] copy FEN attributes as well */
\r
8754 initialRulePlies = FENrulePlies;
\r
8755 epStatus[0] = FENepStatus;
\r
8756 for( i=0; i< nrCastlingRights; i++ )
\r
8757 castlingRights[0][i] = FENcastlingRights[i];
\r
8759 if (blackPlaysFirst) {
\r
8760 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8761 strcpy(moveList[0], "");
\r
8762 strcpy(parseList[0], "");
\r
8763 CopyBoard(boards[1], initial_position);
\r
8764 DisplayMessage("", "Black to play");
\r
8766 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8767 DisplayMessage("", "White to play");
\r
8769 SendBoard(&first, forwardMostMove);
\r
8770 if (appData.debugMode) {
\r
8771 fprintf(debugFP, "Load Position\n");
\r
8774 if (positionNumber > 1) {
\r
8775 sprintf(line, "%s %d", title, positionNumber);
\r
8776 DisplayTitle(line);
\r
8778 DisplayTitle(title);
\r
8780 gameMode = EditGame;
\r
8783 timeRemaining[0][1] = whiteTimeRemaining;
\r
8784 timeRemaining[1][1] = blackTimeRemaining;
\r
8785 DrawPosition(FALSE, boards[currentMove]);
\r
8792 CopyPlayerNameIntoFileName(dest, src)
\r
8793 char **dest, *src;
\r
8795 while (*src != NULLCHAR && *src != ',') {
\r
8796 if (*src == ' ') {
\r
8800 *(*dest)++ = *src++;
\r
8805 char *DefaultFileName(ext)
\r
8808 static char def[MSG_SIZ];
\r
8811 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
8813 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
8815 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
8819 def[0] = NULLCHAR;
\r
8824 /* Save the current game to the given file */
\r
8826 SaveGameToFile(filename, append)
\r
8831 char buf[MSG_SIZ];
\r
8833 if (strcmp(filename, "-") == 0) {
\r
8834 return SaveGame(stdout, 0, NULL);
\r
8836 f = fopen(filename, append ? "a" : "w");
\r
8838 sprintf(buf, "Can't open \"%s\"", filename);
\r
8839 DisplayError(buf, errno);
\r
8842 return SaveGame(f, 0, NULL);
\r
8851 static char buf[MSG_SIZ];
\r
8854 p = strchr(str, ' ');
\r
8855 if (p == NULL) return str;
\r
8856 strncpy(buf, str, p - str);
\r
8857 buf[p - str] = NULLCHAR;
\r
8861 #define PGN_MAX_LINE 75
\r
8863 #define PGN_SIDE_WHITE 0
\r
8864 #define PGN_SIDE_BLACK 1
\r
8867 static int FindFirstMoveOutOfBook( int side )
\r
8871 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
8872 int index = backwardMostMove;
\r
8873 int has_book_hit = 0;
\r
8875 if( (index % 2) != side ) {
\r
8879 while( index < forwardMostMove ) {
\r
8880 /* Check to see if engine is in book */
\r
8881 int depth = pvInfoList[index].depth;
\r
8882 int score = pvInfoList[index].score;
\r
8885 if( depth <= 2 ) {
\r
8888 else if( score == 0 && depth == 63 ) {
\r
8889 in_book = 1; /* Zappa */
\r
8891 else if( score == 2 && depth == 99 ) {
\r
8892 in_book = 1; /* Abrok */
\r
8895 has_book_hit += in_book;
\r
8911 void GetOutOfBookInfo( char * buf )
\r
8915 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8917 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
8918 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
8922 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
8923 for( i=0; i<2; i++ ) {
\r
8927 if( i > 0 && oob[0] >= 0 ) {
\r
8928 strcat( buf, " " );
\r
8931 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
8932 sprintf( buf+strlen(buf), "%s%.2f",
\r
8933 pvInfoList[idx].score >= 0 ? "+" : "",
\r
8934 pvInfoList[idx].score / 100.0 );
\r
8940 /* Save game in PGN style and close the file */
\r
8945 int i, offset, linelen, newblock;
\r
8949 int movelen, numlen, blank;
\r
8950 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
8952 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8954 tm = time((time_t *) NULL);
\r
8956 PrintPGNTags(f, &gameInfo);
\r
8958 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8959 char *fen = PositionToFEN(backwardMostMove, 1);
\r
8960 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
8961 fprintf(f, "\n{--------------\n");
\r
8962 PrintPosition(f, backwardMostMove);
\r
8963 fprintf(f, "--------------}\n");
\r
8967 /* [AS] Out of book annotation */
\r
8968 if( appData.saveOutOfBookInfo ) {
\r
8971 GetOutOfBookInfo( buf );
\r
8973 if( buf[0] != '\0' ) {
\r
8974 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8981 i = backwardMostMove;
\r
8985 while (i < forwardMostMove) {
\r
8986 /* Print comments preceding this move */
\r
8987 if (commentList[i] != NULL) {
\r
8988 if (linelen > 0) fprintf(f, "\n");
\r
8989 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8994 /* Format move number */
\r
8995 if ((i % 2) == 0) {
\r
8996 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8999 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9001 numtext[0] = NULLCHAR;
\r
9004 numlen = strlen(numtext);
\r
9007 /* Print move number */
\r
9008 blank = linelen > 0 && numlen > 0;
\r
9009 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9018 fprintf(f, numtext);
\r
9019 linelen += numlen;
\r
9022 movetext = SavePart(parseList[i]);
\r
9024 /* [AS] Add PV info if present */
\r
9025 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9026 /* [HGM] add time */
\r
9027 char buf[MSG_SIZ]; int seconds = 0;
\r
9030 if(i >= backwardMostMove) {
\r
9031 /* take the time that changed */
\r
9032 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
9034 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
9038 seconds = pvInfoList[i].time/100; // [HGM] PVtime: use engine time
\r
9039 if (appData.debugMode) {
\r
9040 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9041 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9042 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9046 if( seconds < 0 ) buf[0] = 0; else
\r
9047 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
9048 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9050 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
9052 pvInfoList[i].score >= 0 ? "+" : "",
\r
9053 pvInfoList[i].score / 100.0,
\r
9054 pvInfoList[i].depth,
\r
9056 movetext = move_buffer;
\r
9059 movelen = strlen(movetext);
\r
9062 blank = linelen > 0 && movelen > 0;
\r
9063 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9072 fprintf(f, movetext);
\r
9073 linelen += movelen;
\r
9078 /* Start a new line */
\r
9079 if (linelen > 0) fprintf(f, "\n");
\r
9081 /* Print comments after last move */
\r
9082 if (commentList[i] != NULL) {
\r
9083 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9086 /* Print result */
\r
9087 if (gameInfo.resultDetails != NULL &&
\r
9088 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9089 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9090 PGNResult(gameInfo.result));
\r
9092 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9099 /* Save game in old style and close the file */
\r
9101 SaveGameOldStyle(f)
\r
9107 tm = time((time_t *) NULL);
\r
9109 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9110 PrintOpponents(f);
\r
9112 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9113 fprintf(f, "\n[--------------\n");
\r
9114 PrintPosition(f, backwardMostMove);
\r
9115 fprintf(f, "--------------]\n");
\r
9120 i = backwardMostMove;
\r
9121 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9123 while (i < forwardMostMove) {
\r
9124 if (commentList[i] != NULL) {
\r
9125 fprintf(f, "[%s]\n", commentList[i]);
\r
9128 if ((i % 2) == 1) {
\r
9129 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9132 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9134 if (commentList[i] != NULL) {
\r
9138 if (i >= forwardMostMove) {
\r
9142 fprintf(f, "%s\n", parseList[i]);
\r
9147 if (commentList[i] != NULL) {
\r
9148 fprintf(f, "[%s]\n", commentList[i]);
\r
9151 /* This isn't really the old style, but it's close enough */
\r
9152 if (gameInfo.resultDetails != NULL &&
\r
9153 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9154 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9155 gameInfo.resultDetails);
\r
9157 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9164 /* Save the current game to open file f and close the file */
\r
9166 SaveGame(f, dummy, dummy2)
\r
9171 if (gameMode == EditPosition) EditPositionDone();
\r
9172 if (appData.oldSaveStyle)
\r
9173 return SaveGameOldStyle(f);
\r
9175 return SaveGamePGN(f);
\r
9178 /* Save the current position to the given file */
\r
9180 SavePositionToFile(filename)
\r
9184 char buf[MSG_SIZ];
\r
9186 if (strcmp(filename, "-") == 0) {
\r
9187 return SavePosition(stdout, 0, NULL);
\r
9189 f = fopen(filename, "a");
\r
9191 sprintf(buf, "Can't open \"%s\"", filename);
\r
9192 DisplayError(buf, errno);
\r
9195 SavePosition(f, 0, NULL);
\r
9201 /* Save the current position to the given open file and close the file */
\r
9203 SavePosition(f, dummy, dummy2)
\r
9211 if (appData.oldSaveStyle) {
\r
9212 tm = time((time_t *) NULL);
\r
9214 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9215 PrintOpponents(f);
\r
9216 fprintf(f, "[--------------\n");
\r
9217 PrintPosition(f, currentMove);
\r
9218 fprintf(f, "--------------]\n");
\r
9220 fen = PositionToFEN(currentMove, 1);
\r
9221 fprintf(f, "%s\n", fen);
\r
9229 ReloadCmailMsgEvent(unregister)
\r
9233 static char *inFilename = NULL;
\r
9234 static char *outFilename;
\r
9236 struct stat inbuf, outbuf;
\r
9239 /* Any registered moves are unregistered if unregister is set, */
\r
9240 /* i.e. invoked by the signal handler */
\r
9242 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9243 cmailMoveRegistered[i] = FALSE;
\r
9244 if (cmailCommentList[i] != NULL) {
\r
9245 free(cmailCommentList[i]);
\r
9246 cmailCommentList[i] = NULL;
\r
9249 nCmailMovesRegistered = 0;
\r
9252 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9253 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9255 nCmailResults = 0;
\r
9257 if (inFilename == NULL) {
\r
9258 /* Because the filenames are static they only get malloced once */
\r
9259 /* and they never get freed */
\r
9260 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9261 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9263 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9264 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9267 status = stat(outFilename, &outbuf);
\r
9269 cmailMailedMove = FALSE;
\r
9271 status = stat(inFilename, &inbuf);
\r
9272 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9275 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9276 counts the games, notes how each one terminated, etc.
\r
9278 It would be nice to remove this kludge and instead gather all
\r
9279 the information while building the game list. (And to keep it
\r
9280 in the game list nodes instead of having a bunch of fixed-size
\r
9281 parallel arrays.) Note this will require getting each game's
\r
9282 termination from the PGN tags, as the game list builder does
\r
9283 not process the game moves. --mann
\r
9285 cmailMsgLoaded = TRUE;
\r
9286 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9288 /* Load first game in the file or popup game menu */
\r
9289 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9291 #endif /* !WIN32 */
\r
9299 char string[MSG_SIZ];
\r
9301 if ( cmailMailedMove
\r
9302 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9303 return TRUE; /* Allow free viewing */
\r
9306 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9307 /* with the move registered when the conditions for registering no */
\r
9309 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9310 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9311 nCmailMovesRegistered --;
\r
9313 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9315 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9316 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9320 if (cmailOldMove == -1) {
\r
9321 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
9325 if (currentMove > cmailOldMove + 1) {
\r
9326 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
9330 if (currentMove < cmailOldMove) {
\r
9331 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
9335 if (forwardMostMove > currentMove) {
\r
9336 /* Silently truncate extra moves */
\r
9340 if ( (currentMove == cmailOldMove + 1)
\r
9341 || ( (currentMove == cmailOldMove)
\r
9342 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9343 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9344 if (gameInfo.result != GameUnfinished) {
\r
9345 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9348 if (commentList[currentMove] != NULL) {
\r
9349 cmailCommentList[lastLoadGameNumber - 1]
\r
9350 = StrSave(commentList[currentMove]);
\r
9352 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9354 if (appData.debugMode)
\r
9355 fprintf(debugFP, "Saving %s for game %d\n",
\r
9356 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9359 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9361 f = fopen(string, "w");
\r
9362 if (appData.oldSaveStyle) {
\r
9363 SaveGameOldStyle(f); /* also closes the file */
\r
9365 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9366 f = fopen(string, "w");
\r
9367 SavePosition(f, 0, NULL); /* also closes the file */
\r
9369 fprintf(f, "{--------------\n");
\r
9370 PrintPosition(f, currentMove);
\r
9371 fprintf(f, "--------------}\n\n");
\r
9373 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9376 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9377 nCmailMovesRegistered ++;
\r
9378 } else if (nCmailGames == 1) {
\r
9379 DisplayError("You have not made a move yet", 0);
\r
9390 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9391 FILE *commandOutput;
\r
9392 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9393 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9399 if (! cmailMsgLoaded) {
\r
9400 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
9404 if (nCmailGames == nCmailResults) {
\r
9405 DisplayError("No unfinished games", 0);
\r
9409 #if CMAIL_PROHIBIT_REMAIL
\r
9410 if (cmailMailedMove) {
\r
9411 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
9412 DisplayError(msg, 0);
\r
9417 if (! (cmailMailedMove || RegisterMove())) return;
\r
9419 if ( cmailMailedMove
\r
9420 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9421 sprintf(string, partCommandString,
\r
9422 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9423 commandOutput = popen(string, "rb");
\r
9425 if (commandOutput == NULL) {
\r
9426 DisplayError("Failed to invoke cmail", 0);
\r
9428 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9429 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9431 if (nBuffers > 1) {
\r
9432 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9433 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9434 nBytes = MSG_SIZ - 1;
\r
9436 (void) memcpy(msg, buffer, nBytes);
\r
9438 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9440 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9441 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9444 for (i = 0; i < nCmailGames; i ++) {
\r
9445 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9450 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9452 sprintf(buffer, "%s/%s.%s.archive",
\r
9454 appData.cmailGameName,
\r
9456 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
9457 cmailMsgLoaded = FALSE;
\r
9461 DisplayInformation(msg);
\r
9462 pclose(commandOutput);
\r
9465 if ((*cmailMsg) != '\0') {
\r
9466 DisplayInformation(cmailMsg);
\r
9471 #endif /* !WIN32 */
\r
9480 int prependComma = 0;
\r
9482 char string[MSG_SIZ]; /* Space for game-list */
\r
9485 if (!cmailMsgLoaded) return "";
\r
9487 if (cmailMailedMove) {
\r
9488 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
9490 /* Create a list of games left */
\r
9491 sprintf(string, "[");
\r
9492 for (i = 0; i < nCmailGames; i ++) {
\r
9493 if (! ( cmailMoveRegistered[i]
\r
9494 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
9495 if (prependComma) {
\r
9496 sprintf(number, ",%d", i + 1);
\r
9498 sprintf(number, "%d", i + 1);
\r
9502 strcat(string, number);
\r
9505 strcat(string, "]");
\r
9507 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
9508 switch (nCmailGames) {
\r
9511 "Still need to make move for game\n");
\r
9516 "Still need to make moves for both games\n");
\r
9521 "Still need to make moves for all %d games\n",
\r
9526 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
9529 "Still need to make a move for game %s\n",
\r
9534 if (nCmailResults == nCmailGames) {
\r
9535 sprintf(cmailMsg, "No unfinished games\n");
\r
9537 sprintf(cmailMsg, "Ready to send mail\n");
\r
9543 "Still need to make moves for games %s\n",
\r
9549 #endif /* WIN32 */
\r
9555 if (gameMode == Training)
\r
9556 SetTrainingModeOff();
\r
9558 Reset(TRUE, TRUE);
\r
9559 cmailMsgLoaded = FALSE;
\r
9560 if (appData.icsActive) {
\r
9561 SendToICS(ics_prefix);
\r
9562 SendToICS("refresh\n");
\r
9571 if (exiting > 2) {
\r
9572 /* Give up on clean exit */
\r
9575 if (exiting > 1) {
\r
9576 /* Keep trying for clean exit */
\r
9580 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
9582 if (telnetISR != NULL) {
\r
9583 RemoveInputSource(telnetISR);
\r
9585 if (icsPR != NoProc) {
\r
9586 DestroyChildProcess(icsPR, TRUE);
\r
9589 /* Save game if resource set and not already saved by GameEnds() */
\r
9590 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
9591 && forwardMostMove > 0) {
\r
9592 if (*appData.saveGameFile != NULLCHAR) {
\r
9593 SaveGameToFile(appData.saveGameFile, TRUE);
\r
9594 } else if (appData.autoSaveGames) {
\r
9597 if (*appData.savePositionFile != NULLCHAR) {
\r
9598 SavePositionToFile(appData.savePositionFile);
\r
9601 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9603 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
9604 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER);
\r
9606 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
9607 /* make sure this other one finishes before killing it! */
\r
9608 if(endingGame) { int count = 0;
\r
9609 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
9610 while(endingGame && count++ < 10) DoSleep(1);
\r
9611 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
9614 /* Kill off chess programs */
\r
9615 if (first.pr != NoProc) {
\r
9616 ExitAnalyzeMode();
\r
9618 DoSleep( appData.delayBeforeQuit );
\r
9619 SendToProgram("quit\n", &first);
\r
9620 DoSleep( appData.delayAfterQuit );
\r
9621 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
9623 if (second.pr != NoProc) {
\r
9624 DoSleep( appData.delayBeforeQuit );
\r
9625 SendToProgram("quit\n", &second);
\r
9626 DoSleep( appData.delayAfterQuit );
\r
9627 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
9629 if (first.isr != NULL) {
\r
9630 RemoveInputSource(first.isr);
\r
9632 if (second.isr != NULL) {
\r
9633 RemoveInputSource(second.isr);
\r
9636 ShutDownFrontEnd();
\r
9643 if (appData.debugMode)
\r
9644 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
9648 if (gameMode == MachinePlaysWhite ||
\r
9649 gameMode == MachinePlaysBlack) {
\r
9652 DisplayBothClocks();
\r
9654 if (gameMode == PlayFromGameFile) {
\r
9655 if (appData.timeDelay >= 0)
\r
9656 AutoPlayGameLoop();
\r
9657 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
9658 Reset(FALSE, TRUE);
\r
9659 SendToICS(ics_prefix);
\r
9660 SendToICS("refresh\n");
\r
9661 } else if (currentMove < forwardMostMove) {
\r
9662 ForwardInner(forwardMostMove);
\r
9664 pauseExamInvalid = FALSE;
\r
9666 switch (gameMode) {
\r
9669 case IcsExamining:
\r
9670 pauseExamForwardMostMove = forwardMostMove;
\r
9671 pauseExamInvalid = FALSE;
\r
9672 /* fall through */
\r
9673 case IcsObserving:
\r
9674 case IcsPlayingWhite:
\r
9675 case IcsPlayingBlack:
\r
9679 case PlayFromGameFile:
\r
9680 (void) StopLoadGameTimer();
\r
9684 case BeginningOfGame:
\r
9685 if (appData.icsActive) return;
\r
9686 /* else fall through */
\r
9687 case MachinePlaysWhite:
\r
9688 case MachinePlaysBlack:
\r
9689 case TwoMachinesPlay:
\r
9690 if (forwardMostMove == 0)
\r
9691 return; /* don't pause if no one has moved */
\r
9692 if ((gameMode == MachinePlaysWhite &&
\r
9693 !WhiteOnMove(forwardMostMove)) ||
\r
9694 (gameMode == MachinePlaysBlack &&
\r
9695 WhiteOnMove(forwardMostMove))) {
\r
9706 EditCommentEvent()
\r
9708 char title[MSG_SIZ];
\r
9710 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
9711 strcpy(title, "Edit comment");
\r
9713 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
9714 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
9715 parseList[currentMove - 1]);
\r
9718 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
9725 char *tags = PGNTags(&gameInfo);
\r
9726 EditTagsPopUp(tags);
\r
9731 AnalyzeModeEvent()
\r
9733 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
9736 if (gameMode != AnalyzeFile) {
\r
9738 if (gameMode != EditGame) return;
\r
9739 ResurrectChessProgram();
\r
9740 SendToProgram("analyze\n", &first);
\r
9741 first.analyzing = TRUE;
\r
9742 /*first.maybeThinking = TRUE;*/
\r
9743 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9744 AnalysisPopUp("Analysis",
\r
9745 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9747 gameMode = AnalyzeMode;
\r
9752 StartAnalysisClock();
\r
9753 GetTimeMark(&lastNodeCountTime);
\r
9754 lastNodeCount = 0;
\r
9758 AnalyzeFileEvent()
\r
9760 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
9763 if (gameMode != AnalyzeMode) {
\r
9765 if (gameMode != EditGame) return;
\r
9766 ResurrectChessProgram();
\r
9767 SendToProgram("analyze\n", &first);
\r
9768 first.analyzing = TRUE;
\r
9769 /*first.maybeThinking = TRUE;*/
\r
9770 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9771 AnalysisPopUp("Analysis",
\r
9772 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9774 gameMode = AnalyzeFile;
\r
9779 StartAnalysisClock();
\r
9780 GetTimeMark(&lastNodeCountTime);
\r
9781 lastNodeCount = 0;
\r
9785 MachineWhiteEvent()
\r
9787 char buf[MSG_SIZ];
\r
9789 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
9793 if (gameMode == PlayFromGameFile ||
\r
9794 gameMode == TwoMachinesPlay ||
\r
9795 gameMode == Training ||
\r
9796 gameMode == AnalyzeMode ||
\r
9797 gameMode == EndOfGame)
\r
9800 if (gameMode == EditPosition)
\r
9801 EditPositionDone();
\r
9803 if (!WhiteOnMove(currentMove)) {
\r
9804 DisplayError("It is not White's turn", 0);
\r
9808 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9809 ExitAnalyzeMode();
\r
9811 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9812 gameMode == AnalyzeFile)
\r
9815 ResurrectChessProgram(); /* in case it isn't running */
\r
9816 gameMode = MachinePlaysWhite;
\r
9820 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9821 DisplayTitle(buf);
\r
9822 if (first.sendName) {
\r
9823 sprintf(buf, "name %s\n", gameInfo.black);
\r
9824 SendToProgram(buf, &first);
\r
9826 if (first.sendTime) {
\r
9827 if (first.useColors) {
\r
9828 SendToProgram("black\n", &first); /*gnu kludge*/
\r
9830 SendTimeRemaining(&first, TRUE);
\r
9832 if (first.useColors) {
\r
9833 SendToProgram("white\ngo\n", &first);
\r
9835 SendToProgram("go\n", &first);
\r
9837 SetMachineThinkingEnables();
\r
9838 first.maybeThinking = TRUE;
\r
9841 if (appData.autoFlipView && !flipView) {
\r
9842 flipView = !flipView;
\r
9843 DrawPosition(FALSE, NULL);
\r
9848 MachineBlackEvent()
\r
9850 char buf[MSG_SIZ];
\r
9852 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
9856 if (gameMode == PlayFromGameFile ||
\r
9857 gameMode == TwoMachinesPlay ||
\r
9858 gameMode == Training ||
\r
9859 gameMode == AnalyzeMode ||
\r
9860 gameMode == EndOfGame)
\r
9863 if (gameMode == EditPosition)
\r
9864 EditPositionDone();
\r
9866 if (WhiteOnMove(currentMove)) {
\r
9867 DisplayError("It is not Black's turn", 0);
\r
9871 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9872 ExitAnalyzeMode();
\r
9874 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9875 gameMode == AnalyzeFile)
\r
9878 ResurrectChessProgram(); /* in case it isn't running */
\r
9879 gameMode = MachinePlaysBlack;
\r
9883 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9884 DisplayTitle(buf);
\r
9885 if (first.sendName) {
\r
9886 sprintf(buf, "name %s\n", gameInfo.white);
\r
9887 SendToProgram(buf, &first);
\r
9889 if (first.sendTime) {
\r
9890 if (first.useColors) {
\r
9891 SendToProgram("white\n", &first); /*gnu kludge*/
\r
9893 SendTimeRemaining(&first, FALSE);
\r
9895 if (first.useColors) {
\r
9896 SendToProgram("black\ngo\n", &first);
\r
9898 SendToProgram("go\n", &first);
\r
9900 SetMachineThinkingEnables();
\r
9901 first.maybeThinking = TRUE;
\r
9904 if (appData.autoFlipView && flipView) {
\r
9905 flipView = !flipView;
\r
9906 DrawPosition(FALSE, NULL);
\r
9912 DisplayTwoMachinesTitle()
\r
9914 char buf[MSG_SIZ];
\r
9915 if (appData.matchGames > 0) {
\r
9916 if (first.twoMachinesColor[0] == 'w') {
\r
9917 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9918 gameInfo.white, gameInfo.black,
\r
9919 first.matchWins, second.matchWins,
\r
9920 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9922 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9923 gameInfo.white, gameInfo.black,
\r
9924 second.matchWins, first.matchWins,
\r
9925 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9928 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9930 DisplayTitle(buf);
\r
9934 TwoMachinesEvent P((void))
\r
9937 char buf[MSG_SIZ];
\r
9938 ChessProgramState *onmove;
\r
9940 if (appData.noChessProgram) return;
\r
9942 switch (gameMode) {
\r
9943 case TwoMachinesPlay:
\r
9945 case MachinePlaysWhite:
\r
9946 case MachinePlaysBlack:
\r
9947 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9948 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9951 /* fall through */
\r
9952 case BeginningOfGame:
\r
9953 case PlayFromGameFile:
\r
9956 if (gameMode != EditGame) return;
\r
9958 case EditPosition:
\r
9959 EditPositionDone();
\r
9963 ExitAnalyzeMode();
\r
9970 forwardMostMove = currentMove;
\r
9971 ResurrectChessProgram(); /* in case first program isn't running */
\r
9973 if (second.pr == NULL) {
\r
9974 StartChessProgram(&second);
\r
9975 if (second.protocolVersion == 1) {
\r
9976 TwoMachinesEventIfReady();
\r
9978 /* kludge: allow timeout for initial "feature" command */
\r
9980 DisplayMessage("", "Starting second chess program");
\r
9981 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9985 DisplayMessage("", "");
\r
9986 InitChessProgram(&second, FALSE);
\r
9987 SendToProgram("force\n", &second);
\r
9988 if (startedFromSetupPosition) {
\r
9989 SendBoard(&second, backwardMostMove);
\r
9990 if (appData.debugMode) {
\r
9991 fprintf(debugFP, "Two Machines\n");
\r
9994 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9995 SendMoveToProgram(i, &second);
\r
9998 gameMode = TwoMachinesPlay;
\r
10002 DisplayTwoMachinesTitle();
\r
10003 firstMove = TRUE;
\r
10004 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10007 onmove = &second;
\r
10010 SendToProgram(first.computerString, &first);
\r
10011 if (first.sendName) {
\r
10012 sprintf(buf, "name %s\n", second.tidy);
\r
10013 SendToProgram(buf, &first);
\r
10015 SendToProgram(second.computerString, &second);
\r
10016 if (second.sendName) {
\r
10017 sprintf(buf, "name %s\n", first.tidy);
\r
10018 SendToProgram(buf, &second);
\r
10021 if (!first.sendTime || !second.sendTime) {
\r
10023 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10024 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10026 if (onmove->sendTime) {
\r
10027 if (onmove->useColors) {
\r
10028 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10030 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10032 if (onmove->useColors) {
\r
10033 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10035 SendToProgram("go\n", onmove);
\r
10036 onmove->maybeThinking = TRUE;
\r
10037 SetMachineThinkingEnables();
\r
10045 if (gameMode == Training) {
\r
10046 SetTrainingModeOff();
\r
10047 gameMode = PlayFromGameFile;
\r
10048 DisplayMessage("", "Training mode off");
\r
10050 gameMode = Training;
\r
10051 animateTraining = appData.animate;
\r
10053 /* make sure we are not already at the end of the game */
\r
10054 if (currentMove < forwardMostMove) {
\r
10055 SetTrainingModeOn();
\r
10056 DisplayMessage("", "Training mode on");
\r
10058 gameMode = PlayFromGameFile;
\r
10059 DisplayError("Already at end of game", 0);
\r
10068 if (!appData.icsActive) return;
\r
10069 switch (gameMode) {
\r
10070 case IcsPlayingWhite:
\r
10071 case IcsPlayingBlack:
\r
10072 case IcsObserving:
\r
10074 case BeginningOfGame:
\r
10075 case IcsExamining:
\r
10081 case EditPosition:
\r
10082 EditPositionDone();
\r
10085 case AnalyzeMode:
\r
10086 case AnalyzeFile:
\r
10087 ExitAnalyzeMode();
\r
10095 gameMode = IcsIdle;
\r
10106 switch (gameMode) {
\r
10108 SetTrainingModeOff();
\r
10110 case MachinePlaysWhite:
\r
10111 case MachinePlaysBlack:
\r
10112 case BeginningOfGame:
\r
10113 SendToProgram("force\n", &first);
\r
10114 SetUserThinkingEnables();
\r
10116 case PlayFromGameFile:
\r
10117 (void) StopLoadGameTimer();
\r
10118 if (gameFileFP != NULL) {
\r
10119 gameFileFP = NULL;
\r
10122 case EditPosition:
\r
10123 EditPositionDone();
\r
10125 case AnalyzeMode:
\r
10126 case AnalyzeFile:
\r
10127 ExitAnalyzeMode();
\r
10128 SendToProgram("force\n", &first);
\r
10130 case TwoMachinesPlay:
\r
10131 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10132 ResurrectChessProgram();
\r
10133 SetUserThinkingEnables();
\r
10136 ResurrectChessProgram();
\r
10138 case IcsPlayingBlack:
\r
10139 case IcsPlayingWhite:
\r
10140 DisplayError("Warning: You are still playing a game", 0);
\r
10142 case IcsObserving:
\r
10143 DisplayError("Warning: You are still observing a game", 0);
\r
10145 case IcsExamining:
\r
10146 DisplayError("Warning: You are still examining a game", 0);
\r
10157 first.offeredDraw = second.offeredDraw = 0;
\r
10159 if (gameMode == PlayFromGameFile) {
\r
10160 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10161 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10162 DisplayTitle("");
\r
10165 if (gameMode == MachinePlaysWhite ||
\r
10166 gameMode == MachinePlaysBlack ||
\r
10167 gameMode == TwoMachinesPlay ||
\r
10168 gameMode == EndOfGame) {
\r
10169 i = forwardMostMove;
\r
10170 while (i > currentMove) {
\r
10171 SendToProgram("undo\n", &first);
\r
10174 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10175 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10176 DisplayBothClocks();
\r
10177 if (whiteFlag || blackFlag) {
\r
10178 whiteFlag = blackFlag = 0;
\r
10180 DisplayTitle("");
\r
10183 gameMode = EditGame;
\r
10190 EditPositionEvent()
\r
10192 if (gameMode == EditPosition) {
\r
10198 if (gameMode != EditGame) return;
\r
10200 gameMode = EditPosition;
\r
10203 if (currentMove > 0)
\r
10204 CopyBoard(boards[0], boards[currentMove]);
\r
10206 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10208 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10209 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10214 ExitAnalyzeMode()
\r
10216 if (first.analysisSupport && first.analyzing) {
\r
10217 SendToProgram("exit\n", &first);
\r
10218 first.analyzing = FALSE;
\r
10220 AnalysisPopDown();
\r
10221 thinkOutput[0] = NULLCHAR;
\r
10225 EditPositionDone()
\r
10227 startedFromSetupPosition = TRUE;
\r
10228 InitChessProgram(&first, FALSE);
\r
10229 SendToProgram("force\n", &first);
\r
10230 if (blackPlaysFirst) {
\r
10231 strcpy(moveList[0], "");
\r
10232 strcpy(parseList[0], "");
\r
10233 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10234 CopyBoard(boards[1], boards[0]);
\r
10236 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10238 SendBoard(&first, forwardMostMove);
\r
10239 if (appData.debugMode) {
\r
10240 fprintf(debugFP, "EditPosDone\n");
\r
10242 DisplayTitle("");
\r
10243 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10244 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10245 gameMode = EditGame;
\r
10247 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10248 ClearHighlights(); /* [AS] */
\r
10251 /* Pause for `ms' milliseconds */
\r
10252 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10259 GetTimeMark(&m1);
\r
10261 GetTimeMark(&m2);
\r
10262 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10265 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10267 SendMultiLineToICS(buf)
\r
10270 char temp[MSG_SIZ+1], *p;
\r
10273 len = strlen(buf);
\r
10274 if (len > MSG_SIZ)
\r
10277 strncpy(temp, buf, len);
\r
10282 if (*p == '\n' || *p == '\r')
\r
10287 strcat(temp, "\n");
\r
10289 SendToPlayer(temp, strlen(temp));
\r
10293 SetWhiteToPlayEvent()
\r
10295 if (gameMode == EditPosition) {
\r
10296 blackPlaysFirst = FALSE;
\r
10297 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10298 } else if (gameMode == IcsExamining) {
\r
10299 SendToICS(ics_prefix);
\r
10300 SendToICS("tomove white\n");
\r
10305 SetBlackToPlayEvent()
\r
10307 if (gameMode == EditPosition) {
\r
10308 blackPlaysFirst = TRUE;
\r
10309 currentMove = 1; /* kludge */
\r
10310 DisplayBothClocks();
\r
10312 } else if (gameMode == IcsExamining) {
\r
10313 SendToICS(ics_prefix);
\r
10314 SendToICS("tomove black\n");
\r
10319 EditPositionMenuEvent(selection, x, y)
\r
10320 ChessSquare selection;
\r
10323 char buf[MSG_SIZ];
\r
10324 ChessSquare piece = boards[0][y][x];
\r
10326 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10328 switch (selection) {
\r
10330 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10331 SendToICS(ics_prefix);
\r
10332 SendToICS("bsetup clear\n");
\r
10333 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10334 SendToICS(ics_prefix);
\r
10335 SendToICS("clearboard\n");
\r
10337 for (x = 0; x < BOARD_WIDTH; x++) {
\r
10338 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10339 if (gameMode == IcsExamining) {
\r
10340 if (boards[currentMove][y][x] != EmptySquare) {
\r
10341 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10342 AAA + x, ONE + y);
\r
10346 boards[0][y][x] = EmptySquare;
\r
10351 if (gameMode == EditPosition) {
\r
10352 DrawPosition(FALSE, boards[0]);
\r
10357 SetWhiteToPlayEvent();
\r
10361 SetBlackToPlayEvent();
\r
10364 case EmptySquare:
\r
10365 if (gameMode == IcsExamining) {
\r
10366 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10369 boards[0][y][x] = EmptySquare;
\r
10370 DrawPosition(FALSE, boards[0]);
\r
10374 case PromotePiece:
\r
10375 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10376 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10377 selection = (ChessSquare) (PROMOTED piece);
\r
10378 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10379 else selection = (ChessSquare)((int)piece - 1);
\r
10380 goto defaultlabel;
\r
10382 case DemotePiece:
\r
10383 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10384 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10385 selection = (ChessSquare) (DEMOTED piece);
\r
10386 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10387 else selection = (ChessSquare)((int)piece + 1);
\r
10388 goto defaultlabel;
\r
10392 if(gameInfo.variant == VariantShatranj ||
\r
10393 gameInfo.variant == VariantXiangqi ||
\r
10394 gameInfo.variant == VariantCourier )
\r
10395 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10396 goto defaultlabel;
\r
10400 if(gameInfo.variant == VariantXiangqi)
\r
10401 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
10402 if(gameInfo.variant == VariantKnightmate)
\r
10403 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
10406 if (gameMode == IcsExamining) {
\r
10407 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
10408 PieceToChar(selection), AAA + x, ONE + y);
\r
10411 boards[0][y][x] = selection;
\r
10412 DrawPosition(FALSE, boards[0]);
\r
10420 DropMenuEvent(selection, x, y)
\r
10421 ChessSquare selection;
\r
10424 ChessMove moveType;
\r
10426 switch (gameMode) {
\r
10427 case IcsPlayingWhite:
\r
10428 case MachinePlaysBlack:
\r
10429 if (!WhiteOnMove(currentMove)) {
\r
10430 DisplayMoveError("It is Black's turn");
\r
10433 moveType = WhiteDrop;
\r
10435 case IcsPlayingBlack:
\r
10436 case MachinePlaysWhite:
\r
10437 if (WhiteOnMove(currentMove)) {
\r
10438 DisplayMoveError("It is White's turn");
\r
10441 moveType = BlackDrop;
\r
10444 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
10450 if (moveType == BlackDrop && selection < BlackPawn) {
\r
10451 selection = (ChessSquare) ((int) selection
\r
10452 + (int) BlackPawn - (int) WhitePawn);
\r
10454 if (boards[currentMove][y][x] != EmptySquare) {
\r
10455 DisplayMoveError("That square is occupied");
\r
10459 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
10465 /* Accept a pending offer of any kind from opponent */
\r
10467 if (appData.icsActive) {
\r
10468 SendToICS(ics_prefix);
\r
10469 SendToICS("accept\n");
\r
10470 } else if (cmailMsgLoaded) {
\r
10471 if (currentMove == cmailOldMove &&
\r
10472 commentList[cmailOldMove] != NULL &&
\r
10473 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10474 "Black offers a draw" : "White offers a draw")) {
\r
10476 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10477 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10479 DisplayError("There is no pending offer on this move", 0);
\r
10480 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10483 /* Not used for offers from chess program */
\r
10490 /* Decline a pending offer of any kind from opponent */
\r
10492 if (appData.icsActive) {
\r
10493 SendToICS(ics_prefix);
\r
10494 SendToICS("decline\n");
\r
10495 } else if (cmailMsgLoaded) {
\r
10496 if (currentMove == cmailOldMove &&
\r
10497 commentList[cmailOldMove] != NULL &&
\r
10498 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10499 "Black offers a draw" : "White offers a draw")) {
\r
10501 AppendComment(cmailOldMove, "Draw declined");
\r
10502 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
10503 #endif /*NOTDEF*/
\r
10505 DisplayError("There is no pending offer on this move", 0);
\r
10508 /* Not used for offers from chess program */
\r
10515 /* Issue ICS rematch command */
\r
10516 if (appData.icsActive) {
\r
10517 SendToICS(ics_prefix);
\r
10518 SendToICS("rematch\n");
\r
10525 /* Call your opponent's flag (claim a win on time) */
\r
10526 if (appData.icsActive) {
\r
10527 SendToICS(ics_prefix);
\r
10528 SendToICS("flag\n");
\r
10530 switch (gameMode) {
\r
10533 case MachinePlaysWhite:
\r
10536 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10539 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
10541 DisplayError("Your opponent is not out of time", 0);
\r
10544 case MachinePlaysBlack:
\r
10547 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10550 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
10552 DisplayError("Your opponent is not out of time", 0);
\r
10562 /* Offer draw or accept pending draw offer from opponent */
\r
10564 if (appData.icsActive) {
\r
10565 /* Note: tournament rules require draw offers to be
\r
10566 made after you make your move but before you punch
\r
10567 your clock. Currently ICS doesn't let you do that;
\r
10568 instead, you immediately punch your clock after making
\r
10569 a move, but you can offer a draw at any time. */
\r
10571 SendToICS(ics_prefix);
\r
10572 SendToICS("draw\n");
\r
10573 } else if (cmailMsgLoaded) {
\r
10574 if (currentMove == cmailOldMove &&
\r
10575 commentList[cmailOldMove] != NULL &&
\r
10576 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10577 "Black offers a draw" : "White offers a draw")) {
\r
10578 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10579 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10580 } else if (currentMove == cmailOldMove + 1) {
\r
10581 char *offer = WhiteOnMove(cmailOldMove) ?
\r
10582 "White offers a draw" : "Black offers a draw";
\r
10583 AppendComment(currentMove, offer);
\r
10584 DisplayComment(currentMove - 1, offer);
\r
10585 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
10587 DisplayError("You must make your move before offering a draw", 0);
\r
10588 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10590 } else if (first.offeredDraw) {
\r
10591 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
10593 if (first.sendDrawOffers) {
\r
10594 SendToProgram("draw\n", &first);
\r
10595 userOfferedDraw = TRUE;
\r
10603 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
10605 if (appData.icsActive) {
\r
10606 SendToICS(ics_prefix);
\r
10607 SendToICS("adjourn\n");
\r
10609 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
10617 /* Offer Abort or accept pending Abort offer from opponent */
\r
10619 if (appData.icsActive) {
\r
10620 SendToICS(ics_prefix);
\r
10621 SendToICS("abort\n");
\r
10623 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
10630 /* Resign. You can do this even if it's not your turn. */
\r
10632 if (appData.icsActive) {
\r
10633 SendToICS(ics_prefix);
\r
10634 SendToICS("resign\n");
\r
10636 switch (gameMode) {
\r
10637 case MachinePlaysWhite:
\r
10638 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
10640 case MachinePlaysBlack:
\r
10641 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
10644 if (cmailMsgLoaded) {
\r
10646 if (WhiteOnMove(cmailOldMove)) {
\r
10647 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
10649 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
10651 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
10662 StopObservingEvent()
\r
10664 /* Stop observing current games */
\r
10665 SendToICS(ics_prefix);
\r
10666 SendToICS("unobserve\n");
\r
10670 StopExaminingEvent()
\r
10672 /* Stop observing current game */
\r
10673 SendToICS(ics_prefix);
\r
10674 SendToICS("unexamine\n");
\r
10678 ForwardInner(target)
\r
10683 if (appData.debugMode)
\r
10684 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
10685 target, currentMove, forwardMostMove);
\r
10687 if (gameMode == EditPosition)
\r
10690 if (gameMode == PlayFromGameFile && !pausing)
\r
10693 if (gameMode == IcsExamining && pausing)
\r
10694 limit = pauseExamForwardMostMove;
\r
10696 limit = forwardMostMove;
\r
10698 if (target > limit) target = limit;
\r
10700 if (target > 0 && moveList[target - 1][0]) {
\r
10701 int fromX, fromY, toX, toY;
\r
10702 toX = moveList[target - 1][2] - AAA;
\r
10703 toY = moveList[target - 1][3] - ONE;
\r
10704 if (moveList[target - 1][1] == '@') {
\r
10705 if (appData.highlightLastMove) {
\r
10706 SetHighlights(-1, -1, toX, toY);
\r
10709 fromX = moveList[target - 1][0] - AAA;
\r
10710 fromY = moveList[target - 1][1] - ONE;
\r
10711 if (target == currentMove + 1) {
\r
10712 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
10714 if (appData.highlightLastMove) {
\r
10715 SetHighlights(fromX, fromY, toX, toY);
\r
10719 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10720 gameMode == Training || gameMode == PlayFromGameFile ||
\r
10721 gameMode == AnalyzeFile) {
\r
10722 while (currentMove < target) {
\r
10723 SendMoveToProgram(currentMove++, &first);
\r
10726 currentMove = target;
\r
10729 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10730 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10731 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10733 DisplayBothClocks();
\r
10734 DisplayMove(currentMove - 1);
\r
10735 DrawPosition(FALSE, boards[currentMove]);
\r
10736 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10737 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
10738 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10746 if (gameMode == IcsExamining && !pausing) {
\r
10747 SendToICS(ics_prefix);
\r
10748 SendToICS("forward\n");
\r
10750 ForwardInner(currentMove + 1);
\r
10757 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10758 /* to optimze, we temporarily turn off analysis mode while we feed
\r
10759 * the remaining moves to the engine. Otherwise we get analysis output
\r
10760 * after each move.
\r
10762 if (first.analysisSupport) {
\r
10763 SendToProgram("exit\nforce\n", &first);
\r
10764 first.analyzing = FALSE;
\r
10768 if (gameMode == IcsExamining && !pausing) {
\r
10769 SendToICS(ics_prefix);
\r
10770 SendToICS("forward 999999\n");
\r
10772 ForwardInner(forwardMostMove);
\r
10775 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10776 /* we have fed all the moves, so reactivate analysis mode */
\r
10777 SendToProgram("analyze\n", &first);
\r
10778 first.analyzing = TRUE;
\r
10779 /*first.maybeThinking = TRUE;*/
\r
10780 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10785 BackwardInner(target)
\r
10788 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
10790 if (appData.debugMode)
\r
10791 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
10792 target, currentMove, forwardMostMove);
\r
10794 if (gameMode == EditPosition) return;
\r
10795 if (currentMove <= backwardMostMove) {
\r
10796 ClearHighlights();
\r
10797 DrawPosition(full_redraw, boards[currentMove]);
\r
10800 if (gameMode == PlayFromGameFile && !pausing)
\r
10803 if (moveList[target][0]) {
\r
10804 int fromX, fromY, toX, toY;
\r
10805 toX = moveList[target][2] - AAA;
\r
10806 toY = moveList[target][3] - ONE;
\r
10807 if (moveList[target][1] == '@') {
\r
10808 if (appData.highlightLastMove) {
\r
10809 SetHighlights(-1, -1, toX, toY);
\r
10812 fromX = moveList[target][0] - AAA;
\r
10813 fromY = moveList[target][1] - ONE;
\r
10814 if (target == currentMove - 1) {
\r
10815 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
10817 if (appData.highlightLastMove) {
\r
10818 SetHighlights(fromX, fromY, toX, toY);
\r
10822 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
10823 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
10824 while (currentMove > target) {
\r
10825 SendToProgram("undo\n", &first);
\r
10829 currentMove = target;
\r
10832 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10833 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10834 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10836 DisplayBothClocks();
\r
10837 DisplayMove(currentMove - 1);
\r
10838 DrawPosition(full_redraw, boards[currentMove]);
\r
10839 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10840 // [HGM] PV info: routine tests if comment empty
\r
10841 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10847 if (gameMode == IcsExamining && !pausing) {
\r
10848 SendToICS(ics_prefix);
\r
10849 SendToICS("backward\n");
\r
10851 BackwardInner(currentMove - 1);
\r
10858 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10859 /* to optimze, we temporarily turn off analysis mode while we undo
\r
10860 * all the moves. Otherwise we get analysis output after each undo.
\r
10862 if (first.analysisSupport) {
\r
10863 SendToProgram("exit\nforce\n", &first);
\r
10864 first.analyzing = FALSE;
\r
10868 if (gameMode == IcsExamining && !pausing) {
\r
10869 SendToICS(ics_prefix);
\r
10870 SendToICS("backward 999999\n");
\r
10872 BackwardInner(backwardMostMove);
\r
10875 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10876 /* we have fed all the moves, so reactivate analysis mode */
\r
10877 SendToProgram("analyze\n", &first);
\r
10878 first.analyzing = TRUE;
\r
10879 /*first.maybeThinking = TRUE;*/
\r
10880 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10885 ToNrEvent(int to)
\r
10887 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
10888 if (to >= forwardMostMove) to = forwardMostMove;
\r
10889 if (to <= backwardMostMove) to = backwardMostMove;
\r
10890 if (to < currentMove) {
\r
10891 BackwardInner(to);
\r
10893 ForwardInner(to);
\r
10900 if (gameMode != IcsExamining) {
\r
10901 DisplayError("You are not examining a game", 0);
\r
10905 DisplayError("You can't revert while pausing", 0);
\r
10908 SendToICS(ics_prefix);
\r
10909 SendToICS("revert\n");
\r
10913 RetractMoveEvent()
\r
10915 switch (gameMode) {
\r
10916 case MachinePlaysWhite:
\r
10917 case MachinePlaysBlack:
\r
10918 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10919 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10922 if (forwardMostMove < 2) return;
\r
10923 currentMove = forwardMostMove = forwardMostMove - 2;
\r
10924 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10925 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10926 DisplayBothClocks();
\r
10927 DisplayMove(currentMove - 1);
\r
10928 ClearHighlights();/*!! could figure this out*/
\r
10929 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
10930 SendToProgram("remove\n", &first);
\r
10931 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
10934 case BeginningOfGame:
\r
10938 case IcsPlayingWhite:
\r
10939 case IcsPlayingBlack:
\r
10940 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
10941 SendToICS(ics_prefix);
\r
10942 SendToICS("takeback 2\n");
\r
10944 SendToICS(ics_prefix);
\r
10945 SendToICS("takeback 1\n");
\r
10954 ChessProgramState *cps;
\r
10956 switch (gameMode) {
\r
10957 case MachinePlaysWhite:
\r
10958 if (!WhiteOnMove(forwardMostMove)) {
\r
10959 DisplayError("It is your turn", 0);
\r
10964 case MachinePlaysBlack:
\r
10965 if (WhiteOnMove(forwardMostMove)) {
\r
10966 DisplayError("It is your turn", 0);
\r
10971 case TwoMachinesPlay:
\r
10972 if (WhiteOnMove(forwardMostMove) ==
\r
10973 (first.twoMachinesColor[0] == 'w')) {
\r
10979 case BeginningOfGame:
\r
10983 SendToProgram("?\n", cps);
\r
10987 TruncateGameEvent()
\r
10990 if (gameMode != EditGame) return;
\r
10997 if (forwardMostMove > currentMove) {
\r
10998 if (gameInfo.resultDetails != NULL) {
\r
10999 free(gameInfo.resultDetails);
\r
11000 gameInfo.resultDetails = NULL;
\r
11001 gameInfo.result = GameUnfinished;
\r
11003 forwardMostMove = currentMove;
\r
11004 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11012 if (appData.noChessProgram) return;
\r
11013 switch (gameMode) {
\r
11014 case MachinePlaysWhite:
\r
11015 if (WhiteOnMove(forwardMostMove)) {
\r
11016 DisplayError("Wait until your turn", 0);
\r
11020 case BeginningOfGame:
\r
11021 case MachinePlaysBlack:
\r
11022 if (!WhiteOnMove(forwardMostMove)) {
\r
11023 DisplayError("Wait until your turn", 0);
\r
11028 DisplayError("No hint available", 0);
\r
11031 SendToProgram("hint\n", &first);
\r
11032 hintRequested = TRUE;
\r
11038 if (appData.noChessProgram) return;
\r
11039 switch (gameMode) {
\r
11040 case MachinePlaysWhite:
\r
11041 if (WhiteOnMove(forwardMostMove)) {
\r
11042 DisplayError("Wait until your turn", 0);
\r
11046 case BeginningOfGame:
\r
11047 case MachinePlaysBlack:
\r
11048 if (!WhiteOnMove(forwardMostMove)) {
\r
11049 DisplayError("Wait until your turn", 0);
\r
11053 case EditPosition:
\r
11054 EditPositionDone();
\r
11056 case TwoMachinesPlay:
\r
11061 SendToProgram("bk\n", &first);
\r
11062 bookOutput[0] = NULLCHAR;
\r
11063 bookRequested = TRUE;
\r
11069 char *tags = PGNTags(&gameInfo);
\r
11070 TagsPopUp(tags, CmailMsg());
\r
11074 /* end button procedures */
\r
11077 PrintPosition(fp, move)
\r
11083 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11084 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11085 char c = PieceToChar(boards[move][i][j]);
\r
11086 fputc(c == 'x' ? '.' : c, fp);
\r
11087 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11090 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11091 fprintf(fp, "white to play\n");
\r
11093 fprintf(fp, "black to play\n");
\r
11097 PrintOpponents(fp)
\r
11100 if (gameInfo.white != NULL) {
\r
11101 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11103 fprintf(fp, "\n");
\r
11107 /* Find last component of program's own name, using some heuristics */
\r
11109 TidyProgramName(prog, host, buf)
\r
11110 char *prog, *host, buf[MSG_SIZ];
\r
11113 int local = (strcmp(host, "localhost") == 0);
\r
11114 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11116 while (*p == ' ') p++;
\r
11119 if (*prog == '"' || *prog == '\'') {
\r
11120 q = strchr(prog + 1, *prog);
\r
11122 q = strchr(prog, ' ');
\r
11124 if (q == NULL) q = prog + strlen(prog);
\r
11126 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11128 if(p == prog && *p == '"') p++;
\r
11129 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11130 memcpy(buf, p, q - p);
\r
11131 buf[q - p] = NULLCHAR;
\r
11133 strcat(buf, "@");
\r
11134 strcat(buf, host);
\r
11139 TimeControlTagValue()
\r
11141 char buf[MSG_SIZ];
\r
11142 if (!appData.clockMode) {
\r
11143 strcpy(buf, "-");
\r
11144 } else if (movesPerSession > 0) {
\r
11145 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11146 } else if (timeIncrement == 0) {
\r
11147 sprintf(buf, "%ld", timeControl/1000);
\r
11149 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11151 return StrSave(buf);
\r
11157 /* This routine is used only for certain modes */
\r
11158 VariantClass v = gameInfo.variant;
\r
11159 ClearGameInfo(&gameInfo);
\r
11160 gameInfo.variant = v;
\r
11162 switch (gameMode) {
\r
11163 case MachinePlaysWhite:
\r
11164 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11165 gameInfo.site = StrSave(HostName());
\r
11166 gameInfo.date = PGNDate();
\r
11167 gameInfo.round = StrSave("-");
\r
11168 gameInfo.white = StrSave(first.tidy);
\r
11169 gameInfo.black = StrSave(UserName());
\r
11170 gameInfo.timeControl = TimeControlTagValue();
\r
11173 case MachinePlaysBlack:
\r
11174 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11175 gameInfo.site = StrSave(HostName());
\r
11176 gameInfo.date = PGNDate();
\r
11177 gameInfo.round = StrSave("-");
\r
11178 gameInfo.white = StrSave(UserName());
\r
11179 gameInfo.black = StrSave(first.tidy);
\r
11180 gameInfo.timeControl = TimeControlTagValue();
\r
11183 case TwoMachinesPlay:
\r
11184 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11185 gameInfo.site = StrSave(HostName());
\r
11186 gameInfo.date = PGNDate();
\r
11187 if (matchGame > 0) {
\r
11188 char buf[MSG_SIZ];
\r
11189 sprintf(buf, "%d", matchGame);
\r
11190 gameInfo.round = StrSave(buf);
\r
11192 gameInfo.round = StrSave("-");
\r
11194 if (first.twoMachinesColor[0] == 'w') {
\r
11195 gameInfo.white = StrSave(first.tidy);
\r
11196 gameInfo.black = StrSave(second.tidy);
\r
11198 gameInfo.white = StrSave(second.tidy);
\r
11199 gameInfo.black = StrSave(first.tidy);
\r
11201 gameInfo.timeControl = TimeControlTagValue();
\r
11205 gameInfo.event = StrSave("Edited game");
\r
11206 gameInfo.site = StrSave(HostName());
\r
11207 gameInfo.date = PGNDate();
\r
11208 gameInfo.round = StrSave("-");
\r
11209 gameInfo.white = StrSave("-");
\r
11210 gameInfo.black = StrSave("-");
\r
11213 case EditPosition:
\r
11214 gameInfo.event = StrSave("Edited position");
\r
11215 gameInfo.site = StrSave(HostName());
\r
11216 gameInfo.date = PGNDate();
\r
11217 gameInfo.round = StrSave("-");
\r
11218 gameInfo.white = StrSave("-");
\r
11219 gameInfo.black = StrSave("-");
\r
11222 case IcsPlayingWhite:
\r
11223 case IcsPlayingBlack:
\r
11224 case IcsObserving:
\r
11225 case IcsExamining:
\r
11228 case PlayFromGameFile:
\r
11229 gameInfo.event = StrSave("Game from non-PGN file");
\r
11230 gameInfo.site = StrSave(HostName());
\r
11231 gameInfo.date = PGNDate();
\r
11232 gameInfo.round = StrSave("-");
\r
11233 gameInfo.white = StrSave("?");
\r
11234 gameInfo.black = StrSave("?");
\r
11243 ReplaceComment(index, text)
\r
11249 while (*text == '\n') text++;
\r
11250 len = strlen(text);
\r
11251 while (len > 0 && text[len - 1] == '\n') len--;
\r
11253 if (commentList[index] != NULL)
\r
11254 free(commentList[index]);
\r
11257 commentList[index] = NULL;
\r
11260 commentList[index] = (char *) malloc(len + 2);
\r
11261 strncpy(commentList[index], text, len);
\r
11262 commentList[index][len] = '\n';
\r
11263 commentList[index][len + 1] = NULLCHAR;
\r
11276 if (ch == '\r') continue;
\r
11278 } while (ch != '\0');
\r
11282 AppendComment(index, text)
\r
11289 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11292 while (*text == '\n') text++;
\r
11293 len = strlen(text);
\r
11294 while (len > 0 && text[len - 1] == '\n') len--;
\r
11296 if (len == 0) return;
\r
11298 if (commentList[index] != NULL) {
\r
11299 old = commentList[index];
\r
11300 oldlen = strlen(old);
\r
11301 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11302 strcpy(commentList[index], old);
\r
11304 strncpy(&commentList[index][oldlen], text, len);
\r
11305 commentList[index][oldlen + len] = '\n';
\r
11306 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11308 commentList[index] = (char *) malloc(len + 2);
\r
11309 strncpy(commentList[index], text, len);
\r
11310 commentList[index][len] = '\n';
\r
11311 commentList[index][len + 1] = NULLCHAR;
\r
11315 static char * FindStr( char * text, char * sub_text )
\r
11317 char * result = strstr( text, sub_text );
\r
11319 if( result != NULL ) {
\r
11320 result += strlen( sub_text );
\r
11326 /* [AS] Try to extract PV info from PGN comment */
\r
11327 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11328 char *GetInfoFromComment( int index, char * text )
\r
11330 char * sep = text;
\r
11332 if( text != NULL && index > 0 ) {
\r
11335 int time = -1, sec = 0;
\r
11336 char * s_eval = FindStr( text, "[%eval " );
\r
11337 char * s_emt = FindStr( text, "[%emt " );
\r
11339 if( s_eval != NULL || s_emt != NULL ) {
\r
11343 if( s_eval != NULL ) {
\r
11344 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11348 if( delim != ']' ) {
\r
11353 if( s_emt != NULL ) {
\r
11357 /* We expect something like: [+|-]nnn.nn/dd */
\r
11358 int score_lo = 0;
\r
11360 sep = strchr( text, '/' );
\r
11361 if( sep == NULL || sep < (text+4) ) {
\r
11365 time = -1; sec = -1;
\r
11366 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11367 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11368 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11372 if( score_lo < 0 || score_lo >= 100 ) {
\r
11376 if(sec >= 0) time = 60*time + sec;
\r
11377 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11379 /* [HGM] PV time: now locate end of PV info */
\r
11380 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11382 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11384 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11385 while(*sep == ' ') sep++;
\r
11388 if( depth <= 0 ) {
\r
11396 pvInfoList[index-1].depth = depth;
\r
11397 pvInfoList[index-1].score = score;
\r
11398 pvInfoList[index-1].time = time;
\r
11404 SendToProgram(message, cps)
\r
11406 ChessProgramState *cps;
\r
11408 int count, outCount, error;
\r
11409 char buf[MSG_SIZ];
\r
11411 if (cps->pr == NULL) return;
\r
11414 if (appData.debugMode) {
\r
11416 GetTimeMark(&now);
\r
11417 fprintf(debugFP, "%ld >%-6s: %s",
\r
11418 SubtractTimeMarks(&now, &programStartTime),
\r
11419 cps->which, message);
\r
11422 count = strlen(message);
\r
11423 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
11424 if (outCount < count && !exiting
\r
11425 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
11426 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
11427 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11428 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11429 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11430 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11432 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11434 gameInfo.resultDetails = buf;
\r
11436 DisplayFatalError(buf, error, 1);
\r
11441 ReceiveFromProgram(isr, closure, message, count, error)
\r
11442 InputSourceRef isr;
\r
11443 VOIDSTAR closure;
\r
11449 char buf[MSG_SIZ];
\r
11450 ChessProgramState *cps = (ChessProgramState *)closure;
\r
11452 if (isr != cps->isr) return; /* Killed intentionally */
\r
11453 if (count <= 0) {
\r
11454 if (count == 0) {
\r
11456 "Error: %s chess program (%s) exited unexpectedly",
\r
11457 cps->which, cps->program);
\r
11458 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11459 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11460 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11461 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11463 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11465 gameInfo.resultDetails = buf;
\r
11467 RemoveInputSource(cps->isr);
\r
11468 DisplayFatalError(buf, 0, 1);
\r
11471 "Error reading from %s chess program (%s)",
\r
11472 cps->which, cps->program);
\r
11473 RemoveInputSource(cps->isr);
\r
11475 /* [AS] Program is misbehaving badly... kill it */
\r
11476 if( count == -2 ) {
\r
11477 DestroyChildProcess( cps->pr, 9 );
\r
11478 cps->pr = NoProc;
\r
11481 DisplayFatalError(buf, error, 1);
\r
11486 if ((end_str = strchr(message, '\r')) != NULL)
\r
11487 *end_str = NULLCHAR;
\r
11488 if ((end_str = strchr(message, '\n')) != NULL)
\r
11489 *end_str = NULLCHAR;
\r
11491 if (appData.debugMode) {
\r
11492 TimeMark now; int print = 1;
\r
11493 char *quote = ""; char c; int i;
\r
11495 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
11496 char start = message[0];
\r
11497 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
11498 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
11499 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
11500 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
11501 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
11502 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
11503 sscanf(message, "askuser%c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 &&
\r
11504 sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
11505 { quote = "# "; print = (appData.engineComments == 2); }
\r
11506 message[0] = start; // restore original message
\r
11509 GetTimeMark(&now);
\r
11510 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
11511 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
11516 HandleMachineMove(message, cps);
\r
11521 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
11522 ChessProgramState *cps;
\r
11523 int mps, inc, sd, st;
\r
11526 char buf[MSG_SIZ];
\r
11529 if( timeControl_2 > 0 ) {
\r
11530 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
11531 tc = timeControl_2;
\r
11534 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
11535 inc /= cps->timeOdds;
\r
11536 st /= cps->timeOdds;
\r
11538 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
11541 /* Set exact time per move, normally using st command */
\r
11542 if (cps->stKludge) {
\r
11543 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
11544 seconds = st % 60;
\r
11545 if (seconds == 0) {
\r
11546 sprintf(buf, "level 1 %d\n", st/60);
\r
11548 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
11551 sprintf(buf, "st %d\n", st);
\r
11554 /* Set conventional or incremental time control, using level command */
\r
11555 if (seconds == 0) {
\r
11556 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
11557 Fixed in later versions, but still avoid :seconds
\r
11558 when seconds is 0. */
\r
11559 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
11561 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
11562 seconds, inc/1000);
\r
11565 SendToProgram(buf, cps);
\r
11567 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
11568 /* Orthogonally, limit search to given depth */
\r
11570 if (cps->sdKludge) {
\r
11571 sprintf(buf, "depth\n%d\n", sd);
\r
11573 sprintf(buf, "sd %d\n", sd);
\r
11575 SendToProgram(buf, cps);
\r
11578 if(cps->nps > 0) { /* [HGM] nps */
\r
11579 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
11581 sprintf(buf, "nps %d\n", cps->nps);
\r
11582 SendToProgram(buf, cps);
\r
11587 ChessProgramState *WhitePlayer()
\r
11588 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
11590 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
\r
11596 SendTimeRemaining(cps, machineWhite)
\r
11597 ChessProgramState *cps;
\r
11598 int /*boolean*/ machineWhite;
\r
11600 char message[MSG_SIZ];
\r
11601 long time, otime;
\r
11603 /* Note: this routine must be called when the clocks are stopped
\r
11604 or when they have *just* been set or switched; otherwise
\r
11605 it will be off by the time since the current tick started.
\r
11607 if (machineWhite) {
\r
11608 time = whiteTimeRemaining / 10;
\r
11609 otime = blackTimeRemaining / 10;
\r
11611 time = blackTimeRemaining / 10;
\r
11612 otime = whiteTimeRemaining / 10;
\r
11614 /* [HGM] translate opponent's time by time-odds factor */
\r
11615 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
11616 if (appData.debugMode) {
\r
11617 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
11620 if (time <= 0) time = 1;
\r
11621 if (otime <= 0) otime = 1;
\r
11623 sprintf(message, "time %ld\n", time);
\r
11624 SendToProgram(message, cps);
\r
11626 sprintf(message, "otim %ld\n", otime);
\r
11627 SendToProgram(message, cps);
\r
11631 BoolFeature(p, name, loc, cps)
\r
11635 ChessProgramState *cps;
\r
11637 char buf[MSG_SIZ];
\r
11638 int len = strlen(name);
\r
11640 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
11642 sscanf(*p, "%d", &val);
\r
11643 *loc = (val != 0);
\r
11644 while (**p && **p != ' ') (*p)++;
\r
11645 sprintf(buf, "accepted %s\n", name);
\r
11646 SendToProgram(buf, cps);
\r
11653 IntFeature(p, name, loc, cps)
\r
11657 ChessProgramState *cps;
\r
11659 char buf[MSG_SIZ];
\r
11660 int len = strlen(name);
\r
11661 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
11663 sscanf(*p, "%d", loc);
\r
11664 while (**p && **p != ' ') (*p)++;
\r
11665 sprintf(buf, "accepted %s\n", name);
\r
11666 SendToProgram(buf, cps);
\r
11673 StringFeature(p, name, loc, cps)
\r
11677 ChessProgramState *cps;
\r
11679 char buf[MSG_SIZ];
\r
11680 int len = strlen(name);
\r
11681 if (strncmp((*p), name, len) == 0
\r
11682 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
11684 sscanf(*p, "%[^\"]", loc);
\r
11685 while (**p && **p != '\"') (*p)++;
\r
11686 if (**p == '\"') (*p)++;
\r
11687 sprintf(buf, "accepted %s\n", name);
\r
11688 SendToProgram(buf, cps);
\r
11695 FeatureDone(cps, val)
\r
11696 ChessProgramState* cps;
\r
11699 DelayedEventCallback cb = GetDelayedEvent();
\r
11700 if ((cb == InitBackEnd3 && cps == &first) ||
\r
11701 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
11702 CancelDelayedEvent();
\r
11703 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
11705 cps->initDone = val;
\r
11708 /* Parse feature command from engine */
\r
11710 ParseFeatures(args, cps)
\r
11712 ChessProgramState *cps;
\r
11717 char buf[MSG_SIZ];
\r
11720 while (*p == ' ') p++;
\r
11721 if (*p == NULLCHAR) return;
\r
11723 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
11724 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
11725 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
11726 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
11727 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
11728 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
11729 /* Engine can disable reuse, but can't enable it if user said no */
\r
11730 if (!val) cps->reuse = FALSE;
\r
11733 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
11734 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
11735 if (gameMode == TwoMachinesPlay) {
\r
11736 DisplayTwoMachinesTitle();
\r
11738 DisplayTitle("");
\r
11742 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
11743 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
11744 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
11745 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
11746 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
11747 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
11748 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
11749 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
11750 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
11751 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
11752 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
11753 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
11754 if (IntFeature(&p, "done", &val, cps)) {
\r
11755 FeatureDone(cps, val);
\r
11758 /* Added by Tord: */
\r
11759 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
11760 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
11761 /* End of additions by Tord */
\r
11763 /* unknown feature: complain and skip */
\r
11765 while (*q && *q != '=') q++;
\r
11766 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
11767 SendToProgram(buf, cps);
\r
11771 if (*p == '\"') {
\r
11773 while (*p && *p != '\"') p++;
\r
11774 if (*p == '\"') p++;
\r
11776 while (*p && *p != ' ') p++;
\r
11784 PeriodicUpdatesEvent(newState)
\r
11787 if (newState == appData.periodicUpdates)
\r
11790 appData.periodicUpdates=newState;
\r
11792 /* Display type changes, so update it now */
\r
11793 DisplayAnalysis();
\r
11795 /* Get the ball rolling again... */
\r
11797 AnalysisPeriodicEvent(1);
\r
11798 StartAnalysisClock();
\r
11803 PonderNextMoveEvent(newState)
\r
11806 if (newState == appData.ponderNextMove) return;
\r
11807 if (gameMode == EditPosition) EditPositionDone();
\r
11809 SendToProgram("hard\n", &first);
\r
11810 if (gameMode == TwoMachinesPlay) {
\r
11811 SendToProgram("hard\n", &second);
\r
11814 SendToProgram("easy\n", &first);
\r
11815 thinkOutput[0] = NULLCHAR;
\r
11816 if (gameMode == TwoMachinesPlay) {
\r
11817 SendToProgram("easy\n", &second);
\r
11820 appData.ponderNextMove = newState;
\r
11824 ShowThinkingEvent(newState)
\r
11827 if (newState == appData.showThinking) return;
\r
11828 if (gameMode == EditPosition) EditPositionDone();
\r
11830 SendToProgram("post\n", &first);
\r
11831 if (gameMode == TwoMachinesPlay) {
\r
11832 SendToProgram("post\n", &second);
\r
11835 SendToProgram("nopost\n", &first);
\r
11836 thinkOutput[0] = NULLCHAR;
\r
11837 if (gameMode == TwoMachinesPlay) {
\r
11838 SendToProgram("nopost\n", &second);
\r
11841 appData.showThinking = newState;
\r
11845 AskQuestionEvent(title, question, replyPrefix, which)
\r
11846 char *title; char *question; char *replyPrefix; char *which;
\r
11848 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
11849 if (pr == NoProc) return;
\r
11850 AskQuestion(title, question, replyPrefix, pr);
\r
11854 DisplayMove(moveNumber)
\r
11857 char message[MSG_SIZ];
\r
11858 char res[MSG_SIZ];
\r
11859 char cpThinkOutput[MSG_SIZ];
\r
11861 if (moveNumber == forwardMostMove - 1 ||
\r
11862 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11864 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
11866 if (strchr(cpThinkOutput, '\n')) {
\r
11867 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
11870 *cpThinkOutput = NULLCHAR;
\r
11873 /* [AS] Hide thinking from human user */
\r
11874 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
11875 *cpThinkOutput = NULLCHAR;
\r
11876 if( thinkOutput[0] != NULLCHAR ) {
\r
11879 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
11880 cpThinkOutput[i] = '.';
\r
11882 cpThinkOutput[i] = NULLCHAR;
\r
11883 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
11887 if (moveNumber == forwardMostMove - 1 &&
\r
11888 gameInfo.resultDetails != NULL) {
\r
11889 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
11890 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
11892 sprintf(res, " {%s} %s",
\r
11893 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
11896 res[0] = NULLCHAR;
\r
11899 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11900 DisplayMessage(res, cpThinkOutput);
\r
11902 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
11903 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11904 parseList[moveNumber], res);
\r
11905 DisplayMessage(message, cpThinkOutput);
\r
11910 DisplayAnalysisText(text)
\r
11913 char buf[MSG_SIZ];
\r
11915 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11916 sprintf(buf, "Analysis (%s)", first.tidy);
\r
11917 AnalysisPopUp(buf, text);
\r
11922 only_one_move(str)
\r
11925 while (*str && isspace(*str)) ++str;
\r
11926 while (*str && !isspace(*str)) ++str;
\r
11927 if (!*str) return 1;
\r
11928 while (*str && isspace(*str)) ++str;
\r
11929 if (!*str) return 1;
\r
11934 DisplayAnalysis()
\r
11936 char buf[MSG_SIZ];
\r
11937 char lst[MSG_SIZ / 2];
\r
11939 static char *xtra[] = { "", " (--)", " (++)" };
\r
11942 if (programStats.time == 0) {
\r
11943 programStats.time = 1;
\r
11946 if (programStats.got_only_move) {
\r
11947 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
11949 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
11951 nps = (((double)programStats.nodes) /
\r
11952 (((double)programStats.time)/100.0));
\r
11954 cs = programStats.time % 100;
\r
11955 s = programStats.time / 100;
\r
11956 h = (s / (60*60));
\r
11961 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
11962 if (programStats.move_name[0] != NULLCHAR) {
\r
11963 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11964 programStats.depth,
\r
11965 programStats.nr_moves-programStats.moves_left,
\r
11966 programStats.nr_moves, programStats.move_name,
\r
11967 ((float)programStats.score)/100.0, lst,
\r
11968 only_one_move(lst)?
\r
11969 xtra[programStats.got_fail] : "",
\r
11970 programStats.nodes, (int)nps, h, m, s, cs);
\r
11972 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11973 programStats.depth,
\r
11974 programStats.nr_moves-programStats.moves_left,
\r
11975 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
11977 only_one_move(lst)?
\r
11978 xtra[programStats.got_fail] : "",
\r
11979 programStats.nodes, (int)nps, h, m, s, cs);
\r
11982 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11983 programStats.depth,
\r
11984 ((float)programStats.score)/100.0,
\r
11986 only_one_move(lst)?
\r
11987 xtra[programStats.got_fail] : "",
\r
11988 programStats.nodes, (int)nps, h, m, s, cs);
\r
11991 DisplayAnalysisText(buf);
\r
11995 DisplayComment(moveNumber, text)
\r
11999 char title[MSG_SIZ];
\r
12000 char buf[8000]; // comment can be long!
\r
12001 int score, depth;
\r
12003 if( appData.autoDisplayComment ) {
\r
12004 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12005 strcpy(title, "Comment");
\r
12007 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12008 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12009 parseList[moveNumber]);
\r
12011 } else title[0] = 0;
\r
12013 // [HGM] PV info: display PV info together with (or as) comment
\r
12014 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12015 if(text == NULL) text = "";
\r
12016 score = pvInfoList[moveNumber].score;
\r
12017 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12018 depth, pvInfoList[moveNumber].time, text);
\r
12019 CommentPopUp(title, buf);
\r
12021 if (text != NULL)
\r
12022 CommentPopUp(title, text);
\r
12025 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12026 * might be busy thinking or pondering. It can be omitted if your
\r
12027 * gnuchess is configured to stop thinking immediately on any user
\r
12028 * input. However, that gnuchess feature depends on the FIONREAD
\r
12029 * ioctl, which does not work properly on some flavors of Unix.
\r
12033 ChessProgramState *cps;
\r
12036 if (!cps->useSigint) return;
\r
12037 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12038 switch (gameMode) {
\r
12039 case MachinePlaysWhite:
\r
12040 case MachinePlaysBlack:
\r
12041 case TwoMachinesPlay:
\r
12042 case IcsPlayingWhite:
\r
12043 case IcsPlayingBlack:
\r
12044 case AnalyzeMode:
\r
12045 case AnalyzeFile:
\r
12046 /* Skip if we know it isn't thinking */
\r
12047 if (!cps->maybeThinking) return;
\r
12048 if (appData.debugMode)
\r
12049 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12050 InterruptChildProcess(cps->pr);
\r
12051 cps->maybeThinking = FALSE;
\r
12056 #endif /*ATTENTION*/
\r
12062 if (whiteTimeRemaining <= 0) {
\r
12063 if (!whiteFlag) {
\r
12064 whiteFlag = TRUE;
\r
12065 if (appData.icsActive) {
\r
12066 if (appData.autoCallFlag &&
\r
12067 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12068 SendToICS(ics_prefix);
\r
12069 SendToICS("flag\n");
\r
12073 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
12075 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
12076 if (appData.autoCallFlag) {
\r
12077 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12084 if (blackTimeRemaining <= 0) {
\r
12085 if (!blackFlag) {
\r
12086 blackFlag = TRUE;
\r
12087 if (appData.icsActive) {
\r
12088 if (appData.autoCallFlag &&
\r
12089 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12090 SendToICS(ics_prefix);
\r
12091 SendToICS("flag\n");
\r
12095 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
12097 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
12098 if (appData.autoCallFlag) {
\r
12099 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12110 CheckTimeControl()
\r
12112 if (!appData.clockMode || appData.icsActive ||
\r
12113 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12116 * add time to clocks when time control is achieved ([HGM] now also used fot increment)
\r
12118 if ( !WhiteOnMove(forwardMostMove) )
\r
12119 /* White made time control */
\r
12120 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12121 /* [HGM] time odds: correct new time quota for time odds! */
\r
12122 / WhitePlayer()->timeOdds;
\r
12124 /* Black made time control */
\r
12125 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12126 / WhitePlayer()->other->timeOdds;
\r
12130 DisplayBothClocks()
\r
12132 int wom = gameMode == EditPosition ?
\r
12133 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12134 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12135 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12139 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12140 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12141 to use other calls if you don't. Clocks will be less accurate if
\r
12142 you have neither ftime nor gettimeofday.
\r
12145 /* Get the current time as a TimeMark */
\r
12150 #if HAVE_GETTIMEOFDAY
\r
12152 struct timeval timeVal;
\r
12153 struct timezone timeZone;
\r
12155 gettimeofday(&timeVal, &timeZone);
\r
12156 tm->sec = (long) timeVal.tv_sec;
\r
12157 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12159 #else /*!HAVE_GETTIMEOFDAY*/
\r
12162 #include <sys/timeb.h>
\r
12163 struct timeb timeB;
\r
12166 tm->sec = (long) timeB.time;
\r
12167 tm->ms = (int) timeB.millitm;
\r
12169 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12170 tm->sec = (long) time(NULL);
\r
12176 /* Return the difference in milliseconds between two
\r
12177 time marks. We assume the difference will fit in a long!
\r
12180 SubtractTimeMarks(tm2, tm1)
\r
12181 TimeMark *tm2, *tm1;
\r
12183 return 1000L*(tm2->sec - tm1->sec) +
\r
12184 (long) (tm2->ms - tm1->ms);
\r
12189 * Code to manage the game clocks.
\r
12191 * In tournament play, black starts the clock and then white makes a move.
\r
12192 * We give the human user a slight advantage if he is playing white---the
\r
12193 * clocks don't run until he makes his first move, so it takes zero time.
\r
12194 * Also, we don't account for network lag, so we could get out of sync
\r
12195 * with GNU Chess's clock -- but then, referees are always right.
\r
12198 static TimeMark tickStartTM;
\r
12199 static long intendedTickLength;
\r
12202 NextTickLength(timeRemaining)
\r
12203 long timeRemaining;
\r
12205 long nominalTickLength, nextTickLength;
\r
12207 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12208 nominalTickLength = 100L;
\r
12210 nominalTickLength = 1000L;
\r
12211 nextTickLength = timeRemaining % nominalTickLength;
\r
12212 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12214 return nextTickLength;
\r
12217 /* Adjust clock one minute up or down */
\r
12219 AdjustClock(Boolean which, int dir)
\r
12221 if(which) blackTimeRemaining += 60000*dir;
\r
12222 else whiteTimeRemaining += 60000*dir;
\r
12223 DisplayBothClocks();
\r
12226 /* Stop clocks and reset to a fresh time control */
\r
12230 (void) StopClockTimer();
\r
12231 if (appData.icsActive) {
\r
12232 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12233 } else { /* [HGM] correct new time quote for time odds */
\r
12234 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12235 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12237 if (whiteFlag || blackFlag) {
\r
12238 DisplayTitle("");
\r
12239 whiteFlag = blackFlag = FALSE;
\r
12241 DisplayBothClocks();
\r
12244 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12246 /* Decrement running clock by amount of time that has passed */
\r
12248 DecrementClocks()
\r
12250 long timeRemaining;
\r
12251 long lastTickLength, fudge;
\r
12254 if (!appData.clockMode) return;
\r
12255 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12257 GetTimeMark(&now);
\r
12259 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12261 /* Fudge if we woke up a little too soon */
\r
12262 fudge = intendedTickLength - lastTickLength;
\r
12263 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12265 if (WhiteOnMove(forwardMostMove)) {
\r
12266 if(whiteNPS >= 0) lastTickLength = 0;
\r
12267 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12268 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12269 WhiteOnMove(currentMove));
\r
12271 if(blackNPS >= 0) lastTickLength = 0;
\r
12272 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
12273 DisplayBlackClock(blackTimeRemaining - fudge,
\r
12274 !WhiteOnMove(currentMove));
\r
12277 if (CheckFlags()) return;
\r
12279 tickStartTM = now;
\r
12280 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
12281 StartClockTimer(intendedTickLength);
\r
12283 /* if the time remaining has fallen below the alarm threshold, sound the
\r
12284 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
12285 * with increment) the time remaining has increased to a level above the
\r
12286 * threshold, reset the alarm so it can sound again.
\r
12289 if (appData.icsActive && appData.icsAlarm) {
\r
12291 /* make sure we are dealing with the user's clock */
\r
12292 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
12293 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
12296 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
12297 alarmSounded = FALSE;
\r
12298 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
12299 PlayAlarmSound();
\r
12300 alarmSounded = TRUE;
\r
12306 /* A player has just moved, so stop the previously running
\r
12307 clock and (if in clock mode) start the other one.
\r
12308 We redisplay both clocks in case we're in ICS mode, because
\r
12309 ICS gives us an update to both clocks after every move.
\r
12310 Note that this routine is called *after* forwardMostMove
\r
12311 is updated, so the last fractional tick must be subtracted
\r
12312 from the color that is *not* on move now.
\r
12317 long lastTickLength;
\r
12319 int flagged = FALSE;
\r
12321 GetTimeMark(&now);
\r
12323 if (StopClockTimer() && appData.clockMode) {
\r
12324 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12325 if (WhiteOnMove(forwardMostMove)) {
\r
12326 if(blackNPS >= 0) lastTickLength = 0;
\r
12327 blackTimeRemaining -= lastTickLength;
\r
12328 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12329 if(pvInfoList[forwardMostMove-1].time == -1)
\r
12330 pvInfoList[forwardMostMove-1].time =
\r
12331 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
12333 if(whiteNPS >= 0) lastTickLength = 0;
\r
12334 whiteTimeRemaining -= lastTickLength;
\r
12335 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12336 if(pvInfoList[forwardMostMove-1].time == -1)
\r
12337 pvInfoList[forwardMostMove-1].time =
\r
12338 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
12340 flagged = CheckFlags();
\r
12342 CheckTimeControl();
\r
12344 if (flagged || !appData.clockMode) return;
\r
12346 switch (gameMode) {
\r
12347 case MachinePlaysBlack:
\r
12348 case MachinePlaysWhite:
\r
12349 case BeginningOfGame:
\r
12350 if (pausing) return;
\r
12354 case PlayFromGameFile:
\r
12355 case IcsExamining:
\r
12362 tickStartTM = now;
\r
12363 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12364 whiteTimeRemaining : blackTimeRemaining);
\r
12365 StartClockTimer(intendedTickLength);
\r
12369 /* Stop both clocks */
\r
12373 long lastTickLength;
\r
12376 if (!StopClockTimer()) return;
\r
12377 if (!appData.clockMode) return;
\r
12379 GetTimeMark(&now);
\r
12381 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12382 if (WhiteOnMove(forwardMostMove)) {
\r
12383 if(whiteNPS >= 0) lastTickLength = 0;
\r
12384 whiteTimeRemaining -= lastTickLength;
\r
12385 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
12387 if(blackNPS >= 0) lastTickLength = 0;
\r
12388 blackTimeRemaining -= lastTickLength;
\r
12389 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
12394 /* Start clock of player on move. Time may have been reset, so
\r
12395 if clock is already running, stop and restart it. */
\r
12399 (void) StopClockTimer(); /* in case it was running already */
\r
12400 DisplayBothClocks();
\r
12401 if (CheckFlags()) return;
\r
12403 if (!appData.clockMode) return;
\r
12404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12406 GetTimeMark(&tickStartTM);
\r
12407 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12408 whiteTimeRemaining : blackTimeRemaining);
\r
12410 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
12411 whiteNPS = blackNPS = -1;
\r
12412 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
12413 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
12414 whiteNPS = first.nps;
\r
12415 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
12416 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
12417 blackNPS = first.nps;
\r
12418 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
12419 whiteNPS = second.nps;
\r
12420 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
12421 blackNPS = second.nps;
\r
12422 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
12424 StartClockTimer(intendedTickLength);
\r
12431 long second, minute, hour, day;
\r
12433 static char buf[32];
\r
12435 if (ms > 0 && ms <= 9900) {
\r
12436 /* convert milliseconds to tenths, rounding up */
\r
12437 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
12439 sprintf(buf, " %03.1f ", tenths/10.0);
\r
12443 /* convert milliseconds to seconds, rounding up */
\r
12444 /* use floating point to avoid strangeness of integer division
\r
12445 with negative dividends on many machines */
\r
12446 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
12448 if (second < 0) {
\r
12450 second = -second;
\r
12453 day = second / (60 * 60 * 24);
\r
12454 second = second % (60 * 60 * 24);
\r
12455 hour = second / (60 * 60);
\r
12456 second = second % (60 * 60);
\r
12457 minute = second / 60;
\r
12458 second = second % 60;
\r
12461 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
12462 sign, day, hour, minute, second);
\r
12463 else if (hour > 0)
\r
12464 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
12466 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
12473 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
12476 StrStr(string, match)
\r
12477 char *string, *match;
\r
12481 length = strlen(match);
\r
12483 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
12484 if (!strncmp(match, string, length))
\r
12491 StrCaseStr(string, match)
\r
12492 char *string, *match;
\r
12494 int i, j, length;
\r
12496 length = strlen(match);
\r
12498 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
12499 for (j = 0; j < length; j++) {
\r
12500 if (ToLower(match[j]) != ToLower(string[j]))
\r
12503 if (j == length) return string;
\r
12509 #ifndef _amigados
\r
12511 StrCaseCmp(s1, s2)
\r
12517 c1 = ToLower(*s1++);
\r
12518 c2 = ToLower(*s2++);
\r
12519 if (c1 > c2) return 1;
\r
12520 if (c1 < c2) return -1;
\r
12521 if (c1 == NULLCHAR) return 0;
\r
12530 return isupper(c) ? tolower(c) : c;
\r
12538 return islower(c) ? toupper(c) : c;
\r
12540 #endif /* !_amigados */
\r
12548 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
12555 StrSavePtr(s, savePtr)
\r
12556 char *s, **savePtr;
\r
12561 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
12562 strcpy(*savePtr, s);
\r
12564 return(*savePtr);
\r
12572 char buf[MSG_SIZ];
\r
12574 clock = time((time_t *)NULL);
\r
12575 tm = localtime(&clock);
\r
12576 sprintf(buf, "%04d.%02d.%02d",
\r
12577 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
12578 return StrSave(buf);
\r
12583 PositionToFEN(move, useFEN960)
\r
12587 int i, j, fromX, fromY, toX, toY;
\r
12592 ChessSquare piece;
\r
12594 whiteToPlay = (gameMode == EditPosition) ?
\r
12595 !blackPlaysFirst : (move % 2 == 0);
\r
12598 /* Piece placement data */
\r
12599 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
12601 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
12602 if (boards[move][i][j] == EmptySquare) {
\r
12604 } else { ChessSquare piece = boards[move][i][j];
\r
12605 if (emptycount > 0) {
\r
12606 if(emptycount<10) /* [HGM] can be >= 10 */
\r
12607 *p++ = '0' + emptycount;
\r
12608 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
12611 if(PieceToChar(piece) == '+') {
\r
12612 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
12614 piece = (ChessSquare)(DEMOTED piece);
\r
12616 *p++ = PieceToChar(piece);
\r
12617 if(p[-1] == '~') {
\r
12618 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
12619 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
12624 if (emptycount > 0) {
\r
12625 if(emptycount<10) /* [HGM] can be >= 10 */
\r
12626 *p++ = '0' + emptycount;
\r
12627 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
12634 /* [HGM] print Crazyhouse or Shogi holdings */
\r
12635 if( gameInfo.holdingsWidth ) {
\r
12636 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
12638 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
12639 piece = boards[move][i][BOARD_WIDTH-1];
\r
12640 if( piece != EmptySquare )
\r
12641 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
12642 *p++ = PieceToChar(piece);
\r
12644 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
12645 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
12646 if( piece != EmptySquare )
\r
12647 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
12648 *p++ = PieceToChar(piece);
\r
12651 if( q == p ) *p++ = '-';
\r
12656 /* Active color */
\r
12657 *p++ = whiteToPlay ? 'w' : 'b';
\r
12660 if(nrCastlingRights) {
\r
12662 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
12663 /* [HGM] write directly from rights */
\r
12664 if(castlingRights[move][2] >= 0 &&
\r
12665 castlingRights[move][0] >= 0 )
\r
12666 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
12667 if(castlingRights[move][2] >= 0 &&
\r
12668 castlingRights[move][1] >= 0 )
\r
12669 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
12670 if(castlingRights[move][5] >= 0 &&
\r
12671 castlingRights[move][3] >= 0 )
\r
12672 *p++ = castlingRights[move][3] + AAA;
\r
12673 if(castlingRights[move][5] >= 0 &&
\r
12674 castlingRights[move][4] >= 0 )
\r
12675 *p++ = castlingRights[move][4] + AAA;
\r
12678 /* [HGM] write true castling rights */
\r
12679 if( nrCastlingRights == 6 ) {
\r
12680 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
12681 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
12682 if(castlingRights[move][1] == BOARD_LEFT &&
\r
12683 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
12684 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
12685 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
12686 if(castlingRights[move][4] == BOARD_LEFT &&
\r
12687 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
12690 if (q == p) *p++ = '-'; /* No castling rights */
\r
12694 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
12695 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
12696 /* En passant target square */
\r
12697 if (move > backwardMostMove) {
\r
12698 fromX = moveList[move - 1][0] - AAA;
\r
12699 fromY = moveList[move - 1][1] - ONE;
\r
12700 toX = moveList[move - 1][2] - AAA;
\r
12701 toY = moveList[move - 1][3] - ONE;
\r
12702 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
12703 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
12704 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
12706 /* 2-square pawn move just happened */
\r
12707 *p++ = toX + AAA;
\r
12708 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
12718 /* [HGM] find reversible plies */
\r
12719 { int i = 0, j=move;
\r
12721 if (appData.debugMode) { int k;
\r
12722 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
12723 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
12724 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
12728 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
12729 if( j == backwardMostMove ) i += initialRulePlies;
\r
12730 sprintf(p, "%d ", i);
\r
12731 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
12733 /* Fullmove number */
\r
12734 sprintf(p, "%d", (move / 2) + 1);
\r
12736 return StrSave(buf);
\r
12740 ParseFEN(board, blackPlaysFirst, fen)
\r
12742 int *blackPlaysFirst;
\r
12748 ChessSquare piece;
\r
12752 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
12753 if(gameInfo.holdingsWidth) {
\r
12754 for(i=0; i<BOARD_HEIGHT; i++) {
\r
12755 board[i][0] = EmptySquare; /* black holdings */
\r
12756 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
12757 board[i][1] = (ChessSquare) 0; /* black counts */
\r
12758 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
12762 /* Piece placement data */
\r
12763 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
12766 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
12767 if (*p == '/') p++;
\r
12768 emptycount = gameInfo.boardWidth - j;
\r
12769 while (emptycount--)
\r
12770 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12772 #if(BOARD_SIZE >= 10)
\r
12773 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
12774 p++; emptycount=10;
\r
12775 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12776 while (emptycount--)
\r
12777 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12779 } else if (isdigit(*p)) {
\r
12780 emptycount = *p++ - '0';
\r
12781 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
12782 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12783 while (emptycount--)
\r
12784 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12785 } else if (*p == '+' || isalpha(*p)) {
\r
12786 if (j >= gameInfo.boardWidth) return FALSE;
\r
12788 piece = CharToPiece(*++p);
\r
12789 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
12790 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
12791 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
12792 } else piece = CharToPiece(*p++);
\r
12794 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
12795 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
12796 piece = (ChessSquare) (PROMOTED piece);
\r
12797 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
12800 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
12806 while (*p == '/' || *p == ' ') p++;
\r
12808 /* [HGM] look for Crazyhouse holdings here */
\r
12809 while(*p==' ') p++;
\r
12810 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
12811 if(*p == '[') p++;
\r
12812 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
12813 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
12814 /* if we would allow FEN reading to set board size, we would */
\r
12815 /* have to add holdings and shift the board read so far here */
\r
12816 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
12818 if((int) piece >= (int) BlackPawn ) {
\r
12819 i = (int)piece - (int)BlackPawn;
\r
12820 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12821 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
12822 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
12824 i = (int)piece - (int)WhitePawn;
\r
12825 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12826 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
12827 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
12831 if(*p == ']') *p++;
\r
12834 while(*p == ' ') p++;
\r
12836 /* Active color */
\r
12839 *blackPlaysFirst = FALSE;
\r
12842 *blackPlaysFirst = TRUE;
\r
12848 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
12849 /* return the extra info in global variiables */
\r
12851 /* set defaults in case FEN is incomplete */
\r
12852 FENepStatus = EP_UNKNOWN;
\r
12853 for(i=0; i<nrCastlingRights; i++ ) {
\r
12854 FENcastlingRights[i] =
\r
12855 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
12856 } /* assume possible unless obviously impossible */
\r
12857 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
12858 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
12859 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
12860 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
12861 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
12862 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
12863 FENrulePlies = 0;
\r
12865 while(*p==' ') p++;
\r
12866 if(nrCastlingRights) {
\r
12867 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
12868 /* castling indicator present, so default becomes no castlings */
\r
12869 for(i=0; i<nrCastlingRights; i++ ) {
\r
12870 FENcastlingRights[i] = -1;
\r
12873 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
12874 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
12875 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
12876 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
12877 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
12879 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
12880 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
12881 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
12885 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
12886 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
12887 FENcastlingRights[2] = whiteKingFile;
\r
12890 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
12891 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
12892 FENcastlingRights[2] = whiteKingFile;
\r
12895 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
12896 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
12897 FENcastlingRights[5] = blackKingFile;
\r
12900 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
12901 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
12902 FENcastlingRights[5] = blackKingFile;
\r
12905 default: /* FRC castlings */
\r
12906 if(c >= 'a') { /* black rights */
\r
12907 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
12908 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
12909 if(i == BOARD_RGHT) break;
\r
12910 FENcastlingRights[5] = i;
\r
12912 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
12913 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
12915 FENcastlingRights[3] = c;
\r
12917 FENcastlingRights[4] = c;
\r
12918 } else { /* white rights */
\r
12919 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
12920 if(board[0][i] == WhiteKing) break;
\r
12921 if(i == BOARD_RGHT) break;
\r
12922 FENcastlingRights[2] = i;
\r
12923 c -= AAA - 'a' + 'A';
\r
12924 if(board[0][c] >= WhiteKing) break;
\r
12926 FENcastlingRights[0] = c;
\r
12928 FENcastlingRights[1] = c;
\r
12932 if (appData.debugMode) {
\r
12933 fprintf(debugFP, "FEN castling rights:");
\r
12934 for(i=0; i<nrCastlingRights; i++)
\r
12935 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
12936 fprintf(debugFP, "\n");
\r
12939 while(*p==' ') p++;
\r
12942 /* read e.p. field in games that know e.p. capture */
\r
12943 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
12944 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
12946 p++; FENepStatus = EP_NONE;
\r
12948 char c = *p++ - AAA;
\r
12950 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
12951 if(*p >= '0' && *p <='9') *p++;
\r
12957 if(sscanf(p, "%d", &i) == 1) {
\r
12958 FENrulePlies = i; /* 50-move ply counter */
\r
12959 /* (The move number is still ignored) */
\r
12966 EditPositionPasteFEN(char *fen)
\r
12968 if (fen != NULL) {
\r
12969 Board initial_position;
\r
12971 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
12972 DisplayError("Bad FEN position in clipboard", 0);
\r
12975 int savedBlackPlaysFirst = blackPlaysFirst;
\r
12976 EditPositionEvent();
\r
12977 blackPlaysFirst = savedBlackPlaysFirst;
\r
12978 CopyBoard(boards[0], initial_position);
\r
12979 /* [HGM] copy FEN attributes as well */
\r
12981 initialRulePlies = FENrulePlies;
\r
12982 epStatus[0] = FENepStatus;
\r
12983 for( i=0; i<nrCastlingRights; i++ )
\r
12984 castlingRights[0][i] = FENcastlingRights[i];
\r
12986 EditPositionDone();
\r
12987 DisplayBothClocks();
\r
12988 DrawPosition(FALSE, boards[currentMove]);
\r