2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
\r
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
\r
8 * The following terms apply to Digital Equipment Corporation's copyright
\r
9 * interest in XBoard:
\r
10 * ------------------------------------------------------------------------
\r
11 * All Rights Reserved
\r
13 * Permission to use, copy, modify, and distribute this software and its
\r
14 * documentation for any purpose and without fee is hereby granted,
\r
15 * provided that the above copyright notice appear in all copies and that
\r
16 * both that copyright notice and this permission notice appear in
\r
17 * supporting documentation, and that the name of Digital not be
\r
18 * used in advertising or publicity pertaining to distribution of the
\r
19 * software without specific, written prior permission.
\r
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
28 * ------------------------------------------------------------------------
\r
30 * The following terms apply to the enhanced version of XBoard distributed
\r
31 * by the Free Software Foundation:
\r
32 * ------------------------------------------------------------------------
\r
33 * This program is free software; you can redistribute it and/or modify
\r
34 * it under the terms of the GNU General Public License as published by
\r
35 * the Free Software Foundation; either version 2 of the License, or
\r
36 * (at your option) any later version.
\r
38 * This program is distributed in the hope that it will be useful,
\r
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
41 * GNU General Public License for more details.
\r
43 * You should have received a copy of the GNU General Public License
\r
44 * along with this program; if not, write to the Free Software
\r
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\r
46 * ------------------------------------------------------------------------
\r
48 * See the file ChangeLog for a revision history. */
\r
50 /* [AS] Also useful here for debugging */
\r
52 #include <windows.h>
\r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
58 #define DoSleep( n )
\r
68 #include <sys/types.h>
\r
69 #include <sys/stat.h>
\r
73 # include <stdlib.h>
\r
74 # include <string.h>
\r
75 #else /* not STDC_HEADERS */
\r
77 # include <string.h>
\r
78 # else /* not HAVE_STRING_H */
\r
79 # include <strings.h>
\r
80 # endif /* not HAVE_STRING_H */
\r
81 #endif /* not STDC_HEADERS */
\r
83 #if HAVE_SYS_FCNTL_H
\r
84 # include <sys/fcntl.h>
\r
85 #else /* not HAVE_SYS_FCNTL_H */
\r
88 # endif /* HAVE_FCNTL_H */
\r
89 #endif /* not HAVE_SYS_FCNTL_H */
\r
91 #if TIME_WITH_SYS_TIME
\r
92 # include <sys/time.h>
\r
95 # if HAVE_SYS_TIME_H
\r
96 # include <sys/time.h>
\r
102 #if defined(_amigados) && !defined(__GNUC__)
\r
104 int tz_minuteswest;
\r
107 extern int gettimeofday(struct timeval *, struct timezone *);
\r
111 # include <unistd.h>
\r
114 #include "common.h"
\r
115 #include "frontend.h"
\r
116 #include "backend.h"
\r
117 #include "parser.h"
\r
120 # include "zippy.h"
\r
122 #include "backendz.h"
\r
124 /* A point in time */
\r
126 long sec; /* Assuming this is >= 32 bits */
\r
127 int ms; /* Assuming this is >= 16 bits */
\r
130 /* Search stats from chessprogram */
\r
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
\r
133 int depth; /* Current search depth */
\r
134 int nr_moves; /* Total nr of root moves */
\r
135 int moves_left; /* Moves remaining to be searched */
\r
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
\r
137 unsigned long nodes; /* # of nodes searched */
\r
138 int time; /* Search time (centiseconds) */
\r
139 int score; /* Score (centipawns) */
\r
140 int got_only_move; /* If last msg was "(only move)" */
\r
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
\r
142 int ok_to_send; /* handshaking between send & recv */
\r
143 int line_is_book; /* 1 if movelist is book moves */
\r
144 int seen_stat; /* 1 if we've seen the stat01: line */
\r
145 } ChessProgramStats;
\r
147 int establish P((void));
\r
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
149 char *buf, int count, int error));
\r
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
151 char *buf, int count, int error));
\r
152 void SendToICS P((char *s));
\r
153 void SendToICSDelayed P((char *s, long msdelay));
\r
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
155 int toX, int toY));
\r
156 void InitPosition P((int redraw));
\r
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
158 int AutoPlayOneMove P((void));
\r
159 int LoadGameOneMove P((ChessMove readAhead));
\r
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
161 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
162 int SavePositionToFile P((char *filename));
\r
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
168 /*char*/int promoChar));
\r
169 void BackwardInner P((int target));
\r
170 void ForwardInner P((int target));
\r
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
172 void EditPositionDone P((void));
\r
173 void PrintOpponents P((FILE *fp));
\r
174 void PrintPosition P((FILE *fp, int move));
\r
175 void StartChessProgram P((ChessProgramState *cps));
\r
176 void SendToProgram P((char *message, ChessProgramState *cps));
\r
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
179 char *buf, int count, int error));
\r
180 void SendTimeControl P((ChessProgramState *cps,
\r
181 int mps, long tc, int inc, int sd, int st));
\r
182 char *TimeControlTagValue P((void));
\r
183 void Attention P((ChessProgramState *cps));
\r
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
185 void ResurrectChessProgram P((void));
\r
186 void DisplayComment P((int moveNumber, char *text));
\r
187 void DisplayMove P((int moveNumber));
\r
188 void DisplayAnalysis P((void));
\r
190 void ParseGameHistory P((char *game));
\r
191 void ParseBoard12 P((char *string));
\r
192 void StartClocks P((void));
\r
193 void SwitchClocks P((void));
\r
194 void StopClocks P((void));
\r
195 void ResetClocks P((void));
\r
196 char *PGNDate P((void));
\r
197 void SetGameInfo P((void));
\r
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
199 int RegisterMove P((void));
\r
200 void MakeRegisteredMove P((void));
\r
201 void TruncateGame P((void));
\r
202 int looking_at P((char *, int *, char *));
\r
203 void CopyPlayerNameIntoFileName P((char **, char *));
\r
204 char *SavePart P((char *));
\r
205 int SaveGameOldStyle P((FILE *));
\r
206 int SaveGamePGN P((FILE *));
\r
207 void GetTimeMark P((TimeMark *));
\r
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
209 int CheckFlags P((void));
\r
210 long NextTickLength P((long));
\r
211 void CheckTimeControl P((void));
\r
212 void show_bytes P((FILE *, char *, int));
\r
213 int string_to_rating P((char *str));
\r
214 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
215 void InitBackEnd3 P((void));
\r
216 void FeatureDone P((ChessProgramState* cps, int val));
\r
217 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
219 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
221 extern int tinyLayout, smallLayout;
\r
222 static ChessProgramStats programStats;
\r
223 static int exiting = 0; /* [HGM] moved to top */
\r
224 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;
\r
225 extern int startedFromPositionFile;
\r
226 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
227 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
229 /* States for ics_getting_history */
\r
231 #define H_REQUESTED 1
\r
232 #define H_GOT_REQ_HEADER 2
\r
233 #define H_GOT_UNREQ_HEADER 3
\r
234 #define H_GETTING_MOVES 4
\r
235 #define H_GOT_UNWANTED_HEADER 5
\r
237 /* whosays values for GameEnds */
\r
239 #define GE_ENGINE 1
\r
240 #define GE_PLAYER 2
\r
242 #define GE_XBOARD 4
\r
243 #define GE_ENGINE1 5
\r
244 #define GE_ENGINE2 6
\r
246 /* Maximum number of games in a cmail message */
\r
247 #define CMAIL_MAX_GAMES 20
\r
249 /* Different types of move when calling RegisterMove */
\r
250 #define CMAIL_MOVE 0
\r
251 #define CMAIL_RESIGN 1
\r
252 #define CMAIL_DRAW 2
\r
253 #define CMAIL_ACCEPT 3
\r
255 /* Different types of result to remember for each game */
\r
256 #define CMAIL_NOT_RESULT 0
\r
257 #define CMAIL_OLD_RESULT 1
\r
258 #define CMAIL_NEW_RESULT 2
\r
260 /* Telnet protocol constants */
\r
261 #define TN_WILL 0373
\r
262 #define TN_WONT 0374
\r
264 #define TN_DONT 0376
\r
265 #define TN_IAC 0377
\r
266 #define TN_ECHO 0001
\r
267 #define TN_SGA 0003
\r
271 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
273 assert( dst != NULL );
\r
274 assert( src != NULL );
\r
275 assert( count > 0 );
\r
277 strncpy( dst, src, count );
\r
278 dst[ count-1 ] = '\0';
\r
282 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
286 assert( dst != NULL );
\r
287 assert( src != NULL );
\r
288 assert( count > 0 );
\r
290 dst_len = strlen(dst);
\r
292 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
294 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
299 /* Fake up flags for now, as we aren't keeping track of castling
\r
300 availability yet. [HGM] Change of logic: the flag now only
\r
301 indicates the type of castlings allowed by the rule of the game.
\r
302 The actual rights themselves are maintained in the array
\r
303 castlingRights, as part of the game history, and are not probed
\r
309 int flags = F_ALL_CASTLE_OK;
\r
310 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
311 switch (gameInfo.variant) {
\r
312 case VariantSuicide:
\r
313 case VariantGiveaway:
\r
314 flags |= F_IGNORE_CHECK;
\r
315 flags &= ~F_ALL_CASTLE_OK;
\r
317 case VariantAtomic:
\r
318 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
320 case VariantKriegspiel:
\r
321 flags |= F_KRIEGSPIEL_CAPTURE;
\r
323 /* case VariantCapaRandom: */
\r
324 case VariantFischeRandom:
\r
325 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
326 case VariantNoCastle:
\r
327 flags &= ~F_ALL_CASTLE_OK;
\r
335 FILE *gameFileFP, *debugFP;
\r
338 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
339 into a fixed-size buffer. Because of this, we must be prepared to
\r
340 receive strings as long as the size of the input buffer, which is currently
\r
341 set to 4K for Windows and 8K for the rest.
\r
342 So, we must either allocate sufficiently large buffers here, or
\r
343 reduce the size of the input buffer in the input reading part.
\r
346 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
347 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
348 char thinkOutput1[MSG_SIZ*10];
\r
350 ChessProgramState first, second;
\r
352 /* premove variables */
\r
353 int premoveToX = 0;
\r
354 int premoveToY = 0;
\r
355 int premoveFromX = 0;
\r
356 int premoveFromY = 0;
\r
357 int premovePromoChar = 0;
\r
358 int gotPremove = 0;
\r
359 Boolean alarmSounded;
\r
360 /* end premove variables */
\r
362 #define ICS_GENERIC 0
\r
365 #define ICS_CHESSNET 3 /* not really supported */
\r
366 int ics_type = ICS_GENERIC;
\r
367 char *ics_prefix = "$";
\r
369 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
370 int pauseExamForwardMostMove = 0;
\r
371 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
372 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
373 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
374 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
375 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
376 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
377 int whiteFlag = FALSE, blackFlag = FALSE;
\r
378 int userOfferedDraw = FALSE;
\r
379 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
380 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
381 int cmailMoveType[CMAIL_MAX_GAMES];
\r
382 long ics_clock_paused = 0;
\r
383 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
384 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
385 GameMode gameMode = BeginningOfGame;
\r
386 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
387 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
388 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
389 int hiddenThinkOutputState = 0; /* [AS] */
\r
390 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
391 int adjudicateLossPlies = 6;
\r
392 char white_holding[64], black_holding[64];
\r
393 TimeMark lastNodeCountTime;
\r
394 long lastNodeCount=0;
\r
395 int have_sent_ICS_logon = 0;
\r
396 int movesPerSession;
\r
397 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
398 long timeControl_2; /* [AS] Allow separate time controls */
\r
399 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
400 long timeRemaining[2][MAX_MOVES];
\r
402 TimeMark programStartTime;
\r
403 char ics_handle[MSG_SIZ];
\r
404 int have_set_title = 0;
\r
406 /* animateTraining preserves the state of appData.animate
\r
407 * when Training mode is activated. This allows the
\r
408 * response to be animated when appData.animate == TRUE and
\r
409 * appData.animateDragging == TRUE.
\r
411 Boolean animateTraining;
\r
417 Board boards[MAX_MOVES];
\r
418 /* [HGM] Following 7 needed for accurate legality tests: */
\r
419 char epStatus[MAX_MOVES];
\r
420 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
421 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
422 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
423 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
424 int initialRulePlies, FENrulePlies;
\r
426 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
429 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
430 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
431 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
432 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
433 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
436 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
437 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
438 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
439 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
440 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
443 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
444 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
445 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
446 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
447 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
450 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
451 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
452 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
453 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
454 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
457 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
458 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
459 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
460 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
461 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
465 #if (BOARD_SIZE>=10)
\r
466 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
467 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
468 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
469 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
470 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
473 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
475 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
476 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
477 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
480 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
481 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
482 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
483 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
484 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
488 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
489 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
490 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
491 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
492 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
495 #define GothicArray CapablancaArray
\r
499 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
500 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
501 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
502 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
503 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
506 #define FalconArray CapablancaArray
\r
509 #else // !(BOARD_SIZE>=10)
\r
510 #define XiangqiPosition FIDEArray
\r
511 #define CapablancaArray FIDEArray
\r
512 #define GothicArray FIDEArray
\r
513 #endif // !(BOARD_SIZE>=10)
\r
515 #if (BOARD_SIZE>=12)
\r
516 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
517 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
518 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
519 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
520 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
522 #else // !(BOARD_SIZE>=12)
\r
523 #define CourierArray CapablancaArray
\r
524 #endif // !(BOARD_SIZE>=12)
\r
527 Board initialPosition;
\r
530 /* Convert str to a rating. Checks for special cases of "----",
\r
532 "++++", etc. Also strips ()'s */
\r
534 string_to_rating(str)
\r
537 while(*str && !isdigit(*str)) ++str;
\r
539 return 0; /* One of the special "no rating" cases */
\r
545 ClearProgramStats()
\r
547 /* Init programStats */
\r
548 programStats.movelist[0] = 0;
\r
549 programStats.depth = 0;
\r
550 programStats.nr_moves = 0;
\r
551 programStats.moves_left = 0;
\r
552 programStats.nodes = 0;
\r
553 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
554 programStats.score = 0;
\r
555 programStats.got_only_move = 0;
\r
556 programStats.got_fail = 0;
\r
557 programStats.line_is_book = 0;
\r
563 int matched, min, sec;
\r
565 GetTimeMark(&programStartTime);
\r
567 ClearProgramStats();
\r
568 programStats.ok_to_send = 1;
\r
569 programStats.seen_stat = 0;
\r
572 * Initialize game list
\r
574 ListNew(&gameList);
\r
578 * Internet chess server status
\r
580 if (appData.icsActive) {
\r
581 appData.matchMode = FALSE;
\r
582 appData.matchGames = 0;
\r
584 appData.noChessProgram = !appData.zippyPlay;
\r
586 appData.zippyPlay = FALSE;
\r
587 appData.zippyTalk = FALSE;
\r
588 appData.noChessProgram = TRUE;
\r
590 if (*appData.icsHelper != NULLCHAR) {
\r
591 appData.useTelnet = TRUE;
\r
592 appData.telnetProgram = appData.icsHelper;
\r
595 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
598 /* [AS] Initialize pv info list [HGM] and game state */
\r
602 for( i=0; i<MAX_MOVES; i++ ) {
\r
603 pvInfoList[i].depth = -1;
\r
604 epStatus[i]=EP_NONE;
\r
605 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
610 * Parse timeControl resource
\r
612 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
613 appData.movesPerSession)) {
\r
615 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
616 DisplayFatalError(buf, 0, 2);
\r
620 * Parse searchTime resource
\r
622 if (*appData.searchTime != NULLCHAR) {
\r
623 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
624 if (matched == 1) {
\r
625 searchTime = min * 60;
\r
626 } else if (matched == 2) {
\r
627 searchTime = min * 60 + sec;
\r
630 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
631 DisplayFatalError(buf, 0, 2);
\r
635 /* [AS] Adjudication threshold */
\r
636 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
638 first.which = "first";
\r
639 second.which = "second";
\r
640 first.maybeThinking = second.maybeThinking = FALSE;
\r
641 first.pr = second.pr = NoProc;
\r
642 first.isr = second.isr = NULL;
\r
643 first.sendTime = second.sendTime = 2;
\r
644 first.sendDrawOffers = 1;
\r
645 if (appData.firstPlaysBlack) {
\r
646 first.twoMachinesColor = "black\n";
\r
647 second.twoMachinesColor = "white\n";
\r
649 first.twoMachinesColor = "white\n";
\r
650 second.twoMachinesColor = "black\n";
\r
652 first.program = appData.firstChessProgram;
\r
653 second.program = appData.secondChessProgram;
\r
654 first.host = appData.firstHost;
\r
655 second.host = appData.secondHost;
\r
656 first.dir = appData.firstDirectory;
\r
657 second.dir = appData.secondDirectory;
\r
658 first.other = &second;
\r
659 second.other = &first;
\r
660 first.initString = appData.initString;
\r
661 second.initString = appData.secondInitString;
\r
662 first.computerString = appData.firstComputerString;
\r
663 second.computerString = appData.secondComputerString;
\r
664 first.useSigint = second.useSigint = TRUE;
\r
665 first.useSigterm = second.useSigterm = TRUE;
\r
666 first.reuse = appData.reuseFirst;
\r
667 second.reuse = appData.reuseSecond;
\r
668 first.useSetboard = second.useSetboard = FALSE;
\r
669 first.useSAN = second.useSAN = FALSE;
\r
670 first.usePing = second.usePing = FALSE;
\r
671 first.lastPing = second.lastPing = 0;
\r
672 first.lastPong = second.lastPong = 0;
\r
673 first.usePlayother = second.usePlayother = FALSE;
\r
674 first.useColors = second.useColors = TRUE;
\r
675 first.useUsermove = second.useUsermove = FALSE;
\r
676 first.sendICS = second.sendICS = FALSE;
\r
677 first.sendName = second.sendName = appData.icsActive;
\r
678 first.sdKludge = second.sdKludge = FALSE;
\r
679 first.stKludge = second.stKludge = FALSE;
\r
680 TidyProgramName(first.program, first.host, first.tidy);
\r
681 TidyProgramName(second.program, second.host, second.tidy);
\r
682 first.matchWins = second.matchWins = 0;
\r
683 strcpy(first.variants, appData.variant);
\r
684 strcpy(second.variants, appData.variant);
\r
685 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
686 first.analyzing = second.analyzing = FALSE;
\r
687 first.initDone = second.initDone = FALSE;
\r
689 /* New features added by Tord: */
\r
690 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
691 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
692 /* End of new features added by Tord. */
\r
694 /* [HGM] time odds: set factor for each machine */
\r
695 first.timeOdds = appData.firstTimeOdds;
\r
696 second.timeOdds = appData.secondTimeOdds;
\r
698 if(appData.timeOddsMode) {
\r
699 norm = first.timeOdds;
\r
700 if(norm > second.timeOdds) norm = second.timeOdds;
\r
702 first.timeOdds /= norm;
\r
703 second.timeOdds /= norm;
\r
706 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
707 first.accumulateTC = appData.firstAccumulateTC;
\r
708 second.accumulateTC = appData.secondAccumulateTC;
\r
709 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
712 first.debug = second.debug = FALSE;
\r
714 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
715 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
716 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
717 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
718 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
719 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
721 if (appData.firstProtocolVersion > PROTOVER ||
\r
722 appData.firstProtocolVersion < 1) {
\r
724 sprintf(buf, "protocol version %d not supported",
\r
725 appData.firstProtocolVersion);
\r
726 DisplayFatalError(buf, 0, 2);
\r
728 first.protocolVersion = appData.firstProtocolVersion;
\r
731 if (appData.secondProtocolVersion > PROTOVER ||
\r
732 appData.secondProtocolVersion < 1) {
\r
734 sprintf(buf, "protocol version %d not supported",
\r
735 appData.secondProtocolVersion);
\r
736 DisplayFatalError(buf, 0, 2);
\r
738 second.protocolVersion = appData.secondProtocolVersion;
\r
741 if (appData.icsActive) {
\r
742 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
743 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
744 appData.clockMode = FALSE;
\r
745 first.sendTime = second.sendTime = 0;
\r
749 /* Override some settings from environment variables, for backward
\r
750 compatibility. Unfortunately it's not feasible to have the env
\r
751 vars just set defaults, at least in xboard. Ugh.
\r
753 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
758 if (appData.noChessProgram) {
\r
759 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
760 + strlen(PATCHLEVEL));
\r
761 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
765 while (*q != ' ' && *q != NULLCHAR) q++;
\r
767 while (p > first.program && *(p-1) != '/') p--;
\r
768 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
769 + strlen(PATCHLEVEL) + (q - p));
\r
770 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
771 strncat(programVersion, p, q - p);
\r
774 if (!appData.icsActive) {
\r
776 /* Check for variants that are supported only in ICS mode,
\r
777 or not at all. Some that are accepted here nevertheless
\r
778 have bugs; see comments below.
\r
780 VariantClass variant = StringToVariant(appData.variant);
\r
782 case VariantBughouse: /* need four players and two boards */
\r
783 case VariantKriegspiel: /* need to hide pieces and move details */
\r
784 /* case VariantFischeRandom: (Fabien: moved below) */
\r
785 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
786 DisplayFatalError(buf, 0, 2);
\r
789 case VariantUnknown:
\r
790 case VariantLoadable:
\r
800 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
801 DisplayFatalError(buf, 0, 2);
\r
804 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
805 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
806 case VariantGothic: /* [HGM] should work */
\r
807 case VariantCapablanca: /* [HGM] should work */
\r
808 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
809 case VariantShogi: /* [HGM] drops not tested for legality */
\r
810 case VariantKnightmate: /* [HGM] should work */
\r
811 case VariantCylinder: /* [HGM] untested */
\r
812 case VariantFalcon: /* [HGM] untested */
\r
813 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
814 offboard interposition not understood */
\r
815 case VariantNormal: /* definitely works! */
\r
816 case VariantWildCastle: /* pieces not automatically shuffled */
\r
817 case VariantNoCastle: /* pieces not automatically shuffled */
\r
818 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
\r
819 case VariantLosers: /* should work except for win condition,
\r
820 and doesn't know captures are mandatory */
\r
821 case VariantSuicide: /* should work except for win condition,
\r
822 and doesn't know captures are mandatory */
\r
823 case VariantGiveaway: /* should work except for win condition,
\r
824 and doesn't know captures are mandatory */
\r
825 case VariantTwoKings: /* should work */
\r
826 case VariantAtomic: /* should work except for win condition */
\r
827 case Variant3Check: /* should work except for win condition */
\r
828 case VariantShatranj: /* might work if TestLegality is off */
\r
834 int NextIntegerFromString( char ** str, long * value )
\r
839 while( *s == ' ' || *s == '\t' ) {
\r
845 if( *s >= '0' && *s <= '9' ) {
\r
846 while( *s >= '0' && *s <= '9' ) {
\r
847 *value = *value * 10 + (*s - '0');
\r
859 int NextTimeControlFromString( char ** str, long * value )
\r
862 int result = NextIntegerFromString( str, &temp );
\r
864 if( result == 0 ) {
\r
865 *value = temp * 60; /* Minutes */
\r
866 if( **str == ':' ) {
\r
868 result = NextIntegerFromString( str, &temp );
\r
869 *value += temp; /* Seconds */
\r
876 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
877 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
878 int result = -1; long temp, temp2;
\r
880 if(**str != '+') return -1; // old params remain in force!
\r
882 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
885 /* time only: incremental or sudden-death time control */
\r
886 if(**str == '+') { /* increment follows; read it */
\r
888 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
889 *inc = temp2 * 1000;
\r
891 *moves = 0; *tc = temp * 1000;
\r
893 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
895 (*str)++; /* classical time control */
\r
896 result = NextTimeControlFromString( str, &temp2);
\r
899 *tc = temp2 * 1000;
\r
905 int GetTimeQuota(int movenr)
\r
906 { /* [HGM] get time to add from the multi-session time-control string */
\r
907 int moves=1; /* kludge to force reading of first session */
\r
908 long time, increment;
\r
909 char *s = fullTimeControlString;
\r
911 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
913 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
914 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
915 if(movenr == -1) return time; /* last move before new session */
\r
916 if(!moves) return increment; /* current session is incremental */
\r
917 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
918 } while(movenr >= -1); /* try again for next session */
\r
920 return 0; // no new time quota on this move
\r
924 ParseTimeControl(tc, ti, mps)
\r
930 int matched, min, sec;
\r
932 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
933 if (matched == 1) {
\r
934 timeControl = min * 60 * 1000;
\r
935 } else if (matched == 2) {
\r
936 timeControl = (min * 60 + sec) * 1000;
\r
945 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
948 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
949 else sprintf(buf, "+%s+%d", tc, ti);
\r
952 sprintf(buf, "+%d/%s", mps, tc);
\r
953 else sprintf(buf, "+%s", tc);
\r
955 fullTimeControlString = StrSave(buf);
\r
957 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
962 /* Parse second time control */
\r
965 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
973 timeControl_2 = tc2 * 1000;
\r
983 timeControl = tc1 * 1000;
\r
987 timeIncrement = ti * 1000; /* convert to ms */
\r
988 movesPerSession = 0;
\r
991 movesPerSession = mps;
\r
999 if (appData.debugMode) {
\r
1000 fprintf(debugFP, "%s\n", programVersion);
\r
1003 if (appData.matchGames > 0) {
\r
1004 appData.matchMode = TRUE;
\r
1005 } else if (appData.matchMode) {
\r
1006 appData.matchGames = 1;
\r
1008 Reset(TRUE, FALSE);
\r
1009 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1012 /* kludge: allow timeout for initial "feature" commands */
\r
1014 DisplayMessage("", "Starting chess program");
\r
1015 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1020 InitBackEnd3 P((void))
\r
1022 GameMode initialMode;
\r
1023 char buf[MSG_SIZ];
\r
1026 if (appData.debugMode) {
\r
1027 fprintf(debugFP, "From InitBackend3\n");
\r
1029 InitChessProgram(&first, startedFromSetupPosition);
\r
1031 if (appData.icsActive) {
\r
1032 err = establish();
\r
1034 if (*appData.icsCommPort != NULLCHAR) {
\r
1035 sprintf(buf, "Could not open comm port %s",
\r
1036 appData.icsCommPort);
\r
1038 sprintf(buf, "Could not connect to host %s, port %s",
\r
1039 appData.icsHost, appData.icsPort);
\r
1041 DisplayFatalError(buf, err, 1);
\r
1046 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1048 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1049 } else if (appData.noChessProgram) {
\r
1055 if (*appData.cmailGameName != NULLCHAR) {
\r
1057 OpenLoopback(&cmailPR);
\r
1059 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1063 DisplayMessage("", "");
\r
1064 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1065 initialMode = BeginningOfGame;
\r
1066 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1067 initialMode = TwoMachinesPlay;
\r
1068 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1069 initialMode = AnalyzeFile;
\r
1070 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1071 initialMode = AnalyzeMode;
\r
1072 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1073 initialMode = MachinePlaysWhite;
\r
1074 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1075 initialMode = MachinePlaysBlack;
\r
1076 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1077 initialMode = EditGame;
\r
1078 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1079 initialMode = EditPosition;
\r
1080 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1081 initialMode = Training;
\r
1083 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
1084 DisplayFatalError(buf, 0, 2);
\r
1088 if (appData.matchMode) {
\r
1089 /* Set up machine vs. machine match */
\r
1090 if (appData.noChessProgram) {
\r
1091 DisplayFatalError("Can't have a match with no chess programs",
\r
1097 if (*appData.loadGameFile != NULLCHAR) {
\r
1098 if (!LoadGameFromFile(appData.loadGameFile,
\r
1099 appData.loadGameIndex,
\r
1100 appData.loadGameFile, FALSE)) {
\r
1101 DisplayFatalError("Bad game file", 0, 1);
\r
1104 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1105 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1106 appData.loadPositionIndex,
\r
1107 appData.loadPositionFile)) {
\r
1108 DisplayFatalError("Bad position file", 0, 1);
\r
1112 TwoMachinesEvent();
\r
1113 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1114 /* Set up cmail mode */
\r
1115 ReloadCmailMsgEvent(TRUE);
\r
1117 /* Set up other modes */
\r
1118 if (initialMode == AnalyzeFile) {
\r
1119 if (*appData.loadGameFile == NULLCHAR) {
\r
1120 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1124 if (*appData.loadGameFile != NULLCHAR) {
\r
1125 (void) LoadGameFromFile(appData.loadGameFile,
\r
1126 appData.loadGameIndex,
\r
1127 appData.loadGameFile, TRUE);
\r
1128 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1129 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1130 appData.loadPositionIndex,
\r
1131 appData.loadPositionFile);
\r
1132 /* [HGM] try to make self-starting even after FEN load */
\r
1133 /* to allow automatic setup of fairy variants with wtm */
\r
1134 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1135 gameMode = BeginningOfGame;
\r
1136 setboardSpoiledMachineBlack = 1;
\r
1138 /* [HGM] loadPos: make that every new game uses the setup */
\r
1139 /* from file as long as we do not switch variant */
\r
1140 if(!blackPlaysFirst) { int i;
\r
1141 startedFromPositionFile = TRUE;
\r
1142 CopyBoard(filePosition, boards[0]);
\r
1143 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1146 if (initialMode == AnalyzeMode) {
\r
1147 if (appData.noChessProgram) {
\r
1148 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1151 if (appData.icsActive) {
\r
1152 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1155 AnalyzeModeEvent();
\r
1156 } else if (initialMode == AnalyzeFile) {
\r
1157 ShowThinkingEvent(TRUE);
\r
1158 AnalyzeFileEvent();
\r
1159 AnalysisPeriodicEvent(1);
\r
1160 } else if (initialMode == MachinePlaysWhite) {
\r
1161 if (appData.noChessProgram) {
\r
1162 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1166 if (appData.icsActive) {
\r
1167 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1171 MachineWhiteEvent();
\r
1172 } else if (initialMode == MachinePlaysBlack) {
\r
1173 if (appData.noChessProgram) {
\r
1174 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1178 if (appData.icsActive) {
\r
1179 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1183 MachineBlackEvent();
\r
1184 } else if (initialMode == TwoMachinesPlay) {
\r
1185 if (appData.noChessProgram) {
\r
1186 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1190 if (appData.icsActive) {
\r
1191 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1195 TwoMachinesEvent();
\r
1196 } else if (initialMode == EditGame) {
\r
1198 } else if (initialMode == EditPosition) {
\r
1199 EditPositionEvent();
\r
1200 } else if (initialMode == Training) {
\r
1201 if (*appData.loadGameFile == NULLCHAR) {
\r
1202 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1211 * Establish will establish a contact to a remote host.port.
\r
1212 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1213 * used to talk to the host.
\r
1214 * Returns 0 if okay, error code if not.
\r
1219 char buf[MSG_SIZ];
\r
1221 if (*appData.icsCommPort != NULLCHAR) {
\r
1222 /* Talk to the host through a serial comm port */
\r
1223 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1225 } else if (*appData.gateway != NULLCHAR) {
\r
1226 if (*appData.remoteShell == NULLCHAR) {
\r
1227 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1228 sprintf(buf, "%s %s %s",
\r
1229 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1230 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1233 /* Use the rsh program to run telnet program on a gateway host */
\r
1234 if (*appData.remoteUser == NULLCHAR) {
\r
1235 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1236 appData.gateway, appData.telnetProgram,
\r
1237 appData.icsHost, appData.icsPort);
\r
1239 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1240 appData.remoteShell, appData.gateway,
\r
1241 appData.remoteUser, appData.telnetProgram,
\r
1242 appData.icsHost, appData.icsPort);
\r
1244 return StartChildProcess(buf, "", &icsPR);
\r
1247 } else if (appData.useTelnet) {
\r
1248 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1251 /* TCP socket interface differs somewhat between
\r
1252 Unix and NT; handle details in the front end.
\r
1254 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1259 show_bytes(fp, buf, count)
\r
1265 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1266 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1275 /* Returns an errno value */
\r
1277 OutputMaybeTelnet(pr, message, count, outError)
\r
1283 char buf[8192], *p, *q, *buflim;
\r
1284 int left, newcount, outcount;
\r
1286 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1287 *appData.gateway != NULLCHAR) {
\r
1288 if (appData.debugMode) {
\r
1289 fprintf(debugFP, ">ICS: ");
\r
1290 show_bytes(debugFP, message, count);
\r
1291 fprintf(debugFP, "\n");
\r
1293 return OutputToProcess(pr, message, count, outError);
\r
1296 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1302 if (q >= buflim) {
\r
1303 if (appData.debugMode) {
\r
1304 fprintf(debugFP, ">ICS: ");
\r
1305 show_bytes(debugFP, buf, newcount);
\r
1306 fprintf(debugFP, "\n");
\r
1308 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1309 if (outcount < newcount) return -1; /* to be sure */
\r
1316 } else if (((unsigned char) *p) == TN_IAC) {
\r
1317 *q++ = (char) TN_IAC;
\r
1324 if (appData.debugMode) {
\r
1325 fprintf(debugFP, ">ICS: ");
\r
1326 show_bytes(debugFP, buf, newcount);
\r
1327 fprintf(debugFP, "\n");
\r
1329 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1330 if (outcount < newcount) return -1; /* to be sure */
\r
1335 read_from_player(isr, closure, message, count, error)
\r
1336 InputSourceRef isr;
\r
1342 int outError, outCount;
\r
1343 static int gotEof = 0;
\r
1345 /* Pass data read from player on to ICS */
\r
1348 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1349 if (outCount < count) {
\r
1350 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1352 } else if (count < 0) {
\r
1353 RemoveInputSource(isr);
\r
1354 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1355 } else if (gotEof++ > 0) {
\r
1356 RemoveInputSource(isr);
\r
1357 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1365 int count, outCount, outError;
\r
1367 if (icsPR == NULL) return;
\r
1369 count = strlen(s);
\r
1370 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1371 if (outCount < count) {
\r
1372 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1376 /* This is used for sending logon scripts to the ICS. Sending
\r
1377 without a delay causes problems when using timestamp on ICC
\r
1378 (at least on my machine). */
\r
1380 SendToICSDelayed(s,msdelay)
\r
1384 int count, outCount, outError;
\r
1386 if (icsPR == NULL) return;
\r
1388 count = strlen(s);
\r
1389 if (appData.debugMode) {
\r
1390 fprintf(debugFP, ">ICS: ");
\r
1391 show_bytes(debugFP, s, count);
\r
1392 fprintf(debugFP, "\n");
\r
1394 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1396 if (outCount < count) {
\r
1397 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1402 /* Remove all highlighting escape sequences in s
\r
1403 Also deletes any suffix starting with '('
\r
1406 StripHighlightAndTitle(s)
\r
1409 static char retbuf[MSG_SIZ];
\r
1412 while (*s != NULLCHAR) {
\r
1413 while (*s == '\033') {
\r
1414 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1415 if (*s != NULLCHAR) s++;
\r
1417 while (*s != NULLCHAR && *s != '\033') {
\r
1418 if (*s == '(' || *s == '[') {
\r
1429 /* Remove all highlighting escape sequences in s */
\r
1434 static char retbuf[MSG_SIZ];
\r
1437 while (*s != NULLCHAR) {
\r
1438 while (*s == '\033') {
\r
1439 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1440 if (*s != NULLCHAR) s++;
\r
1442 while (*s != NULLCHAR && *s != '\033') {
\r
1450 char *variantNames[] = VARIANT_NAMES;
\r
1455 return variantNames[v];
\r
1459 /* Identify a variant from the strings the chess servers use or the
\r
1460 PGN Variant tag names we use. */
\r
1462 StringToVariant(e)
\r
1467 VariantClass v = VariantNormal;
\r
1468 int i, found = FALSE;
\r
1469 char buf[MSG_SIZ];
\r
1473 /* [HGM] skip over optional board-size prefixes */
\r
1474 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1475 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1476 while( *e++ != '_');
\r
1479 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1480 if (StrCaseStr(e, variantNames[i])) {
\r
1481 v = (VariantClass) i;
\r
1488 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1489 || StrCaseStr(e, "wild/fr")) {
\r
1490 v = VariantFischeRandom;
\r
1491 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1492 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1494 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1495 if (isdigit(*p)) {
\r
1501 case 0: /* FICS only, actually */
\r
1503 /* Castling legal even if K starts on d-file */
\r
1504 v = VariantWildCastle;
\r
1509 /* Castling illegal even if K & R happen to start in
\r
1510 normal positions. */
\r
1511 v = VariantNoCastle;
\r
1524 /* Castling legal iff K & R start in normal positions */
\r
1525 v = VariantNormal;
\r
1530 /* Special wilds for position setup; unclear what to do here */
\r
1531 v = VariantLoadable;
\r
1534 /* Bizarre ICC game */
\r
1535 v = VariantTwoKings;
\r
1538 v = VariantKriegspiel;
\r
1541 v = VariantLosers;
\r
1544 v = VariantFischeRandom;
\r
1547 v = VariantCrazyhouse;
\r
1550 v = VariantBughouse;
\r
1553 v = Variant3Check;
\r
1556 /* Not quite the same as FICS suicide! */
\r
1557 v = VariantGiveaway;
\r
1560 v = VariantAtomic;
\r
1563 v = VariantShatranj;
\r
1566 /* Temporary names for future ICC types. The name *will* change in
\r
1567 the next xboard/WinBoard release after ICC defines it. */
\r
1596 v = VariantXiangqi;
\r
1599 v = VariantCourier;
\r
1602 v = VariantGothic;
\r
1605 v = VariantCapablanca;
\r
1608 v = VariantKnightmate;
\r
1614 v = VariantCylinder;
\r
1617 v = VariantFalcon;
\r
1621 /* Found "wild" or "w" in the string but no number;
\r
1622 must assume it's normal chess. */
\r
1623 v = VariantNormal;
\r
1626 sprintf(buf, "Unknown wild type %d", wnum);
\r
1627 DisplayError(buf, 0);
\r
1628 v = VariantUnknown;
\r
1633 if (appData.debugMode) {
\r
1634 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1635 e, wnum, VariantName(v));
\r
1640 static int leftover_start = 0, leftover_len = 0;
\r
1641 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1643 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1644 advance *index beyond it, and set leftover_start to the new value of
\r
1645 *index; else return FALSE. If pattern contains the character '*', it
\r
1646 matches any sequence of characters not containing '\r', '\n', or the
\r
1647 character following the '*' (if any), and the matched sequence(s) are
\r
1648 copied into star_match.
\r
1651 looking_at(buf, index, pattern)
\r
1656 char *bufp = &buf[*index], *patternp = pattern;
\r
1657 int star_count = 0;
\r
1658 char *matchp = star_match[0];
\r
1661 if (*patternp == NULLCHAR) {
\r
1662 *index = leftover_start = bufp - buf;
\r
1663 *matchp = NULLCHAR;
\r
1666 if (*bufp == NULLCHAR) return FALSE;
\r
1667 if (*patternp == '*') {
\r
1668 if (*bufp == *(patternp + 1)) {
\r
1669 *matchp = NULLCHAR;
\r
1670 matchp = star_match[++star_count];
\r
1674 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1676 if (*patternp == NULLCHAR)
\r
1681 *matchp++ = *bufp++;
\r
1685 if (*patternp != *bufp) return FALSE;
\r
1692 SendToPlayer(data, length)
\r
1696 int error, outCount;
\r
1697 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1698 if (outCount < length) {
\r
1699 DisplayFatalError("Error writing to display", error, 1);
\r
1704 PackHolding(packed, holding)
\r
1708 char *p = holding;
\r
1710 int runlength = 0;
\r
1716 switch (runlength) {
\r
1727 sprintf(q, "%d", runlength);
\r
1739 /* Telnet protocol requests from the front end */
\r
1741 TelnetRequest(ddww, option)
\r
1742 unsigned char ddww, option;
\r
1744 unsigned char msg[3];
\r
1745 int outCount, outError;
\r
1747 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1749 if (appData.debugMode) {
\r
1750 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1766 sprintf(buf1, "%d", ddww);
\r
1771 optionStr = "ECHO";
\r
1775 sprintf(buf2, "%d", option);
\r
1778 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1783 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1784 if (outCount < 3) {
\r
1785 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1792 if (!appData.icsActive) return;
\r
1793 TelnetRequest(TN_DO, TN_ECHO);
\r
1799 if (!appData.icsActive) return;
\r
1800 TelnetRequest(TN_DONT, TN_ECHO);
\r
1804 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1806 /* put the holdings sent to us by the server on the board holdings area */
\r
1807 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1809 ChessSquare piece;
\r
1811 if(gameInfo.holdingsWidth < 2) return;
\r
1813 if( (int)lowestPiece >= BlackPawn ) {
\r
1814 holdingsColumn = 0;
\r
1816 holdingsStartRow = BOARD_HEIGHT-1;
\r
1819 holdingsColumn = BOARD_WIDTH-1;
\r
1820 countsColumn = BOARD_WIDTH-2;
\r
1821 holdingsStartRow = 0;
\r
1825 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1826 board[i][holdingsColumn] = EmptySquare;
\r
1827 board[i][countsColumn] = (ChessSquare) 0;
\r
1829 while( (p=*holdings++) != NULLCHAR ) {
\r
1830 piece = CharToPiece( ToUpper(p) );
\r
1831 if(piece == EmptySquare) continue;
\r
1832 /*j = (int) piece - (int) WhitePawn;*/
\r
1833 j = PieceToNumber(piece);
\r
1834 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1835 if(j < 0) continue; /* should not happen */
\r
1836 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1837 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1838 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1843 char startBoard[MSG_SIZ]; /* [HGM] variantswitch */
\r
1846 VariantSwitch(Board board, VariantClass newVariant)
\r
1848 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j, oldCurrentMove = currentMove;
\r
1849 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1851 startedFromPositionFile = FALSE;
\r
1852 if(gameInfo.variant == newVariant) return;
\r
1854 /* [HGM] This routine is called each time an assignment is made to
\r
1855 * gameInfo.variant during a game, to make sure the board sizes
\r
1856 * are set to match the new variant. If that means adding or deleting
\r
1857 * holdings, we shift the playing board accordingly
\r
1858 * This kludge is needed because in ICS observe mode, we get boards
\r
1859 * of an ongoing game without knowing the variant, and learn about the
\r
1860 * latter only later. This can be because of the move list we requested,
\r
1861 * in which case the game history is refilled from the beginning anyway,
\r
1862 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1863 * case we want to add those holdings to the already received position.
\r
1867 if (appData.debugMode) {
\r
1868 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1869 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1870 setbuf(debugFP, NULL);
\r
1872 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1873 switch(newVariant) {
\r
1874 case VariantShogi:
\r
1875 newWidth = 9; newHeight = 9;
\r
1876 gameInfo.holdingsSize = 7;
\r
1877 case VariantBughouse:
\r
1878 case VariantCrazyhouse:
\r
1879 newHoldingsWidth = 2; break;
\r
1881 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1884 if(newWidth != gameInfo.boardWidth ||
\r
1885 newHeight != gameInfo.boardHeight ||
\r
1886 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1888 /* shift position to new playing area, if needed */
\r
1889 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1890 for(i=0; i<BOARD_HEIGHT; i++)
\r
1891 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1892 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1894 for(i=0; i<newHeight; i++) {
\r
1895 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1896 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1898 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1899 for(i=0; i<BOARD_HEIGHT; i++)
\r
1900 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1901 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1905 gameInfo.boardWidth = newWidth;
\r
1906 gameInfo.boardHeight = newHeight;
\r
1907 gameInfo.holdingsWidth = newHoldingsWidth;
\r
1908 gameInfo.variant = newVariant;
\r
1909 InitDrawingSizes(-2, 0);
\r
1911 /* [HGM] The following should definitely be solved in a better way */
\r
1913 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
1914 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
1915 saveEP = epStatus[0];
\r
1917 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
1918 forwardMostMove = backwardMostMove =
\r
1919 currentMove = oldCurrentMove; /* InitPos reset this, but we need still to redraw it */
\r
1921 epStatus[0] = saveEP;
\r
1922 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
1923 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
1925 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
1928 static int loggedOn = FALSE;
\r
1930 /*-- Game start info cache: --*/
\r
1932 char gs_kind[MSG_SIZ];
\r
1933 static char player1Name[128] = "";
\r
1934 static char player2Name[128] = "";
\r
1935 static int player1Rating = -1;
\r
1936 static int player2Rating = -1;
\r
1937 /*----------------------------*/
\r
1939 ColorClass curColor = ColorNormal;
\r
1942 read_from_ics(isr, closure, data, count, error)
\r
1943 InputSourceRef isr;
\r
1949 #define BUF_SIZE 8192
\r
1950 #define STARTED_NONE 0
\r
1951 #define STARTED_MOVES 1
\r
1952 #define STARTED_BOARD 2
\r
1953 #define STARTED_OBSERVE 3
\r
1954 #define STARTED_HOLDINGS 4
\r
1955 #define STARTED_CHATTER 5
\r
1956 #define STARTED_COMMENT 6
\r
1957 #define STARTED_MOVES_NOHIDE 7
\r
1959 static int started = STARTED_NONE;
\r
1960 static char parse[20000];
\r
1961 static int parse_pos = 0;
\r
1962 static char buf[BUF_SIZE + 1];
\r
1963 static int firstTime = TRUE, intfSet = FALSE;
\r
1964 static ColorClass prevColor = ColorNormal;
\r
1965 static int savingComment = FALSE;
\r
1974 if (appData.debugMode) {
\r
1976 fprintf(debugFP, "<ICS: ");
\r
1977 show_bytes(debugFP, data, count);
\r
1978 fprintf(debugFP, "\n");
\r
1983 if (appData.debugMode) { int f = forwardMostMove;
\r
1984 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
1985 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
1988 /* If last read ended with a partial line that we couldn't parse,
\r
1989 prepend it to the new read and try again. */
\r
1990 if (leftover_len > 0) {
\r
1991 for (i=0; i<leftover_len; i++)
\r
1992 buf[i] = buf[leftover_start + i];
\r
1995 /* Copy in new characters, removing nulls and \r's */
\r
1996 buf_len = leftover_len;
\r
1997 for (i = 0; i < count; i++) {
\r
1998 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1999 buf[buf_len++] = data[i];
\r
2002 buf[buf_len] = NULLCHAR;
\r
2003 next_out = leftover_len;
\r
2004 leftover_start = 0;
\r
2007 while (i < buf_len) {
\r
2008 /* Deal with part of the TELNET option negotiation
\r
2009 protocol. We refuse to do anything beyond the
\r
2010 defaults, except that we allow the WILL ECHO option,
\r
2011 which ICS uses to turn off password echoing when we are
\r
2012 directly connected to it. We reject this option
\r
2013 if localLineEditing mode is on (always on in xboard)
\r
2014 and we are talking to port 23, which might be a real
\r
2015 telnet server that will try to keep WILL ECHO on permanently.
\r
2017 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2018 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2019 unsigned char option;
\r
2021 switch ((unsigned char) buf[++i]) {
\r
2023 if (appData.debugMode)
\r
2024 fprintf(debugFP, "\n<WILL ");
\r
2025 switch (option = (unsigned char) buf[++i]) {
\r
2027 if (appData.debugMode)
\r
2028 fprintf(debugFP, "ECHO ");
\r
2029 /* Reply only if this is a change, according
\r
2030 to the protocol rules. */
\r
2031 if (remoteEchoOption) break;
\r
2032 if (appData.localLineEditing &&
\r
2033 atoi(appData.icsPort) == TN_PORT) {
\r
2034 TelnetRequest(TN_DONT, TN_ECHO);
\r
2037 TelnetRequest(TN_DO, TN_ECHO);
\r
2038 remoteEchoOption = TRUE;
\r
2042 if (appData.debugMode)
\r
2043 fprintf(debugFP, "%d ", option);
\r
2044 /* Whatever this is, we don't want it. */
\r
2045 TelnetRequest(TN_DONT, option);
\r
2050 if (appData.debugMode)
\r
2051 fprintf(debugFP, "\n<WONT ");
\r
2052 switch (option = (unsigned char) buf[++i]) {
\r
2054 if (appData.debugMode)
\r
2055 fprintf(debugFP, "ECHO ");
\r
2056 /* Reply only if this is a change, according
\r
2057 to the protocol rules. */
\r
2058 if (!remoteEchoOption) break;
\r
2060 TelnetRequest(TN_DONT, TN_ECHO);
\r
2061 remoteEchoOption = FALSE;
\r
2064 if (appData.debugMode)
\r
2065 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2066 /* Whatever this is, it must already be turned
\r
2067 off, because we never agree to turn on
\r
2068 anything non-default, so according to the
\r
2069 protocol rules, we don't reply. */
\r
2074 if (appData.debugMode)
\r
2075 fprintf(debugFP, "\n<DO ");
\r
2076 switch (option = (unsigned char) buf[++i]) {
\r
2078 /* Whatever this is, we refuse to do it. */
\r
2079 if (appData.debugMode)
\r
2080 fprintf(debugFP, "%d ", option);
\r
2081 TelnetRequest(TN_WONT, option);
\r
2086 if (appData.debugMode)
\r
2087 fprintf(debugFP, "\n<DONT ");
\r
2088 switch (option = (unsigned char) buf[++i]) {
\r
2090 if (appData.debugMode)
\r
2091 fprintf(debugFP, "%d ", option);
\r
2092 /* Whatever this is, we are already not doing
\r
2093 it, because we never agree to do anything
\r
2094 non-default, so according to the protocol
\r
2095 rules, we don't reply. */
\r
2100 if (appData.debugMode)
\r
2101 fprintf(debugFP, "\n<IAC ");
\r
2102 /* Doubled IAC; pass it through */
\r
2106 if (appData.debugMode)
\r
2107 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2108 /* Drop all other telnet commands on the floor */
\r
2111 if (oldi > next_out)
\r
2112 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2113 if (++i > next_out)
\r
2118 /* OK, this at least will *usually* work */
\r
2119 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2123 if (loggedOn && !intfSet) {
\r
2124 if (ics_type == ICS_ICC) {
\r
2126 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2129 } else if (ics_type == ICS_CHESSNET) {
\r
2130 sprintf(str, "/style 12\n");
\r
2132 strcpy(str, "alias $ @\n$set interface ");
\r
2133 strcat(str, programVersion);
\r
2134 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2136 strcat(str, "$iset nohighlight 1\n");
\r
2138 strcat(str, "$iset lock 1\n$style 12\n");
\r
2144 if (started == STARTED_COMMENT) {
\r
2145 /* Accumulate characters in comment */
\r
2146 parse[parse_pos++] = buf[i];
\r
2147 if (buf[i] == '\n') {
\r
2148 parse[parse_pos] = NULLCHAR;
\r
2149 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2150 started = STARTED_NONE;
\r
2152 /* Don't match patterns against characters in chatter */
\r
2157 if (started == STARTED_CHATTER) {
\r
2158 if (buf[i] != '\n') {
\r
2159 /* Don't match patterns against characters in chatter */
\r
2163 started = STARTED_NONE;
\r
2166 /* Kludge to deal with rcmd protocol */
\r
2167 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2168 DisplayFatalError(&buf[1], 0, 1);
\r
2171 firstTime = FALSE;
\r
2174 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2175 ics_type = ICS_ICC;
\r
2177 if (appData.debugMode)
\r
2178 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2181 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2182 ics_type = ICS_FICS;
\r
2184 if (appData.debugMode)
\r
2185 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2188 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2189 ics_type = ICS_CHESSNET;
\r
2191 if (appData.debugMode)
\r
2192 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2197 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2198 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2199 looking_at(buf, &i, "will be \"*\""))) {
\r
2200 strcpy(ics_handle, star_match[0]);
\r
2204 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2205 char buf[MSG_SIZ];
\r
2206 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2207 DisplayIcsInteractionTitle(buf);
\r
2208 have_set_title = TRUE;
\r
2211 /* skip finger notes */
\r
2212 if (started == STARTED_NONE &&
\r
2213 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2214 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2215 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2216 started = STARTED_CHATTER;
\r
2221 /* skip formula vars */
\r
2222 if (started == STARTED_NONE &&
\r
2223 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2224 started = STARTED_CHATTER;
\r
2230 if (appData.zippyTalk || appData.zippyPlay) {
\r
2232 if (ZippyControl(buf, &i) ||
\r
2233 ZippyConverse(buf, &i) ||
\r
2234 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2240 if (/* Don't color "message" or "messages" output */
\r
2241 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2242 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2243 looking_at(buf, &i, "--* (*:*): ") ||
\r
2244 /* Regular tells and says */
\r
2245 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2246 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2247 looking_at(buf, &i, "* says: ") ||
\r
2248 /* Message notifications (same color as tells) */
\r
2249 looking_at(buf, &i, "* has left a message ") ||
\r
2250 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2251 /* Whispers and kibitzes */
\r
2252 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2253 looking_at(buf, &i, "* kibitzes: ") ||
\r
2254 /* Channel tells */
\r
2255 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2257 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2258 /* Avoid "tells you:" spoofs in channels */
\r
2261 if (star_match[0][0] == NULLCHAR ||
\r
2262 strchr(star_match[0], ' ') ||
\r
2263 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2264 /* Reject bogus matches */
\r
2267 if (appData.colorize) {
\r
2268 if (oldi > next_out) {
\r
2269 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2274 Colorize(ColorTell, FALSE);
\r
2275 curColor = ColorTell;
\r
2278 Colorize(ColorKibitz, FALSE);
\r
2279 curColor = ColorKibitz;
\r
2282 p = strrchr(star_match[1], '(');
\r
2284 p = star_match[1];
\r
2288 if (atoi(p) == 1) {
\r
2289 Colorize(ColorChannel1, FALSE);
\r
2290 curColor = ColorChannel1;
\r
2292 Colorize(ColorChannel, FALSE);
\r
2293 curColor = ColorChannel;
\r
2297 curColor = ColorNormal;
\r
2301 if (started == STARTED_NONE && appData.autoComment &&
\r
2302 (gameMode == IcsObserving ||
\r
2303 gameMode == IcsPlayingWhite ||
\r
2304 gameMode == IcsPlayingBlack)) {
\r
2305 parse_pos = i - oldi;
\r
2306 memcpy(parse, &buf[oldi], parse_pos);
\r
2307 parse[parse_pos] = NULLCHAR;
\r
2308 started = STARTED_COMMENT;
\r
2309 savingComment = TRUE;
\r
2311 started = STARTED_CHATTER;
\r
2312 savingComment = FALSE;
\r
2319 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2320 looking_at(buf, &i, "* c-shouts: ")) {
\r
2321 if (appData.colorize) {
\r
2322 if (oldi > next_out) {
\r
2323 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2326 Colorize(ColorSShout, FALSE);
\r
2327 curColor = ColorSShout;
\r
2330 started = STARTED_CHATTER;
\r
2334 if (looking_at(buf, &i, "--->")) {
\r
2339 if (looking_at(buf, &i, "* shouts: ") ||
\r
2340 looking_at(buf, &i, "--> ")) {
\r
2341 if (appData.colorize) {
\r
2342 if (oldi > next_out) {
\r
2343 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2346 Colorize(ColorShout, FALSE);
\r
2347 curColor = ColorShout;
\r
2350 started = STARTED_CHATTER;
\r
2354 if (looking_at( buf, &i, "Challenge:")) {
\r
2355 if (appData.colorize) {
\r
2356 if (oldi > next_out) {
\r
2357 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2360 Colorize(ColorChallenge, FALSE);
\r
2361 curColor = ColorChallenge;
\r
2367 if (looking_at(buf, &i, "* offers you") ||
\r
2368 looking_at(buf, &i, "* offers to be") ||
\r
2369 looking_at(buf, &i, "* would like to") ||
\r
2370 looking_at(buf, &i, "* requests to") ||
\r
2371 looking_at(buf, &i, "Your opponent offers") ||
\r
2372 looking_at(buf, &i, "Your opponent requests")) {
\r
2374 if (appData.colorize) {
\r
2375 if (oldi > next_out) {
\r
2376 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2379 Colorize(ColorRequest, FALSE);
\r
2380 curColor = ColorRequest;
\r
2385 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2386 if (appData.colorize) {
\r
2387 if (oldi > next_out) {
\r
2388 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2391 Colorize(ColorSeek, FALSE);
\r
2392 curColor = ColorSeek;
\r
2398 if (looking_at(buf, &i, "\\ ")) {
\r
2399 if (prevColor != ColorNormal) {
\r
2400 if (oldi > next_out) {
\r
2401 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2404 Colorize(prevColor, TRUE);
\r
2405 curColor = prevColor;
\r
2407 if (savingComment) {
\r
2408 parse_pos = i - oldi;
\r
2409 memcpy(parse, &buf[oldi], parse_pos);
\r
2410 parse[parse_pos] = NULLCHAR;
\r
2411 started = STARTED_COMMENT;
\r
2413 started = STARTED_CHATTER;
\r
2418 if (looking_at(buf, &i, "Black Strength :") ||
\r
2419 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2420 looking_at(buf, &i, "<10>") ||
\r
2421 looking_at(buf, &i, "#@#")) {
\r
2422 /* Wrong board style */
\r
2424 SendToICS(ics_prefix);
\r
2425 SendToICS("set style 12\n");
\r
2426 SendToICS(ics_prefix);
\r
2427 SendToICS("refresh\n");
\r
2431 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2433 have_sent_ICS_logon = 1;
\r
2437 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2438 (looking_at(buf, &i, "\n<12> ") ||
\r
2439 looking_at(buf, &i, "<12> "))) {
\r
2441 if (oldi > next_out) {
\r
2442 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2445 started = STARTED_BOARD;
\r
2450 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2451 looking_at(buf, &i, "<b1> ")) {
\r
2452 if (oldi > next_out) {
\r
2453 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2456 started = STARTED_HOLDINGS;
\r
2461 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2463 /* Header for a move list -- first line */
\r
2465 switch (ics_getting_history) {
\r
2467 switch (gameMode) {
\r
2469 case BeginningOfGame:
\r
2470 /* User typed "moves" or "oldmoves" while we
\r
2471 were idle. Pretend we asked for these
\r
2472 moves and soak them up so user can step
\r
2473 through them and/or save them.
\r
2475 Reset(FALSE, TRUE);
\r
2476 gameMode = IcsObserving;
\r
2479 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2481 case EditGame: /*?*/
\r
2482 case EditPosition: /*?*/
\r
2483 /* Should above feature work in these modes too? */
\r
2484 /* For now it doesn't */
\r
2485 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2488 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2493 /* Is this the right one? */
\r
2494 if (gameInfo.white && gameInfo.black &&
\r
2495 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2496 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2498 ics_getting_history = H_GOT_REQ_HEADER;
\r
2501 case H_GOT_REQ_HEADER:
\r
2502 case H_GOT_UNREQ_HEADER:
\r
2503 case H_GOT_UNWANTED_HEADER:
\r
2504 case H_GETTING_MOVES:
\r
2505 /* Should not happen */
\r
2506 DisplayError("Error gathering move list: two headers", 0);
\r
2507 ics_getting_history = H_FALSE;
\r
2511 /* Save player ratings into gameInfo if needed */
\r
2512 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2513 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2514 (gameInfo.whiteRating == -1 ||
\r
2515 gameInfo.blackRating == -1)) {
\r
2517 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2518 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2519 if (appData.debugMode)
\r
2520 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2521 gameInfo.whiteRating, gameInfo.blackRating);
\r
2526 if (looking_at(buf, &i,
\r
2527 "* * match, initial time: * minute*, increment: * second")) {
\r
2528 /* Header for a move list -- second line */
\r
2529 /* Initial board will follow if this is a wild game */
\r
2530 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2531 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2532 gameInfo.event = StrSave(str);
\r
2533 /* [HGM] we switched variant. Translate boards if needed. */
\r
2534 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2538 if (looking_at(buf, &i, "Move ")) {
\r
2539 /* Beginning of a move list */
\r
2540 switch (ics_getting_history) {
\r
2542 /* Normally should not happen */
\r
2543 /* Maybe user hit reset while we were parsing */
\r
2546 /* Happens if we are ignoring a move list that is not
\r
2547 * the one we just requested. Common if the user
\r
2548 * tries to observe two games without turning off
\r
2551 case H_GETTING_MOVES:
\r
2552 /* Should not happen */
\r
2553 DisplayError("Error gathering move list: nested", 0);
\r
2554 ics_getting_history = H_FALSE;
\r
2556 case H_GOT_REQ_HEADER:
\r
2557 ics_getting_history = H_GETTING_MOVES;
\r
2558 started = STARTED_MOVES;
\r
2560 if (oldi > next_out) {
\r
2561 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2564 case H_GOT_UNREQ_HEADER:
\r
2565 ics_getting_history = H_GETTING_MOVES;
\r
2566 started = STARTED_MOVES_NOHIDE;
\r
2569 case H_GOT_UNWANTED_HEADER:
\r
2570 ics_getting_history = H_FALSE;
\r
2576 if (looking_at(buf, &i, "% ") ||
\r
2577 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2578 && looking_at(buf, &i, "}*"))) {
\r
2579 savingComment = FALSE;
\r
2580 switch (started) {
\r
2581 case STARTED_MOVES:
\r
2582 case STARTED_MOVES_NOHIDE:
\r
2583 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2584 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2585 ParseGameHistory(parse);
\r
2587 if (appData.zippyPlay && first.initDone) {
\r
2588 FeedMovesToProgram(&first, forwardMostMove);
\r
2589 if (gameMode == IcsPlayingWhite) {
\r
2590 if (WhiteOnMove(forwardMostMove)) {
\r
2591 if (first.sendTime) {
\r
2592 if (first.useColors) {
\r
2593 SendToProgram("black\n", &first);
\r
2595 SendTimeRemaining(&first, TRUE);
\r
2597 if (first.useColors) {
\r
2598 SendToProgram("white\ngo\n", &first);
\r
2600 SendToProgram("go\n", &first);
\r
2602 first.maybeThinking = TRUE;
\r
2604 if (first.usePlayother) {
\r
2605 if (first.sendTime) {
\r
2606 SendTimeRemaining(&first, TRUE);
\r
2608 SendToProgram("playother\n", &first);
\r
2609 firstMove = FALSE;
\r
2614 } else if (gameMode == IcsPlayingBlack) {
\r
2615 if (!WhiteOnMove(forwardMostMove)) {
\r
2616 if (first.sendTime) {
\r
2617 if (first.useColors) {
\r
2618 SendToProgram("white\n", &first);
\r
2620 SendTimeRemaining(&first, FALSE);
\r
2622 if (first.useColors) {
\r
2623 SendToProgram("black\ngo\n", &first);
\r
2625 SendToProgram("go\n", &first);
\r
2627 first.maybeThinking = TRUE;
\r
2629 if (first.usePlayother) {
\r
2630 if (first.sendTime) {
\r
2631 SendTimeRemaining(&first, FALSE);
\r
2633 SendToProgram("playother\n", &first);
\r
2634 firstMove = FALSE;
\r
2642 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2643 /* Moves came from oldmoves or moves command
\r
2644 while we weren't doing anything else.
\r
2646 currentMove = forwardMostMove;
\r
2647 ClearHighlights();/*!!could figure this out*/
\r
2648 flipView = appData.flipView;
\r
2649 DrawPosition(FALSE, boards[currentMove]);
\r
2650 DisplayBothClocks();
\r
2651 sprintf(str, "%s vs. %s",
\r
2652 gameInfo.white, gameInfo.black);
\r
2653 DisplayTitle(str);
\r
2654 gameMode = IcsIdle;
\r
2656 /* Moves were history of an active game */
\r
2657 if (gameInfo.resultDetails != NULL) {
\r
2658 free(gameInfo.resultDetails);
\r
2659 gameInfo.resultDetails = NULL;
\r
2662 HistorySet(parseList, backwardMostMove,
\r
2663 forwardMostMove, currentMove-1);
\r
2664 DisplayMove(currentMove - 1);
\r
2665 if (started == STARTED_MOVES) next_out = i;
\r
2666 started = STARTED_NONE;
\r
2667 ics_getting_history = H_FALSE;
\r
2670 case STARTED_OBSERVE:
\r
2671 started = STARTED_NONE;
\r
2672 SendToICS(ics_prefix);
\r
2673 SendToICS("refresh\n");
\r
2682 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2683 started == STARTED_HOLDINGS ||
\r
2684 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2685 /* Accumulate characters in move list or board */
\r
2686 parse[parse_pos++] = buf[i];
\r
2689 /* Start of game messages. Mostly we detect start of game
\r
2690 when the first board image arrives. On some versions
\r
2691 of the ICS, though, we need to do a "refresh" after starting
\r
2692 to observe in order to get the current board right away. */
\r
2693 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2694 started = STARTED_OBSERVE;
\r
2698 /* Handle auto-observe */
\r
2699 if (appData.autoObserve &&
\r
2700 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2701 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2703 /* Choose the player that was highlighted, if any. */
\r
2704 if (star_match[0][0] == '\033' ||
\r
2705 star_match[1][0] != '\033') {
\r
2706 player = star_match[0];
\r
2708 player = star_match[2];
\r
2710 sprintf(str, "%sobserve %s\n",
\r
2711 ics_prefix, StripHighlightAndTitle(player));
\r
2714 /* Save ratings from notify string */
\r
2715 strcpy(player1Name, star_match[0]);
\r
2716 player1Rating = string_to_rating(star_match[1]);
\r
2717 strcpy(player2Name, star_match[2]);
\r
2718 player2Rating = string_to_rating(star_match[3]);
\r
2720 if (appData.debugMode)
\r
2722 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2723 player1Name, player1Rating,
\r
2724 player2Name, player2Rating);
\r
2729 /* Deal with automatic examine mode after a game,
\r
2730 and with IcsObserving -> IcsExamining transition */
\r
2731 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2732 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2734 int gamenum = atoi(star_match[0]);
\r
2735 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2736 gamenum == ics_gamenum) {
\r
2737 /* We were already playing or observing this game;
\r
2738 no need to refetch history */
\r
2739 gameMode = IcsExamining;
\r
2741 pauseExamForwardMostMove = forwardMostMove;
\r
2742 } else if (currentMove < forwardMostMove) {
\r
2743 ForwardInner(forwardMostMove);
\r
2746 /* I don't think this case really can happen */
\r
2747 SendToICS(ics_prefix);
\r
2748 SendToICS("refresh\n");
\r
2753 /* Error messages */
\r
2754 if (ics_user_moved) {
\r
2755 if (looking_at(buf, &i, "Illegal move") ||
\r
2756 looking_at(buf, &i, "Not a legal move") ||
\r
2757 looking_at(buf, &i, "Your king is in check") ||
\r
2758 looking_at(buf, &i, "It isn't your turn") ||
\r
2759 looking_at(buf, &i, "It is not your move")) {
\r
2760 /* Illegal move */
\r
2761 ics_user_moved = 0;
\r
2762 if (forwardMostMove > backwardMostMove) {
\r
2763 currentMove = --forwardMostMove;
\r
2764 DisplayMove(currentMove - 1); /* before DMError */
\r
2765 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2766 DrawPosition(FALSE, boards[currentMove]);
\r
2768 DisplayBothClocks();
\r
2774 if (looking_at(buf, &i, "still have time") ||
\r
2775 looking_at(buf, &i, "not out of time") ||
\r
2776 looking_at(buf, &i, "either player is out of time") ||
\r
2777 looking_at(buf, &i, "has timeseal; checking")) {
\r
2778 /* We must have called his flag a little too soon */
\r
2779 whiteFlag = blackFlag = FALSE;
\r
2783 if (looking_at(buf, &i, "added * seconds to") ||
\r
2784 looking_at(buf, &i, "seconds were added to")) {
\r
2785 /* Update the clocks */
\r
2786 SendToICS(ics_prefix);
\r
2787 SendToICS("refresh\n");
\r
2791 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2792 ics_clock_paused = TRUE;
\r
2797 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2798 ics_clock_paused = FALSE;
\r
2803 /* Grab player ratings from the Creating: message.
\r
2804 Note we have to check for the special case when
\r
2805 the ICS inserts things like [white] or [black]. */
\r
2806 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2807 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2809 0 player 1 name (not necessarily white)
\r
2811 2 empty, white, or black (IGNORED)
\r
2812 3 player 2 name (not necessarily black)
\r
2815 The names/ratings are sorted out when the game
\r
2816 actually starts (below).
\r
2818 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2819 player1Rating = string_to_rating(star_match[1]);
\r
2820 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2821 player2Rating = string_to_rating(star_match[4]);
\r
2823 if (appData.debugMode)
\r
2825 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2826 player1Name, player1Rating,
\r
2827 player2Name, player2Rating);
\r
2832 /* Improved generic start/end-of-game messages */
\r
2833 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2834 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2835 /* If tkind == 0: */
\r
2836 /* star_match[0] is the game number */
\r
2837 /* [1] is the white player's name */
\r
2838 /* [2] is the black player's name */
\r
2839 /* For end-of-game: */
\r
2840 /* [3] is the reason for the game end */
\r
2841 /* [4] is a PGN end game-token, preceded by " " */
\r
2842 /* For start-of-game: */
\r
2843 /* [3] begins with "Creating" or "Continuing" */
\r
2844 /* [4] is " *" or empty (don't care). */
\r
2845 int gamenum = atoi(star_match[0]);
\r
2846 char *whitename, *blackname, *why, *endtoken;
\r
2847 ChessMove endtype = (ChessMove) 0;
\r
2850 whitename = star_match[1];
\r
2851 blackname = star_match[2];
\r
2852 why = star_match[3];
\r
2853 endtoken = star_match[4];
\r
2855 whitename = star_match[1];
\r
2856 blackname = star_match[3];
\r
2857 why = star_match[5];
\r
2858 endtoken = star_match[6];
\r
2861 /* Game start messages */
\r
2862 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2863 strncmp(why, "Continuing ", 11) == 0) {
\r
2864 gs_gamenum = gamenum;
\r
2865 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2867 if (appData.zippyPlay) {
\r
2868 ZippyGameStart(whitename, blackname);
\r
2874 /* Game end messages */
\r
2875 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2876 ics_gamenum != gamenum) {
\r
2879 while (endtoken[0] == ' ') endtoken++;
\r
2880 switch (endtoken[0]) {
\r
2883 endtype = GameUnfinished;
\r
2886 endtype = BlackWins;
\r
2889 if (endtoken[1] == '/')
\r
2890 endtype = GameIsDrawn;
\r
2892 endtype = WhiteWins;
\r
2895 GameEnds(endtype, why, GE_ICS);
\r
2897 if (appData.zippyPlay && first.initDone) {
\r
2898 ZippyGameEnd(endtype, why);
\r
2899 if (first.pr == NULL) {
\r
2900 /* Start the next process early so that we'll
\r
2901 be ready for the next challenge */
\r
2902 StartChessProgram(&first);
\r
2904 /* Send "new" early, in case this command takes
\r
2905 a long time to finish, so that we'll be ready
\r
2906 for the next challenge. */
\r
2907 Reset(TRUE, TRUE);
\r
2913 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2914 looking_at(buf, &i, "no longer observing game *") ||
\r
2915 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2916 if (gameMode == IcsObserving &&
\r
2917 atoi(star_match[0]) == ics_gamenum)
\r
2920 gameMode = IcsIdle;
\r
2922 ics_user_moved = FALSE;
\r
2927 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2928 if (gameMode == IcsExamining &&
\r
2929 atoi(star_match[0]) == ics_gamenum)
\r
2931 gameMode = IcsIdle;
\r
2933 ics_user_moved = FALSE;
\r
2938 /* Advance leftover_start past any newlines we find,
\r
2939 so only partial lines can get reparsed */
\r
2940 if (looking_at(buf, &i, "\n")) {
\r
2941 prevColor = curColor;
\r
2942 if (curColor != ColorNormal) {
\r
2943 if (oldi > next_out) {
\r
2944 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2947 Colorize(ColorNormal, FALSE);
\r
2948 curColor = ColorNormal;
\r
2950 if (started == STARTED_BOARD) {
\r
2951 started = STARTED_NONE;
\r
2952 parse[parse_pos] = NULLCHAR;
\r
2953 ParseBoard12(parse);
\r
2954 ics_user_moved = 0;
\r
2956 /* Send premove here */
\r
2957 if (appData.premove) {
\r
2958 char str[MSG_SIZ];
\r
2959 if (currentMove == 0 &&
\r
2960 gameMode == IcsPlayingWhite &&
\r
2961 appData.premoveWhite) {
\r
2962 sprintf(str, "%s%s\n", ics_prefix,
\r
2963 appData.premoveWhiteText);
\r
2964 if (appData.debugMode)
\r
2965 fprintf(debugFP, "Sending premove:\n");
\r
2967 } else if (currentMove == 1 &&
\r
2968 gameMode == IcsPlayingBlack &&
\r
2969 appData.premoveBlack) {
\r
2970 sprintf(str, "%s%s\n", ics_prefix,
\r
2971 appData.premoveBlackText);
\r
2972 if (appData.debugMode)
\r
2973 fprintf(debugFP, "Sending premove:\n");
\r
2975 } else if (gotPremove) {
\r
2977 ClearPremoveHighlights();
\r
2978 if (appData.debugMode)
\r
2979 fprintf(debugFP, "Sending premove:\n");
\r
2980 UserMoveEvent(premoveFromX, premoveFromY,
\r
2981 premoveToX, premoveToY,
\r
2982 premovePromoChar);
\r
2986 /* Usually suppress following prompt */
\r
2987 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2988 if (looking_at(buf, &i, "*% ")) {
\r
2989 savingComment = FALSE;
\r
2993 } else if (started == STARTED_HOLDINGS) {
\r
2995 char new_piece[MSG_SIZ];
\r
2996 started = STARTED_NONE;
\r
2997 parse[parse_pos] = NULLCHAR;
\r
2998 if (appData.debugMode)
\r
2999 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3000 parse, currentMove);
\r
3001 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3002 gamenum == ics_gamenum) {
\r
3003 if (gameInfo.variant == VariantNormal) {
\r
3004 /* [HGM] We seem to switch variant during a game!
\r
3005 * Presumably no holdings were displayed, so we have
\r
3006 * to move the position two files to the right to
\r
3007 * create room for them!
\r
3009 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3010 /* Get a move list just to see the header, which
\r
3011 will tell us whether this is really bug or zh */
\r
3012 if (ics_getting_history == H_FALSE) {
\r
3013 ics_getting_history = H_REQUESTED;
\r
3014 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3018 new_piece[0] = NULLCHAR;
\r
3019 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3020 &gamenum, white_holding, black_holding,
\r
3022 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3023 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3024 /* [HGM] copy holdings to board holdings area */
\r
3025 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3026 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3028 if (appData.zippyPlay && first.initDone) {
\r
3029 ZippyHoldings(white_holding, black_holding,
\r
3033 if (tinyLayout || smallLayout) {
\r
3034 char wh[16], bh[16];
\r
3035 PackHolding(wh, white_holding);
\r
3036 PackHolding(bh, black_holding);
\r
3037 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3038 gameInfo.white, gameInfo.black);
\r
3040 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3041 gameInfo.white, white_holding,
\r
3042 gameInfo.black, black_holding);
\r
3045 DrawPosition(FALSE, boards[currentMove]);
\r
3046 DisplayTitle(str);
\r
3048 /* Suppress following prompt */
\r
3049 if (looking_at(buf, &i, "*% ")) {
\r
3050 savingComment = FALSE;
\r
3057 i++; /* skip unparsed character and loop back */
\r
3060 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
3061 started != STARTED_HOLDINGS && i > next_out) {
\r
3062 SendToPlayer(&buf[next_out], i - next_out);
\r
3066 leftover_len = buf_len - leftover_start;
\r
3067 /* if buffer ends with something we couldn't parse,
\r
3068 reparse it after appending the next read */
\r
3070 } else if (count == 0) {
\r
3071 RemoveInputSource(isr);
\r
3072 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
3074 DisplayFatalError("Error reading from ICS", error, 1);
\r
3079 /* Board style 12 looks like this:
\r
3081 <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
3083 * The "<12> " is stripped before it gets to this routine. The two
\r
3084 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3085 * some chess servers may not have them, or may have only the first.
\r
3086 * Additional trailing fields may be added in the future.
\r
3089 #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
3091 #define RELATION_OBSERVING_PLAYED 0
\r
3092 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3093 #define RELATION_PLAYING_MYMOVE 1
\r
3094 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3095 #define RELATION_EXAMINING 2
\r
3096 #define RELATION_ISOLATED_BOARD -3
\r
3097 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3100 ParseBoard12(string)
\r
3103 GameMode newGameMode;
\r
3104 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
3105 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
3106 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3107 char to_play, board_chars[72];
\r
3108 char move_str[500], str[500], elapsed_time[500];
\r
3109 char black[32], white[32];
\r
3111 int prevMove = currentMove;
\r
3113 ChessMove moveType;
\r
3114 int fromX, fromY, toX, toY;
\r
3117 fromX = fromY = toX = toY = -1;
\r
3121 if (appData.debugMode)
\r
3122 fprintf(debugFP, "Parsing board: %s\n", string);
\r
3124 move_str[0] = NULLCHAR;
\r
3125 elapsed_time[0] = NULLCHAR;
\r
3126 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
3127 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3128 &gamenum, white, black, &relation, &basetime, &increment,
\r
3129 &white_stren, &black_stren, &white_time, &black_time,
\r
3130 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3134 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
3135 DisplayError(str, 0);
\r
3139 /* Convert the move number to internal form */
\r
3140 moveNum = (moveNum - 1) * 2;
\r
3141 if (to_play == 'B') moveNum++;
\r
3142 if (moveNum >= MAX_MOVES) {
\r
3143 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3148 switch (relation) {
\r
3149 case RELATION_OBSERVING_PLAYED:
\r
3150 case RELATION_OBSERVING_STATIC:
\r
3151 if (gamenum == -1) {
\r
3152 /* Old ICC buglet */
\r
3153 relation = RELATION_OBSERVING_STATIC;
\r
3155 newGameMode = IcsObserving;
\r
3157 case RELATION_PLAYING_MYMOVE:
\r
3158 case RELATION_PLAYING_NOTMYMOVE:
\r
3160 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3161 IcsPlayingWhite : IcsPlayingBlack;
\r
3163 case RELATION_EXAMINING:
\r
3164 newGameMode = IcsExamining;
\r
3166 case RELATION_ISOLATED_BOARD:
\r
3168 /* Just display this board. If user was doing something else,
\r
3169 we will forget about it until the next board comes. */
\r
3170 newGameMode = IcsIdle;
\r
3172 case RELATION_STARTING_POSITION:
\r
3173 newGameMode = gameMode;
\r
3177 /* Modify behavior for initial board display on move listing
\r
3180 switch (ics_getting_history) {
\r
3184 case H_GOT_REQ_HEADER:
\r
3185 case H_GOT_UNREQ_HEADER:
\r
3186 /* This is the initial position of the current game */
\r
3187 gamenum = ics_gamenum;
\r
3188 moveNum = 0; /* old ICS bug workaround */
\r
3189 if (to_play == 'B') {
\r
3190 startedFromSetupPosition = TRUE;
\r
3191 blackPlaysFirst = TRUE;
\r
3193 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3194 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3195 if (currentMove == 0) currentMove = 1;
\r
3197 newGameMode = gameMode;
\r
3198 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3200 case H_GOT_UNWANTED_HEADER:
\r
3201 /* This is an initial board that we don't want */
\r
3203 case H_GETTING_MOVES:
\r
3204 /* Should not happen */
\r
3205 DisplayError("Error gathering move list: extra board", 0);
\r
3206 ics_getting_history = H_FALSE;
\r
3210 /* Take action if this is the first board of a new game, or of a
\r
3211 different game than is currently being displayed. */
\r
3212 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3213 relation == RELATION_ISOLATED_BOARD) {
\r
3215 /* Forget the old game and get the history (if any) of the new one */
\r
3216 if (gameMode != BeginningOfGame) {
\r
3217 Reset(FALSE, TRUE);
\r
3220 if (appData.autoRaiseBoard) BoardToTop();
\r
3222 if (gamenum == -1) {
\r
3223 newGameMode = IcsIdle;
\r
3224 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3225 appData.getMoveList) {
\r
3226 /* Need to get game history */
\r
3227 ics_getting_history = H_REQUESTED;
\r
3228 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3232 /* Initially flip the board to have black on the bottom if playing
\r
3233 black or if the ICS flip flag is set, but let the user change
\r
3234 it with the Flip View button. */
\r
3235 flipView = appData.autoFlipView ?
\r
3236 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3239 /* Done with values from previous mode; copy in new ones */
\r
3240 gameMode = newGameMode;
\r
3242 ics_gamenum = gamenum;
\r
3243 if (gamenum == gs_gamenum) {
\r
3244 int klen = strlen(gs_kind);
\r
3245 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3246 sprintf(str, "ICS %s", gs_kind);
\r
3247 gameInfo.event = StrSave(str);
\r
3249 gameInfo.event = StrSave("ICS game");
\r
3251 gameInfo.site = StrSave(appData.icsHost);
\r
3252 gameInfo.date = PGNDate();
\r
3253 gameInfo.round = StrSave("-");
\r
3254 gameInfo.white = StrSave(white);
\r
3255 gameInfo.black = StrSave(black);
\r
3256 timeControl = basetime * 60 * 1000;
\r
3257 timeControl_2 = 0;
\r
3258 timeIncrement = increment * 1000;
\r
3259 movesPerSession = 0;
\r
3260 gameInfo.timeControl = TimeControlTagValue();
\r
3261 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3262 if (appData.debugMode) {
\r
3263 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3264 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3265 setbuf(debugFP, NULL);
\r
3268 gameInfo.outOfBook = NULL;
\r
3270 /* Do we have the ratings? */
\r
3271 if (strcmp(player1Name, white) == 0 &&
\r
3272 strcmp(player2Name, black) == 0) {
\r
3273 if (appData.debugMode)
\r
3274 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3275 player1Rating, player2Rating);
\r
3276 gameInfo.whiteRating = player1Rating;
\r
3277 gameInfo.blackRating = player2Rating;
\r
3278 } else if (strcmp(player2Name, white) == 0 &&
\r
3279 strcmp(player1Name, black) == 0) {
\r
3280 if (appData.debugMode)
\r
3281 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3282 player2Rating, player1Rating);
\r
3283 gameInfo.whiteRating = player2Rating;
\r
3284 gameInfo.blackRating = player1Rating;
\r
3286 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3288 /* Silence shouts if requested */
\r
3289 if (appData.quietPlay &&
\r
3290 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3291 SendToICS(ics_prefix);
\r
3292 SendToICS("set shout 0\n");
\r
3296 /* Deal with midgame name changes */
\r
3298 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3299 if (gameInfo.white) free(gameInfo.white);
\r
3300 gameInfo.white = StrSave(white);
\r
3302 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3303 if (gameInfo.black) free(gameInfo.black);
\r
3304 gameInfo.black = StrSave(black);
\r
3308 /* Throw away game result if anything actually changes in examine mode */
\r
3309 if (gameMode == IcsExamining && !newGame) {
\r
3310 gameInfo.result = GameUnfinished;
\r
3311 if (gameInfo.resultDetails != NULL) {
\r
3312 free(gameInfo.resultDetails);
\r
3313 gameInfo.resultDetails = NULL;
\r
3317 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3318 in if they are in a different variation than we are. */
\r
3319 if (pauseExamInvalid) return;
\r
3320 if (pausing && gameMode == IcsExamining) {
\r
3321 if (moveNum <= pauseExamForwardMostMove) {
\r
3322 pauseExamInvalid = TRUE;
\r
3323 forwardMostMove = pauseExamForwardMostMove;
\r
3328 /* Parse the board */
\r
3329 for (k = 0; k < 8; k++) {
\r
3330 for (j = 0; j < 8; j++)
\r
3331 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3332 if(gameInfo.holdingsWidth > 1) {
\r
3333 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3334 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3337 CopyBoard(boards[moveNum], board);
\r
3338 if (moveNum == 0) {
\r
3339 startedFromSetupPosition =
\r
3340 !CompareBoards(board, initialPosition);
\r
3341 if(startedFromSetupPosition)
\r
3342 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3344 /* [HGM] variantswitch: remember the last initial position parsed, */
\r
3345 /* because it might have been parsed in the wrong variant, so that */
\r
3346 /* we can re-parse it once we know the proper variant (which might */
\r
3347 /* have different piece assignments for the same letters). */
\r
3348 if(moveNum == 0) strcpy(startBoard, string);
\r
3350 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3351 to make it also work for FRC opening positions. Note that board12
\r
3352 is really defective for later FRC positions, as it has no way to
\r
3353 indicate which Rook can castle if they are on the same side of King.
\r
3354 For the initial position we grant rights to the outermost Rooks,
\r
3355 and remember thos rights, and we then copy them on positions
\r
3356 later in an FRC game. This means WB might not recognize castlings with
\r
3357 Rooks that have moved back to their original position as illegal,
\r
3358 but in ICS mode that is not its job anyway.
\r
3360 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3363 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3364 if(board[0][i] == WhiteRook) j = i;
\r
3365 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3366 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3367 if(board[0][i] == WhiteRook) j = i;
\r
3368 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3369 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3370 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3371 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3372 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3373 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3374 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3376 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3377 if(board[0][k] == WhiteKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3378 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3379 if(board[BOARD_HEIGHT-1][k] == BlackKing)
\r
3380 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3382 r = castlingRights[moveNum][0] = initialRights[0];
\r
3383 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3384 r = castlingRights[moveNum][1] = initialRights[1];
\r
3385 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3386 r = castlingRights[moveNum][3] = initialRights[3];
\r
3387 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3388 r = castlingRights[moveNum][4] = initialRights[4];
\r
3389 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3390 /* wildcastle kludge: always assume King has rights */
\r
3391 r = castlingRights[moveNum][2] = initialRights[2];
\r
3392 r = castlingRights[moveNum][5] = initialRights[5];
\r
3394 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3395 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3398 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3399 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3400 /* This was an initial position from a move list, not
\r
3401 the current position */
\r
3405 /* Update currentMove and known move number limits */
\r
3406 newMove = newGame || moveNum > forwardMostMove;
\r
3408 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3409 if (gameMode == IcsExamining && moveNum == 0) {
\r
3410 /* Workaround for ICS limitation: we are not told the wild
\r
3411 type when starting to examine a game. But if we ask for
\r
3412 the move list, the move list header will tell us */
\r
3413 ics_getting_history = H_REQUESTED;
\r
3414 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3417 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3418 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3419 forwardMostMove = moveNum;
\r
3420 if (!pausing || currentMove > forwardMostMove)
\r
3421 currentMove = forwardMostMove;
\r
3423 /* New part of history that is not contiguous with old part */
\r
3424 if (pausing && gameMode == IcsExamining) {
\r
3425 pauseExamInvalid = TRUE;
\r
3426 forwardMostMove = pauseExamForwardMostMove;
\r
3429 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3430 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3431 ics_getting_history = H_REQUESTED;
\r
3432 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3437 /* Update the clocks */
\r
3438 if (strchr(elapsed_time, '.')) {
\r
3439 /* Time is in ms */
\r
3440 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3441 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3443 /* Time is in seconds */
\r
3444 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3445 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3450 if (appData.zippyPlay && newGame &&
\r
3451 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3452 gameMode != IcsExamining)
\r
3453 ZippyFirstBoard(moveNum, basetime, increment);
\r
3456 /* Put the move on the move list, first converting
\r
3457 to canonical algebraic form. */
\r
3458 if (moveNum > 0) {
\r
3459 if (appData.debugMode) {
\r
3460 if (appData.debugMode) { int f = forwardMostMove;
\r
3461 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3462 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3464 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3465 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3466 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3467 setbuf(debugFP, NULL);
\r
3469 if (moveNum <= backwardMostMove) {
\r
3470 /* We don't know what the board looked like before
\r
3471 this move. Punt. */
\r
3472 strcpy(parseList[moveNum - 1], move_str);
\r
3473 strcat(parseList[moveNum - 1], " ");
\r
3474 strcat(parseList[moveNum - 1], elapsed_time);
\r
3475 moveList[moveNum - 1][0] = NULLCHAR;
\r
3476 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3477 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3478 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3479 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3480 fromY, fromX, toY, toX, promoChar,
\r
3481 parseList[moveNum-1]);
\r
3482 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3483 castlingRights[moveNum]) ) {
\r
3485 case MT_STALEMATE:
\r
3489 if(gameInfo.variant != VariantShogi)
\r
3490 strcat(parseList[moveNum - 1], "+");
\r
3492 case MT_CHECKMATE:
\r
3493 strcat(parseList[moveNum - 1], "#");
\r
3496 strcat(parseList[moveNum - 1], " ");
\r
3497 strcat(parseList[moveNum - 1], elapsed_time);
\r
3498 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3499 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3500 strcat(moveList[moveNum - 1], "\n");
\r
3501 } else if (strcmp(move_str, "none") == 0) {
\r
3502 /* Again, we don't know what the board looked like;
\r
3503 this is really the start of the game. */
\r
3504 parseList[moveNum - 1][0] = NULLCHAR;
\r
3505 moveList[moveNum - 1][0] = NULLCHAR;
\r
3506 backwardMostMove = moveNum;
\r
3507 startedFromSetupPosition = TRUE;
\r
3508 fromX = fromY = toX = toY = -1;
\r
3510 /* Move from ICS was illegal!? Punt. */
\r
3511 if (appData.debugMode) {
\r
3512 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3513 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3516 if (appData.testLegality && appData.debugMode) {
\r
3517 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3518 DisplayError(str, 0);
\r
3521 strcpy(parseList[moveNum - 1], move_str);
\r
3522 strcat(parseList[moveNum - 1], " ");
\r
3523 strcat(parseList[moveNum - 1], elapsed_time);
\r
3524 moveList[moveNum - 1][0] = NULLCHAR;
\r
3525 fromX = fromY = toX = toY = -1;
\r
3527 if (appData.debugMode) {
\r
3528 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3529 setbuf(debugFP, NULL);
\r
3533 /* Send move to chess program (BEFORE animating it). */
\r
3534 if (appData.zippyPlay && !newGame && newMove &&
\r
3535 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3537 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3538 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3539 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3540 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3542 DisplayError(str, 0);
\r
3544 if (first.sendTime) {
\r
3545 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3547 SendMoveToProgram(moveNum - 1, &first);
\r
3549 firstMove = FALSE;
\r
3550 if (first.useColors) {
\r
3551 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3553 "black\ngo\n", &first);
\r
3555 SendToProgram("go\n", &first);
\r
3557 first.maybeThinking = TRUE;
\r
3560 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3561 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3562 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3563 DisplayError(str, 0);
\r
3565 SendMoveToProgram(moveNum - 1, &first);
\r
3572 if (moveNum > 0 && !gotPremove) {
\r
3573 /* If move comes from a remote source, animate it. If it
\r
3574 isn't remote, it will have already been animated. */
\r
3575 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3576 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3578 if (!pausing && appData.highlightLastMove) {
\r
3579 SetHighlights(fromX, fromY, toX, toY);
\r
3583 /* Start the clocks */
\r
3584 whiteFlag = blackFlag = FALSE;
\r
3585 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3586 if (ticking == 0) {
\r
3587 ics_clock_paused = TRUE;
\r
3589 } else if (ticking == 1) {
\r
3590 ics_clock_paused = FALSE;
\r
3592 if (gameMode == IcsIdle ||
\r
3593 relation == RELATION_OBSERVING_STATIC ||
\r
3594 relation == RELATION_EXAMINING ||
\r
3596 DisplayBothClocks();
\r
3600 /* Display opponents and material strengths */
\r
3601 if (gameInfo.variant != VariantBughouse &&
\r
3602 gameInfo.variant != VariantCrazyhouse) {
\r
3603 if (tinyLayout || smallLayout) {
\r
3604 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3605 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3606 basetime, increment);
\r
3608 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3609 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3610 basetime, increment);
\r
3612 DisplayTitle(str);
\r
3616 /* Display the board */
\r
3619 if (appData.premove)
\r
3620 if (!gotPremove ||
\r
3621 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3622 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3623 ClearPremoveHighlights();
\r
3625 DrawPosition(FALSE, boards[currentMove]);
\r
3626 DisplayMove(moveNum - 1);
\r
3627 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3631 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3635 GetMoveListEvent()
\r
3637 char buf[MSG_SIZ];
\r
3638 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3639 ics_getting_history = H_REQUESTED;
\r
3640 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3646 AnalysisPeriodicEvent(force)
\r
3649 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3650 && !force) || !appData.periodicUpdates)
\r
3653 /* Send . command to Crafty to collect stats */
\r
3654 SendToProgram(".\n", &first);
\r
3656 /* Don't send another until we get a response (this makes
\r
3657 us stop sending to old Crafty's which don't understand
\r
3658 the "." command (sending illegal cmds resets node count & time,
\r
3659 which looks bad)) */
\r
3660 programStats.ok_to_send = 0;
\r
3664 SendMoveToProgram(moveNum, cps)
\r
3666 ChessProgramState *cps;
\r
3668 char buf[MSG_SIZ];
\r
3670 if (cps->useUsermove) {
\r
3671 SendToProgram("usermove ", cps);
\r
3673 if (cps->useSAN) {
\r
3675 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3676 int len = space - parseList[moveNum];
\r
3677 memcpy(buf, parseList[moveNum], len);
\r
3678 buf[len++] = '\n';
\r
3679 buf[len] = NULLCHAR;
\r
3681 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3683 SendToProgram(buf, cps);
\r
3685 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3686 * the engine. It would be nice to have a better way to identify castle
\r
3688 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3689 if (appData.debugMode) {
\r
3690 fprintf(debugFP, "Tord's FRC castling code\n");
\r
3692 int fromX = moveList[moveNum][0] - AAA;
\r
3693 int fromY = moveList[moveNum][1] - ONE;
\r
3694 int toX = moveList[moveNum][2] - AAA;
\r
3695 int toY = moveList[moveNum][3] - ONE;
\r
3696 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3697 && boards[moveNum][toY][toX] == WhiteRook)
\r
3698 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3699 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3700 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3701 else SendToProgram("O-O-O\n", cps);
\r
3703 else SendToProgram(moveList[moveNum], cps);
\r
3705 else SendToProgram(moveList[moveNum], cps);
\r
3706 /* End of additions by Tord */
\r
3709 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3710 /* Send 'go' if we are in a mode where machine should play. */
\r
3711 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3712 (gameMode == TwoMachinesPlay ||
\r
3714 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3716 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3717 SendToProgram("go\n", cps);
\r
3718 if (appData.debugMode) {
\r
3719 fprintf(debugFP, "(extra)\n");
\r
3722 setboardSpoiledMachineBlack = 0;
\r
3726 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3727 ChessMove moveType;
\r
3728 int fromX, fromY, toX, toY;
\r
3730 char user_move[MSG_SIZ];
\r
3732 switch (moveType) {
\r
3734 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3735 (int)moveType, fromX, fromY, toX, toY);
\r
3736 DisplayError(user_move + strlen("say "), 0);
\r
3738 case WhiteKingSideCastle:
\r
3739 case BlackKingSideCastle:
\r
3740 case WhiteQueenSideCastleWild:
\r
3741 case BlackQueenSideCastleWild:
\r
3743 case WhiteHSideCastleFR:
\r
3744 case BlackHSideCastleFR:
\r
3746 sprintf(user_move, "o-o\n");
\r
3748 case WhiteQueenSideCastle:
\r
3749 case BlackQueenSideCastle:
\r
3750 case WhiteKingSideCastleWild:
\r
3751 case BlackKingSideCastleWild:
\r
3753 case WhiteASideCastleFR:
\r
3754 case BlackASideCastleFR:
\r
3756 sprintf(user_move, "o-o-o\n");
\r
3758 case WhitePromotionQueen:
\r
3759 case BlackPromotionQueen:
\r
3760 case WhitePromotionRook:
\r
3761 case BlackPromotionRook:
\r
3762 case WhitePromotionBishop:
\r
3763 case BlackPromotionBishop:
\r
3764 case WhitePromotionKnight:
\r
3765 case BlackPromotionKnight:
\r
3766 case WhitePromotionKing:
\r
3767 case BlackPromotionKing:
\r
3768 case WhitePromotionChancellor:
\r
3769 case BlackPromotionChancellor:
\r
3770 case WhitePromotionArchbishop:
\r
3771 case BlackPromotionArchbishop:
\r
3772 if(gameInfo.variant == VariantShatranj)
\r
3773 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3774 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3775 PieceToChar(WhiteFerz));
\r
3777 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3778 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3779 PieceToChar(PromoPiece(moveType)));
\r
3783 sprintf(user_move, "%c@%c%c\n",
\r
3784 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3785 AAA + toX, ONE + toY);
\r
3788 case WhiteCapturesEnPassant:
\r
3789 case BlackCapturesEnPassant:
\r
3790 case IllegalMove: /* could be a variant we don't quite understand */
\r
3791 sprintf(user_move, "%c%c%c%c\n",
\r
3792 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3795 SendToICS(user_move);
\r
3799 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3800 int rf, ff, rt, ft;
\r
3804 if (rf == DROP_RANK) {
\r
3805 sprintf(move, "%c@%c%c\n",
\r
3806 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3808 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3809 sprintf(move, "%c%c%c%c\n",
\r
3810 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3812 sprintf(move, "%c%c%c%c%c\n",
\r
3813 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3816 AlphaRank(move, 4);
\r
3820 ProcessICSInitScript(f)
\r
3823 char buf[MSG_SIZ];
\r
3825 while (fgets(buf, MSG_SIZ, f)) {
\r
3826 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3833 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3835 AlphaRank(char *move, int n)
\r
3837 char *p = move, c; int x, y;
\r
3839 if( !appData.alphaRank ) return;
\r
3841 if (appData.debugMode) {
\r
3842 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
3845 if(move[1]=='*' &&
\r
3846 move[2]>='0' && move[2]<='9' &&
\r
3847 move[3]>='a' && move[3]<='x' ) {
\r
3848 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3849 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3851 if(move[0]>='0' && move[0]<='9' &&
\r
3852 move[1]>='a' && move[1]<='x' &&
\r
3853 move[2]>='0' && move[2]<='9' &&
\r
3854 move[3]>='a' && move[3]<='x' ) {
\r
3855 /* input move, Shogi -> normal */
\r
3856 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
3857 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
3858 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3859 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3861 if(move[1]=='@' &&
\r
3862 move[3]>='0' && move[3]<='9' &&
\r
3863 move[2]>='a' && move[2]<='x' ) {
\r
3865 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3866 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3869 move[0]>='a' && move[0]<='x' &&
\r
3870 move[3]>='0' && move[3]<='9' &&
\r
3871 move[2]>='a' && move[2]<='x' ) {
\r
3872 /* output move, normal -> Shogi */
\r
3873 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
3874 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
3875 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3876 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3877 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
3879 if (appData.debugMode) {
\r
3880 fprintf(debugFP, " out = '%s'\n", move);
\r
3884 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3886 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3889 ChessMove *moveType;
\r
3890 int *fromX, *fromY, *toX, *toY;
\r
3893 if (appData.debugMode) {
\r
3894 fprintf(debugFP, "move to parse: %s\n", move);
\r
3896 *moveType = yylexstr(moveNum, move);
\r
3898 switch (*moveType) {
\r
3899 case WhitePromotionChancellor:
\r
3900 case BlackPromotionChancellor:
\r
3901 case WhitePromotionArchbishop:
\r
3902 case BlackPromotionArchbishop:
\r
3903 case WhitePromotionQueen:
\r
3904 case BlackPromotionQueen:
\r
3905 case WhitePromotionRook:
\r
3906 case BlackPromotionRook:
\r
3907 case WhitePromotionBishop:
\r
3908 case BlackPromotionBishop:
\r
3909 case WhitePromotionKnight:
\r
3910 case BlackPromotionKnight:
\r
3911 case WhitePromotionKing:
\r
3912 case BlackPromotionKing:
\r
3914 case WhiteCapturesEnPassant:
\r
3915 case BlackCapturesEnPassant:
\r
3916 case WhiteKingSideCastle:
\r
3917 case WhiteQueenSideCastle:
\r
3918 case BlackKingSideCastle:
\r
3919 case BlackQueenSideCastle:
\r
3920 case WhiteKingSideCastleWild:
\r
3921 case WhiteQueenSideCastleWild:
\r
3922 case BlackKingSideCastleWild:
\r
3923 case BlackQueenSideCastleWild:
\r
3924 /* Code added by Tord: */
\r
3925 case WhiteHSideCastleFR:
\r
3926 case WhiteASideCastleFR:
\r
3927 case BlackHSideCastleFR:
\r
3928 case BlackASideCastleFR:
\r
3929 /* End of code added by Tord */
\r
3930 case IllegalMove: /* bug or odd chess variant */
\r
3931 *fromX = currentMoveString[0] - AAA;
\r
3932 *fromY = currentMoveString[1] - ONE;
\r
3933 *toX = currentMoveString[2] - AAA;
\r
3934 *toY = currentMoveString[3] - ONE;
\r
3935 *promoChar = currentMoveString[4];
\r
3936 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
3937 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
3938 if (appData.debugMode) {
\r
3939 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
3941 *fromX = *fromY = *toX = *toY = 0;
\r
3944 if (appData.testLegality) {
\r
3945 return (*moveType != IllegalMove);
\r
3947 return !(fromX == fromY && toX == toY);
\r
3952 *fromX = *moveType == WhiteDrop ?
\r
3953 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
3954 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
3955 *fromY = DROP_RANK;
\r
3956 *toX = currentMoveString[2] - AAA;
\r
3957 *toY = currentMoveString[3] - ONE;
\r
3958 *promoChar = NULLCHAR;
\r
3961 case AmbiguousMove:
\r
3962 case ImpossibleMove:
\r
3963 case (ChessMove) 0: /* end of file */
\r
3972 if (appData.debugMode) {
\r
3973 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
3976 *fromX = *fromY = *toX = *toY = 0;
\r
3977 *promoChar = NULLCHAR;
\r
3982 /* [AS] FRC game initialization */
\r
3983 static int FindEmptySquare( Board board, int n )
\r
3988 while( board[0][i] != EmptySquare ) i++;
\r
3998 static void ShuffleFRC( Board board )
\r
4004 for( i=0; i<8; i++ ) {
\r
4005 board[0][i] = EmptySquare;
\r
4008 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4009 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4010 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4011 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4012 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4013 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4014 initialRights[1] = initialRights[4] =
\r
4015 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4016 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4017 initialRights[2] = initialRights[5] =
\r
4018 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4019 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4020 initialRights[0] = initialRights[3] =
\r
4021 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4023 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4024 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4028 static unsigned char FRC_KnightTable[10] = {
\r
4029 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4032 static void SetupFRC( Board board, int pos_index )
\r
4035 unsigned char knights;
\r
4037 /* Bring the position index into a safe range (just in case...) */
\r
4038 if( pos_index < 0 ) pos_index = 0;
\r
4042 /* Clear the board */
\r
4043 for( i=0; i<8; i++ ) {
\r
4044 board[0][i] = EmptySquare;
\r
4047 /* Place bishops and queen */
\r
4048 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4051 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4054 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4057 /* Place knigths */
\r
4058 knights = FRC_KnightTable[ pos_index ];
\r
4060 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4061 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4063 /* Place rooks and king */
\r
4064 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4065 initialRights[1] = initialRights[4] =
\r
4066 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4067 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4068 initialRights[2] = initialRights[5] =
\r
4069 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4070 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4071 initialRights[0] = initialRights[3] =
\r
4072 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4074 /* Mirror piece placement for black */
\r
4075 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4076 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4080 BOOL SetCharTable( char *table, const char * map )
\r
4081 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4082 /* Basically a safe strcpy that uses the last character as King */
\r
4084 BOOL result = FALSE; int NrPieces;
\r
4086 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4087 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4088 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4090 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4091 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4092 table[i] = map[i];
\r
4093 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4095 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4096 table[(int) BlackKing] = map[NrPieces-1];
\r
4105 InitPosition(redraw)
\r
4108 ChessSquare (* pieces)[BOARD_SIZE];
\r
4109 int i, j, pawnRow, overrule,
\r
4110 oldx = gameInfo.boardWidth,
\r
4111 oldy = gameInfo.boardHeight,
\r
4112 oldh = gameInfo.holdingsWidth,
\r
4113 oldv = gameInfo.variant;
\r
4115 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4117 /* [AS] Initialize pv info list [HGM] and game status */
\r
4119 for( i=0; i<MAX_MOVES; i++ ) {
\r
4120 pvInfoList[i].depth = 0;
\r
4121 epStatus[i]=EP_NONE;
\r
4122 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4125 initialRulePlies = 0; /* 50-move counter start */
\r
4127 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4128 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4132 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4133 /* the initialized data only consist of the two backranks. The switch */
\r
4134 /* selects which one we will use, which is than copied to the Board */
\r
4135 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4136 /* empty squares. This initial position is then copied to boards[0], */
\r
4137 /* possibly after shuffling, so that it remains available. */
\r
4139 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4140 gameInfo.boardWidth = 8;
\r
4141 gameInfo.boardHeight = 8;
\r
4142 gameInfo.holdingsSize = 0;
\r
4143 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4144 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4145 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4147 switch (gameInfo.variant) {
\r
4149 pieces = FIDEArray;
\r
4151 case VariantShatranj:
\r
4152 pieces = ShatranjArray;
\r
4153 nrCastlingRights = 0;
\r
4154 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4156 case VariantTwoKings:
\r
4157 pieces = twoKingsArray;
\r
4158 nrCastlingRights = 8; /* add rights for second King */
\r
4159 castlingRights[0][6] = initialRights[2] = 5;
\r
4160 castlingRights[0][7] = initialRights[5] = 5;
\r
4161 castlingRank[6] = 0;
\r
4162 castlingRank[7] = BOARD_HEIGHT-1;
\r
4164 case VariantCapablanca:
\r
4165 pieces = CapablancaArray;
\r
4166 gameInfo.boardWidth = 10;
\r
4167 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4169 case VariantGothic:
\r
4170 pieces = GothicArray;
\r
4171 gameInfo.boardWidth = 10;
\r
4172 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4174 case VariantFalcon:
\r
4175 pieces = FalconArray;
\r
4176 gameInfo.boardWidth = 10;
\r
4177 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4179 case VariantXiangqi:
\r
4180 pieces = XiangqiArray;
\r
4181 gameInfo.boardWidth = 9;
\r
4182 gameInfo.boardHeight = 10;
\r
4183 nrCastlingRights = 0;
\r
4184 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4186 case VariantShogi:
\r
4187 pieces = ShogiArray;
\r
4188 gameInfo.boardWidth = 9;
\r
4189 gameInfo.boardHeight = 9;
\r
4190 gameInfo.holdingsSize = 7;
\r
4191 nrCastlingRights = 0;
\r
4192 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4194 case VariantCourier:
\r
4195 pieces = CourierArray;
\r
4196 gameInfo.boardWidth = 12;
\r
4197 nrCastlingRights = 0;
\r
4198 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4199 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4201 case VariantKnightmate:
\r
4202 pieces = KnightmateArray;
\r
4203 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4205 case VariantFairy:
\r
4206 pieces = fairyArray;
\r
4207 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4209 case VariantCrazyhouse:
\r
4210 case VariantBughouse:
\r
4211 pieces = FIDEArray;
\r
4212 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4213 gameInfo.holdingsSize = 5;
\r
4215 case VariantWildCastle:
\r
4216 pieces = FIDEArray;
\r
4217 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4219 case VariantNoCastle:
\r
4220 pieces = FIDEArray;
\r
4221 nrCastlingRights = 0;
\r
4222 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4223 /* !!?unconstrained back-rank shuffle */
\r
4228 if(appData.NrFiles >= 0) {
\r
4229 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4230 gameInfo.boardWidth = appData.NrFiles;
\r
4232 if(appData.NrRanks >= 0) {
\r
4233 gameInfo.boardHeight = appData.NrRanks;
\r
4235 if(appData.holdingsSize >= 0) {
\r
4236 i = appData.holdingsSize;
\r
4237 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4238 gameInfo.holdingsSize = i;
\r
4240 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4241 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4242 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
4244 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4245 if(pawnRow < 1) pawnRow = 1;
\r
4247 /* User pieceToChar list overrules defaults */
\r
4248 if(appData.pieceToCharTable != NULL)
\r
4249 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4251 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4253 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4254 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4255 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4256 initialPosition[i][j] = s;
\r
4258 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4259 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4260 initialPosition[pawnRow][j] = WhitePawn;
\r
4261 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4262 if(gameInfo.variant == VariantXiangqi) {
\r
4264 initialPosition[pawnRow][j] =
\r
4265 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4266 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4267 initialPosition[2][j] = WhiteCannon;
\r
4268 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4272 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4274 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4277 initialPosition[1][j] = WhiteBishop;
\r
4278 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4280 initialPosition[1][j] = WhiteRook;
\r
4281 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4284 if( nrCastlingRights == -1) {
\r
4285 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4286 /* This sets default castling rights from none to normal corners */
\r
4287 /* Variants with other castling rights must set them themselves above */
\r
4288 nrCastlingRights = 6;
\r
4290 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4291 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4292 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4293 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4294 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4295 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4298 if(gameInfo.variant == VariantFischeRandom) {
\r
4299 if( appData.defaultFrcPosition < 0 ) {
\r
4300 ShuffleFRC( initialPosition );
\r
4303 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4305 startedFromSetupPosition = TRUE;
\r
4306 } else if(startedFromPositionFile) {
\r
4307 /* [HGM] loadPos: use PositionFile for every new game */
\r
4308 CopyBoard(initialPosition, filePosition);
\r
4309 for(i=0; i<nrCastlingRights; i++)
\r
4310 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4311 startedFromSetupPosition = TRUE;
\r
4314 CopyBoard(boards[0], initialPosition);
\r
4316 if(oldx != gameInfo.boardWidth ||
\r
4317 oldy != gameInfo.boardHeight ||
\r
4318 oldh != gameInfo.holdingsWidth
\r
4320 || oldv == VariantGothic || // For licensing popups
\r
4321 gameInfo.variant == VariantGothic
\r
4324 || oldv == VariantFalcon ||
\r
4325 gameInfo.variant == VariantFalcon
\r
4328 InitDrawingSizes(-2 ,0);
\r
4331 DrawPosition(TRUE, boards[currentMove]);
\r
4335 SendBoard(cps, moveNum)
\r
4336 ChessProgramState *cps;
\r
4339 char message[MSG_SIZ];
\r
4341 if (cps->useSetboard) {
\r
4342 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4343 sprintf(message, "setboard %s\n", fen);
\r
4344 SendToProgram(message, cps);
\r
4350 /* Kludge to set black to move, avoiding the troublesome and now
\r
4351 * deprecated "black" command.
\r
4353 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4355 SendToProgram("edit\n", cps);
\r
4356 SendToProgram("#\n", cps);
\r
4357 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4358 bp = &boards[moveNum][i][0];
\r
4359 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4360 if ((int) *bp < (int) BlackPawn) {
\r
4361 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4362 AAA + j, ONE + i);
\r
4363 if(message[0] == '+' || message[0] == '~') {
\r
4364 sprintf(message, "%c%c%c+\n",
\r
4365 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4366 AAA + j, ONE + i);
\r
4368 if(appData.alphaRank) {
\r
4369 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4370 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4372 SendToProgram(message, cps);
\r
4377 SendToProgram("c\n", cps);
\r
4378 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4379 bp = &boards[moveNum][i][0];
\r
4380 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4381 if (((int) *bp != (int) EmptySquare)
\r
4382 && ((int) *bp >= (int) BlackPawn)) {
\r
4383 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4384 AAA + j, ONE + i);
\r
4385 if(message[0] == '+' || message[0] == '~') {
\r
4386 sprintf(message, "%c%c%c+\n",
\r
4387 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4388 AAA + j, ONE + i);
\r
4390 if(appData.alphaRank) {
\r
4391 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4392 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4394 SendToProgram(message, cps);
\r
4399 SendToProgram(".\n", cps);
\r
4401 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4405 IsPromotion(fromX, fromY, toX, toY)
\r
4406 int fromX, fromY, toX, toY;
\r
4408 /* [HGM] add Shogi promotions */
\r
4409 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4410 ChessSquare piece;
\r
4412 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4413 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4414 /* [HGM] Note to self: line above also weeds out drops */
\r
4415 piece = boards[currentMove][fromY][fromX];
\r
4416 if(gameInfo.variant == VariantShogi) {
\r
4417 promotionZoneSize = 3;
\r
4418 highestPromotingPiece = (int)WhiteKing;
\r
4419 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4420 and if in normal chess we then allow promotion to King, why not
\r
4421 allow promotion of other piece in Shogi? */
\r
4423 if((int)piece >= BlackPawn) {
\r
4424 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4426 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4428 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4429 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4431 return ( (int)piece <= highestPromotingPiece );
\r
4435 InPalace(row, column)
\r
4437 { /* [HGM] for Xiangqi */
\r
4438 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4439 column < (BOARD_WIDTH + 4)/2 &&
\r
4440 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4445 PieceForSquare (x, y)
\r
4449 if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)
\r
4452 return boards[currentMove][y][x];
\r
4456 OKToStartUserMove(x, y)
\r
4459 ChessSquare from_piece;
\r
4462 if (matchMode) return FALSE;
\r
4463 if (gameMode == EditPosition) return TRUE;
\r
4465 if (x >= 0 && y >= 0)
\r
4466 from_piece = boards[currentMove][y][x];
\r
4468 from_piece = EmptySquare;
\r
4470 if (from_piece == EmptySquare) return FALSE;
\r
4472 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4473 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4475 switch (gameMode) {
\r
4476 case PlayFromGameFile:
\r
4478 case TwoMachinesPlay:
\r
4482 case IcsObserving:
\r
4486 case MachinePlaysWhite:
\r
4487 case IcsPlayingBlack:
\r
4488 if (appData.zippyPlay) return FALSE;
\r
4489 if (white_piece) {
\r
4490 DisplayMoveError("You are playing Black");
\r
4495 case MachinePlaysBlack:
\r
4496 case IcsPlayingWhite:
\r
4497 if (appData.zippyPlay) return FALSE;
\r
4498 if (!white_piece) {
\r
4499 DisplayMoveError("You are playing White");
\r
4505 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4506 DisplayMoveError("It is White's turn");
\r
4509 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4510 DisplayMoveError("It is Black's turn");
\r
4513 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4514 /* Editing correspondence game history */
\r
4515 /* Could disallow this or prompt for confirmation */
\r
4516 cmailOldMove = -1;
\r
4518 if (currentMove < forwardMostMove) {
\r
4519 /* Discarding moves */
\r
4520 /* Could prompt for confirmation here,
\r
4521 but I don't think that's such a good idea */
\r
4522 forwardMostMove = currentMove;
\r
4526 case BeginningOfGame:
\r
4527 if (appData.icsActive) return FALSE;
\r
4528 if (!appData.noChessProgram) {
\r
4529 if (!white_piece) {
\r
4530 DisplayMoveError("You are playing White");
\r
4537 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4538 DisplayMoveError("It is White's turn");
\r
4541 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4542 DisplayMoveError("It is Black's turn");
\r
4548 case IcsExamining:
\r
4551 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4552 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4553 DisplayMoveError("Displayed position is not current");
\r
4559 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4560 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4561 int lastLoadGameUseList = FALSE;
\r
4562 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4563 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4567 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4568 int fromX, fromY, toX, toY;
\r
4571 ChessMove moveType;
\r
4572 ChessSquare pdown, pup;
\r
4574 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4575 if ((fromX == toX) && (fromY == toY)) {
\r
4576 return ImpossibleMove;
\r
4579 /* [HGM] suppress all moves into holdings area and guard band */
\r
4580 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4581 return ImpossibleMove;
\r
4583 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4584 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
4585 pdown = boards[currentMove][fromY][fromX];
\r
4586 pup = boards[currentMove][toY][toX];
\r
4587 if ( gameMode != EditPosition &&
\r
4588 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4589 WhitePawn <= pup && pup < BlackPawn ||
\r
4590 BlackPawn <= pdown && pdown < EmptySquare &&
\r
4591 BlackPawn <= pup && pup < EmptySquare
\r
4592 ) && !(gameInfo.variant == VariantFischeRandom &&
\r
4593 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
4594 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
4596 return ImpossibleMove;
\r
4598 /* Check if the user is playing in turn. This is complicated because we
\r
4599 let the user "pick up" a piece before it is his turn. So the piece he
\r
4600 tried to pick up may have been captured by the time he puts it down!
\r
4601 Therefore we use the color the user is supposed to be playing in this
\r
4602 test, not the color of the piece that is currently on the starting
\r
4603 square---except in EditGame mode, where the user is playing both
\r
4604 sides; fortunately there the capture race can't happen. (It can
\r
4605 now happen in IcsExamining mode, but that's just too bad. The user
\r
4606 will get a somewhat confusing message in that case.)
\r
4609 switch (gameMode) {
\r
4610 case PlayFromGameFile:
\r
4612 case TwoMachinesPlay:
\r
4614 case IcsObserving:
\r
4616 /* We switched into a game mode where moves are not accepted,
\r
4617 perhaps while the mouse button was down. */
\r
4618 return ImpossibleMove;
\r
4620 case MachinePlaysWhite:
\r
4621 /* User is moving for Black */
\r
4622 if (WhiteOnMove(currentMove)) {
\r
4623 DisplayMoveError("It is White's turn");
\r
4624 return ImpossibleMove;
\r
4628 case MachinePlaysBlack:
\r
4629 /* User is moving for White */
\r
4630 if (!WhiteOnMove(currentMove)) {
\r
4631 DisplayMoveError("It is Black's turn");
\r
4632 return ImpossibleMove;
\r
4637 case IcsExamining:
\r
4638 case BeginningOfGame:
\r
4641 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4642 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4643 /* User is moving for Black */
\r
4644 if (WhiteOnMove(currentMove)) {
\r
4645 DisplayMoveError("It is White's turn");
\r
4646 return ImpossibleMove;
\r
4649 /* User is moving for White */
\r
4650 if (!WhiteOnMove(currentMove)) {
\r
4651 DisplayMoveError("It is Black's turn");
\r
4652 return ImpossibleMove;
\r
4657 case IcsPlayingBlack:
\r
4658 /* User is moving for Black */
\r
4659 if (WhiteOnMove(currentMove)) {
\r
4660 if (!appData.premove) {
\r
4661 DisplayMoveError("It is White's turn");
\r
4662 } else if (toX >= 0 && toY >= 0) {
\r
4665 premoveFromX = fromX;
\r
4666 premoveFromY = fromY;
\r
4667 premovePromoChar = promoChar;
\r
4669 if (appData.debugMode)
\r
4670 fprintf(debugFP, "Got premove: fromX %d,"
\r
4671 "fromY %d, toX %d, toY %d\n",
\r
4672 fromX, fromY, toX, toY);
\r
4674 return ImpossibleMove;
\r
4678 case IcsPlayingWhite:
\r
4679 /* User is moving for White */
\r
4680 if (!WhiteOnMove(currentMove)) {
\r
4681 if (!appData.premove) {
\r
4682 DisplayMoveError("It is Black's turn");
\r
4683 } else if (toX >= 0 && toY >= 0) {
\r
4686 premoveFromX = fromX;
\r
4687 premoveFromY = fromY;
\r
4688 premovePromoChar = promoChar;
\r
4690 if (appData.debugMode)
\r
4691 fprintf(debugFP, "Got premove: fromX %d,"
\r
4692 "fromY %d, toX %d, toY %d\n",
\r
4693 fromX, fromY, toX, toY);
\r
4695 return ImpossibleMove;
\r
4702 case EditPosition:
\r
4703 /* EditPosition, empty square, or different color piece;
\r
4704 click-click move is possible */
\r
4705 if (toX == -2 || toY == -2) {
\r
4706 boards[0][fromY][fromX] = EmptySquare;
\r
4707 DrawPosition(FALSE, boards[currentMove]);
\r
4708 } else if (toX >= 0 && toY >= 0) {
\r
4709 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
4710 boards[0][fromY][fromX] = EmptySquare;
\r
4711 DrawPosition(FALSE, boards[currentMove]);
\r
4713 return ImpossibleMove;
\r
4716 /* [HGM] If move started in holdings, it means a drop */
\r
4717 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4718 if( pup != EmptySquare ) return ImpossibleMove;
\r
4719 if(appData.testLegality) {
\r
4720 /* it would be more logical if LegalityTest() also figured out
\r
4721 * which drops are legal. For now we forbid pawns on back rank.
\r
4722 * Shogi is on its own here...
\r
4724 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
4725 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
4726 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
4728 return WhiteDrop; /* Not needed to specify white or black yet */
\r
4731 userOfferedDraw = FALSE;
\r
4733 /* [HGM] always test for legality, to get promotion info */
\r
4734 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
4735 epStatus[currentMove], castlingRights[currentMove],
\r
4736 fromY, fromX, toY, toX, promoChar);
\r
4738 /* [HGM] but possibly ignore an IllegalMove result */
\r
4739 if (appData.testLegality) {
\r
4740 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
4741 DisplayMoveError("Illegal move");
\r
4742 return ImpossibleMove;
\r
4747 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
4748 function is made into one that returns an OK move type if FinishMove
\r
4749 should be called. This to give the calling driver routine the
\r
4750 opportunity to finish the userMove input with a promotion popup,
\r
4751 without bothering the user with this for invalid or illegal moves */
\r
4753 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
4756 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
4758 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
4759 ChessMove moveType;
\r
4760 int fromX, fromY, toX, toY;
\r
4761 /*char*/int promoChar;
\r
4763 /* [HGM] <popupFix> kludge to avoid having know the exact promotion
\r
4764 move type in caller when we know the move is a legal promotion */
\r
4765 if(moveType == NormalMove)
\r
4766 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
4768 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
4769 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
4770 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
4771 fromX = boards[currentMove][fromY][fromX];
\r
4772 fromY = DROP_RANK;
\r
4775 /* [HGM] <popupFix> The following if has been moved here from
\r
4776 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
4777 piece drops in training games?), and because it can only be
\r
4778 performed after it is known to what we promote. */
\r
4779 if (gameMode == Training) {
\r
4780 /* compare the move played on the board to the next move in the
\r
4781 * game. If they match, display the move and the opponent's response.
\r
4782 * If they don't match, display an error message.
\r
4786 CopyBoard(testBoard, boards[currentMove]);
\r
4787 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
4789 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
4790 ForwardInner(currentMove+1);
\r
4792 /* Autoplay the opponent's response.
\r
4793 * if appData.animate was TRUE when Training mode was entered,
\r
4794 * the response will be animated.
\r
4796 saveAnimate = appData.animate;
\r
4797 appData.animate = animateTraining;
\r
4798 ForwardInner(currentMove+1);
\r
4799 appData.animate = saveAnimate;
\r
4801 /* check for the end of the game */
\r
4802 if (currentMove >= forwardMostMove) {
\r
4803 gameMode = PlayFromGameFile;
\r
4805 SetTrainingModeOff();
\r
4806 DisplayInformation("End of game");
\r
4809 DisplayError("Incorrect move", 0);
\r
4814 /* Ok, now we know that the move is good, so we can kill
\r
4815 the previous line in Analysis Mode */
\r
4816 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
4817 forwardMostMove = currentMove;
\r
4820 /* If we need the chess program but it's dead, restart it */
\r
4821 ResurrectChessProgram();
\r
4823 /* A user move restarts a paused game*/
\r
4827 thinkOutput[0] = NULLCHAR;
\r
4829 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
4831 if (gameMode == BeginningOfGame) {
\r
4832 if (appData.noChessProgram) {
\r
4833 gameMode = EditGame;
\r
4836 char buf[MSG_SIZ];
\r
4837 gameMode = MachinePlaysBlack;
\r
4839 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
4840 DisplayTitle(buf);
\r
4841 if (first.sendName) {
\r
4842 sprintf(buf, "name %s\n", gameInfo.white);
\r
4843 SendToProgram(buf, &first);
\r
4849 /* Relay move to ICS or chess engine */
\r
4850 if (appData.icsActive) {
\r
4851 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
4852 gameMode == IcsExamining) {
\r
4853 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
4854 ics_user_moved = 1;
\r
4857 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
4858 gameMode == MachinePlaysWhite ||
\r
4859 gameMode == MachinePlaysBlack)) {
\r
4860 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
4862 SendMoveToProgram(forwardMostMove-1, &first);
\r
4863 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
4864 first.maybeThinking = TRUE;
\r
4866 if (currentMove == cmailOldMove + 1) {
\r
4867 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
4871 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
4873 switch (gameMode) {
\r
4875 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
4876 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
4880 case MT_CHECKMATE:
\r
4881 if (WhiteOnMove(currentMove)) {
\r
4882 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
4884 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
4887 case MT_STALEMATE:
\r
4888 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
4893 case MachinePlaysBlack:
\r
4894 case MachinePlaysWhite:
\r
4895 /* disable certain menu options while machine is thinking */
\r
4896 SetMachineThinkingEnables();
\r
4905 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
4906 int fromX, fromY, toX, toY;
\r
4909 /* [HGM] This routine was added to allow calling of its two logical
\r
4910 parts from other modules in the old way. Before, UserMoveEvent()
\r
4911 automatically called FinishMove() if the move was OK, and returned
\r
4912 otherwise. I separated the two, in order to make it possible to
\r
4913 slip a promotion popup in between. But that it always needs two
\r
4914 calls, to the first part, (now called UserMoveTest() ), and to
\r
4915 FinishMove if the first part succeeded. Calls that do not need
\r
4916 to do anything in between, can call this routine the old way.
\r
4918 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
4920 if(moveType != ImpossibleMove)
\r
4921 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
4924 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
4926 char * hint = lastHint;
\r
4927 FrontEndProgramStats stats;
\r
4929 stats.which = cps == &first ? 0 : 1;
\r
4930 stats.depth = cpstats->depth;
\r
4931 stats.nodes = cpstats->nodes;
\r
4932 stats.score = cpstats->score;
\r
4933 stats.time = cpstats->time;
\r
4934 stats.pv = cpstats->movelist;
\r
4935 stats.hint = lastHint;
\r
4936 stats.an_move_index = 0;
\r
4937 stats.an_move_count = 0;
\r
4939 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
4940 stats.hint = cpstats->move_name;
\r
4941 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
4942 stats.an_move_count = cpstats->nr_moves;
\r
4945 SetProgramStats( &stats );
\r
4949 HandleMachineMove(message, cps)
\r
4951 ChessProgramState *cps;
\r
4953 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
4954 char realname[MSG_SIZ];
\r
4955 int fromX, fromY, toX, toY;
\r
4956 ChessMove moveType;
\r
4962 * Kludge to ignore BEL characters
\r
4964 while (*message == '\007') message++;
\r
4967 * [HGM] engine debug message: ignore lines starting with '#' character
\r
4969 if(cps->debug && *message == '#') return;
\r
4972 * Look for book output
\r
4974 if (cps == &first && bookRequested) {
\r
4975 if (message[0] == '\t' || message[0] == ' ') {
\r
4976 /* Part of the book output is here; append it */
\r
4977 strcat(bookOutput, message);
\r
4978 strcat(bookOutput, " \n");
\r
4980 } else if (bookOutput[0] != NULLCHAR) {
\r
4981 /* All of book output has arrived; display it */
\r
4982 char *p = bookOutput;
\r
4983 while (*p != NULLCHAR) {
\r
4984 if (*p == '\t') *p = ' ';
\r
4987 DisplayInformation(bookOutput);
\r
4988 bookRequested = FALSE;
\r
4989 /* Fall through to parse the current output */
\r
4994 * Look for machine move.
\r
4996 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
4997 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
4999 /* This method is only useful on engines that support ping */
\r
5000 if (cps->lastPing != cps->lastPong) {
\r
5001 if (gameMode == BeginningOfGame) {
\r
5002 /* Extra move from before last new; ignore */
\r
5003 if (appData.debugMode) {
\r
5004 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5007 if (appData.debugMode) {
\r
5008 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5009 cps->which, gameMode);
\r
5012 SendToProgram("undo\n", cps);
\r
5017 switch (gameMode) {
\r
5018 case BeginningOfGame:
\r
5019 /* Extra move from before last reset; ignore */
\r
5020 if (appData.debugMode) {
\r
5021 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5028 /* Extra move after we tried to stop. The mode test is
\r
5029 not a reliable way of detecting this problem, but it's
\r
5030 the best we can do on engines that don't support ping.
\r
5032 if (appData.debugMode) {
\r
5033 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5034 cps->which, gameMode);
\r
5036 SendToProgram("undo\n", cps);
\r
5039 case MachinePlaysWhite:
\r
5040 case IcsPlayingWhite:
\r
5041 machineWhite = TRUE;
\r
5044 case MachinePlaysBlack:
\r
5045 case IcsPlayingBlack:
\r
5046 machineWhite = FALSE;
\r
5049 case TwoMachinesPlay:
\r
5050 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5053 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5054 if (appData.debugMode) {
\r
5056 "Ignoring move out of turn by %s, gameMode %d"
\r
5057 ", forwardMost %d\n",
\r
5058 cps->which, gameMode, forwardMostMove);
\r
5063 if (appData.debugMode) { int f = forwardMostMove;
\r
5064 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5065 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5067 AlphaRank(machineMove, 4);
\r
5068 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5069 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5070 /* Machine move could not be parsed; ignore it. */
\r
5071 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
5072 machineMove, cps->which);
\r
5073 DisplayError(buf1, 0);
\r
5074 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5075 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5076 if (gameMode == TwoMachinesPlay) {
\r
5077 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5083 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5084 /* So we have to redo legality test with true e.p. status here, */
\r
5085 /* to make sure an illegal e.p. capture does not slip through, */
\r
5086 /* to cause a forfeit on a justified illegal-move complaint */
\r
5087 /* of the opponent. */
\r
5088 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5089 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5091 ChessMove moveType;
\r
5092 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5093 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5094 fromY, fromX, toY, toX, promoChar);
\r
5095 if (appData.debugMode) {
\r
5097 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5098 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5099 fprintf(debugFP, "castling rights\n");
\r
5101 if(moveType == IllegalMove) {
\r
5102 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5103 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5104 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5106 } else if(gameInfo.variant != VariantFischeRandom)
\r
5107 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5108 when they shouldn't (like TSCP-Gothic) */
\r
5109 switch(moveType) {
\r
5110 case WhiteASideCastleFR:
\r
5111 case BlackASideCastleFR:
\r
5113 currentMoveString[2]++;
\r
5115 case WhiteHSideCastleFR:
\r
5116 case BlackHSideCastleFR:
\r
5118 currentMoveString[2]--;
\r
5122 hintRequested = FALSE;
\r
5123 lastHint[0] = NULLCHAR;
\r
5124 bookRequested = FALSE;
\r
5125 /* Program may be pondering now */
\r
5126 cps->maybeThinking = TRUE;
\r
5127 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5128 if (cps->offeredDraw) cps->offeredDraw--;
\r
5131 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5133 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5134 ics_user_moved = 1;
\r
5137 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5138 strcpy(machineMove, currentMoveString);
\r
5139 strcat(machineMove, "\n");
\r
5140 strcpy(moveList[forwardMostMove], machineMove);
\r
5142 /* [AS] Save move info and clear stats for next move */
\r
5143 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5144 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5145 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5146 ClearProgramStats();
\r
5147 thinkOutput[0] = NULLCHAR;
\r
5148 hiddenThinkOutputState = 0;
\r
5150 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5152 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5153 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5156 while( count < adjudicateLossPlies ) {
\r
5157 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5160 score = -score; /* Flip score for winning side */
\r
5163 if( score > adjudicateLossThreshold ) {
\r
5170 if( count >= adjudicateLossPlies ) {
\r
5171 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5173 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5174 "Xboard adjudication",
\r
5181 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines
\r
5183 if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {
\r
5184 int count = 0, epFile = epStatus[forwardMostMove];
\r
5186 if(appData.testLegality && appData.checkMates)
\r
5187 // don't wait for engine to announce game end if we can judge ourselves
\r
5188 switch (MateTest(boards[forwardMostMove],
\r
5189 PosFlags(forwardMostMove), epFile,
\r
5190 castlingRights[forwardMostMove]) ) {
\r
5195 case MT_STALEMATE:
\r
5196 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5197 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5200 case MT_CHECKMATE:
\r
5201 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5202 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5203 "Xboard adjudication: Checkmate",
\r
5208 if( appData.testLegality )
\r
5209 { /* [HGM] Some more adjudications for obstinate engines */
\r
5210 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5211 NrWQ=0, NrBQ=0, bishopsColor = 0,
\r
5212 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5213 static int moveCount;
\r
5215 /* First absolutely insufficient mating material. Count what is on board. */
\r
5216 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5217 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5221 { /* count B,N,R and other of each side */
\r
5225 bishopsColor |= 1 << ((i^j)&1);
\r
5230 bishopsColor |= 1 << ((i^j)&1);
\r
5240 case EmptySquare:
\r
5245 PawnAdvance += m; NrPawns++;
\r
5247 NrPieces += (p != EmptySquare);
\r
5250 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2
\r
5251 || NrPieces == 4 && NrBB+NrWB==2 && bishopsColor != 3)
\r
5252 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5254 /* always flag draws, for judging claims */
\r
5255 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5257 if(appData.materialDraws) {
\r
5258 /* but only adjudicate them if adjudication enabled */
\r
5259 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5260 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5265 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5266 if(NrPieces == 4 &&
\r
5267 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5268 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5269 || NrWN==2 || NrBN==2 /* KNNK */
\r
5270 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5272 if(--moveCount < 0 && appData.trivialDraws)
\r
5273 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5274 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5275 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5278 } else moveCount = 6;
\r
5280 if (appData.debugMode) { int i;
\r
5281 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5282 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5283 appData.drawRepeats);
\r
5284 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5285 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5289 /* Check for rep-draws */
\r
5291 for(k = forwardMostMove-2;
\r
5292 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5293 epStatus[k] < EP_UNKNOWN &&
\r
5294 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5298 if (appData.debugMode) {
\r
5299 fprintf(debugFP, " loop\n");
\r
5302 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5304 if (appData.debugMode) {
\r
5305 fprintf(debugFP, "match\n");
\r
5308 /* compare castling rights */
\r
5309 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5310 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5311 rights++; /* King lost rights, while rook still had them */
\r
5312 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5313 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5314 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5315 rights++; /* but at least one rook lost them */
\r
5317 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5318 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5320 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5321 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5322 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5326 if (appData.debugMode) {
\r
5327 for(i=0; i<nrCastlingRights; i++)
\r
5328 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5331 if (appData.debugMode) {
\r
5332 fprintf(debugFP, " %d %d\n", rights, k);
\r
5335 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5336 && appData.drawRepeats > 1) {
\r
5337 /* adjudicate after user-specified nr of repeats */
\r
5338 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5339 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5342 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5343 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5347 /* Now we test for 50-move draws. Determine ply count */
\r
5348 count = forwardMostMove;
\r
5349 /* look for last irreversble move */
\r
5350 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5352 /* if we hit starting position, add initial plies */
\r
5353 if( count == backwardMostMove )
\r
5354 count -= initialRulePlies;
\r
5355 count = forwardMostMove - count;
\r
5357 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5358 /* this is used to judge if draw claims are legal */
\r
5359 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5360 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5361 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5365 /* if draw offer is pending, treat it as a draw claim
\r
5366 * when draw condition present, to allow engines a way to
\r
5367 * claim draws before making their move to avoid a race
\r
5368 * condition occurring after their move
\r
5370 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
5372 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
5373 p = "Draw claim: 50-move rule";
\r
5374 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
5375 p = "Draw claim: 3-fold repetition";
\r
5376 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
5377 p = "Draw claim: insufficient mating material";
\r
5379 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
5380 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5390 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5391 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5393 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5398 if (gameMode == TwoMachinesPlay) {
\r
5399 /* [HGM] relaying draw offers moved to after reception of move */
\r
5400 /* and interpreting offer as claim if it brings draw condition */
\r
5401 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
5402 SendToProgram("draw\n", cps->other);
\r
5404 if (cps->other->sendTime) {
\r
5405 SendTimeRemaining(cps->other,
\r
5406 cps->other->twoMachinesColor[0] == 'w');
\r
5408 SendMoveToProgram(forwardMostMove-1, cps->other);
\r
5410 firstMove = FALSE;
\r
5411 if (cps->other->useColors) {
\r
5412 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5414 SendToProgram("go\n", cps->other);
\r
5416 cps->other->maybeThinking = TRUE;
\r
5419 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5421 if (!pausing && appData.ringBellAfterMoves) {
\r
5426 * Reenable menu items that were disabled while
\r
5427 * machine was thinking
\r
5429 if (gameMode != TwoMachinesPlay)
\r
5430 SetUserThinkingEnables();
\r
5435 /* Set special modes for chess engines. Later something general
\r
5436 * could be added here; for now there is just one kludge feature,
\r
5437 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5438 * when "xboard" is given as an interactive command.
\r
5440 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5441 cps->useSigint = FALSE;
\r
5442 cps->useSigterm = FALSE;
\r
5445 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5446 * want this, I was asked to put it in, and obliged.
\r
5448 if (!strncmp(message, "setboard ", 9)) {
\r
5449 Board initial_position; int i;
\r
5451 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
5453 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5454 DisplayError("Bad FEN received from engine", 0);
\r
5457 Reset(FALSE, FALSE);
\r
5458 CopyBoard(boards[0], initial_position);
\r
5459 initialRulePlies = FENrulePlies;
\r
5460 epStatus[0] = FENepStatus;
\r
5461 for( i=0; i<nrCastlingRights; i++ )
\r
5462 castlingRights[0][i] = FENcastlingRights[i];
\r
5463 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5464 else gameMode = MachinePlaysBlack;
\r
5465 DrawPosition(FALSE, boards[currentMove]);
\r
5471 * Look for communication commands
\r
5473 if (!strncmp(message, "telluser ", 9)) {
\r
5474 DisplayNote(message + 9);
\r
5477 if (!strncmp(message, "tellusererror ", 14)) {
\r
5478 DisplayError(message + 14, 0);
\r
5481 if (!strncmp(message, "tellopponent ", 13)) {
\r
5482 if (appData.icsActive) {
\r
5484 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5488 DisplayNote(message + 13);
\r
5492 if (!strncmp(message, "tellothers ", 11)) {
\r
5493 if (appData.icsActive) {
\r
5495 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5501 if (!strncmp(message, "tellall ", 8)) {
\r
5502 if (appData.icsActive) {
\r
5504 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5508 DisplayNote(message + 8);
\r
5512 if (strncmp(message, "warning", 7) == 0) {
\r
5513 /* Undocumented feature, use tellusererror in new code */
\r
5514 DisplayError(message, 0);
\r
5517 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5518 strcpy(realname, cps->tidy);
\r
5519 strcat(realname, " query");
\r
5520 AskQuestion(realname, buf2, buf1, cps->pr);
\r
5523 /* Commands from the engine directly to ICS. We don't allow these to be
\r
5524 * sent until we are logged on. Crafty kibitzes have been known to
\r
5525 * interfere with the login process.
\r
5528 if (!strncmp(message, "tellics ", 8)) {
\r
5529 SendToICS(message + 8);
\r
5533 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
5534 SendToICS(ics_prefix);
\r
5535 SendToICS(message + 15);
\r
5539 /* The following are for backward compatibility only */
\r
5540 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
5541 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
5542 SendToICS(ics_prefix);
\r
5543 SendToICS(message);
\r
5548 if (strncmp(message, "feature ", 8) == 0) {
\r
5549 ParseFeatures(message+8, cps);
\r
5551 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
5555 * If the move is illegal, cancel it and redraw the board.
\r
5556 * Also deal with other error cases. Matching is rather loose
\r
5557 * here to accommodate engines written before the spec.
\r
5559 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
5560 strncmp(message, "Error", 5) == 0) {
\r
5561 if (StrStr(message, "name") ||
\r
5562 StrStr(message, "rating") || StrStr(message, "?") ||
\r
5563 StrStr(message, "result") || StrStr(message, "board") ||
\r
5564 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
5565 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
5566 StrStr(message, "random") || StrStr(message, "depth") ||
\r
5567 StrStr(message, "accepted")) {
\r
5570 if (StrStr(message, "protover")) {
\r
5571 /* Program is responding to input, so it's apparently done
\r
5572 initializing, and this error message indicates it is
\r
5573 protocol version 1. So we don't need to wait any longer
\r
5574 for it to initialize and send feature commands. */
\r
5575 FeatureDone(cps, 1);
\r
5576 cps->protocolVersion = 1;
\r
5579 cps->maybeThinking = FALSE;
\r
5581 if (StrStr(message, "draw")) {
\r
5582 /* Program doesn't have "draw" command */
\r
5583 cps->sendDrawOffers = 0;
\r
5586 if (cps->sendTime != 1 &&
\r
5587 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
5588 /* Program apparently doesn't have "time" or "otim" command */
\r
5589 cps->sendTime = 0;
\r
5592 if (StrStr(message, "analyze")) {
\r
5593 cps->analysisSupport = FALSE;
\r
5594 cps->analyzing = FALSE;
\r
5595 Reset(FALSE, TRUE);
\r
5596 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
5597 DisplayError(buf2, 0);
\r
5600 if (StrStr(message, "(no matching move)st")) {
\r
5601 /* Special kludge for GNU Chess 4 only */
\r
5602 cps->stKludge = TRUE;
\r
5603 SendTimeControl(cps, movesPerSession, timeControl,
\r
5604 timeIncrement, appData.searchDepth,
\r
5608 if (StrStr(message, "(no matching move)sd")) {
\r
5609 /* Special kludge for GNU Chess 4 only */
\r
5610 cps->sdKludge = TRUE;
\r
5611 SendTimeControl(cps, movesPerSession, timeControl,
\r
5612 timeIncrement, appData.searchDepth,
\r
5616 if (!StrStr(message, "llegal")) {
\r
5619 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5620 gameMode == IcsIdle) return;
\r
5621 if (forwardMostMove <= backwardMostMove) return;
\r
5623 /* Following removed: it caused a bug where a real illegal move
\r
5624 message in analyze mored would be ignored. */
\r
5625 if (cps == &first && programStats.ok_to_send == 0) {
\r
5626 /* Bogus message from Crafty responding to "." This filtering
\r
5627 can miss some of the bad messages, but fortunately the bug
\r
5628 is fixed in current Crafty versions, so it doesn't matter. */
\r
5632 if (pausing) PauseEvent();
\r
5633 if (gameMode == PlayFromGameFile) {
\r
5634 /* Stop reading this game file */
\r
5635 gameMode = EditGame;
\r
5638 currentMove = --forwardMostMove;
\r
5639 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
5641 DisplayBothClocks();
\r
5642 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
5643 parseList[currentMove], cps->which);
\r
5644 DisplayMoveError(buf1);
\r
5645 DrawPosition(FALSE, boards[currentMove]);
\r
5647 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
5648 /* only passes fully legal moves */
\r
5649 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
5650 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
5651 "False illegal-move claim", GE_XBOARD );
\r
5655 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
5656 /* Program has a broken "time" command that
\r
5657 outputs a string not ending in newline.
\r
5659 cps->sendTime = 0;
\r
5663 * If chess program startup fails, exit with an error message.
\r
5664 * Attempts to recover here are futile.
\r
5666 if ((StrStr(message, "unknown host") != NULL)
\r
5667 || (StrStr(message, "No remote directory") != NULL)
\r
5668 || (StrStr(message, "not found") != NULL)
\r
5669 || (StrStr(message, "No such file") != NULL)
\r
5670 || (StrStr(message, "can't alloc") != NULL)
\r
5671 || (StrStr(message, "Permission denied") != NULL)) {
\r
5673 cps->maybeThinking = FALSE;
\r
5674 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
5675 cps->which, cps->program, cps->host, message);
\r
5676 RemoveInputSource(cps->isr);
\r
5677 DisplayFatalError(buf1, 0, 1);
\r
5682 * Look for hint output
\r
5684 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
5685 if (cps == &first && hintRequested) {
\r
5686 hintRequested = FALSE;
\r
5687 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
5688 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5689 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
5690 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
5691 fromY, fromX, toY, toX, promoChar, buf1);
\r
5692 sprintf(buf2, "Hint: %s", buf1);
\r
5693 DisplayInformation(buf2);
\r
5695 /* Hint move could not be parsed!? */
\r
5697 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
5698 buf1, cps->which);
\r
5699 DisplayError(buf2, 0);
\r
5702 strcpy(lastHint, buf1);
\r
5708 * Ignore other messages if game is not in progress
\r
5710 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
5711 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
5714 * look for win, lose, draw, or draw offer
\r
5716 if (strncmp(message, "1-0", 3) == 0) {
\r
5717 char *p, *q, *r = "";
\r
5718 p = strchr(message, '{');
\r
5720 q = strchr(p, '}');
\r
5726 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
5728 } else if (strncmp(message, "0-1", 3) == 0) {
\r
5729 char *p, *q, *r = "";
\r
5730 p = strchr(message, '{');
\r
5732 q = strchr(p, '}');
\r
5738 /* Kludge for Arasan 4.1 bug */
\r
5739 if (strcmp(r, "Black resigns") == 0) {
\r
5740 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
5743 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
5745 } else if (strncmp(message, "1/2", 3) == 0) {
\r
5746 char *p, *q, *r = "";
\r
5747 p = strchr(message, '{');
\r
5749 q = strchr(p, '}');
\r
5756 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
5759 } else if (strncmp(message, "White resign", 12) == 0) {
\r
5760 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5762 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
5763 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5765 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
5766 strncmp(message, "Black matches", 13) == 0 ) {
\r
5767 /* [HGM] ignore GNUShogi noises */
\r
5769 } else if (strncmp(message, "White", 5) == 0 &&
\r
5770 message[5] != '(' &&
\r
5771 StrStr(message, "Black") == NULL) {
\r
5772 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5774 } else if (strncmp(message, "Black", 5) == 0 &&
\r
5775 message[5] != '(') {
\r
5776 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5778 } else if (strcmp(message, "resign") == 0 ||
\r
5779 strcmp(message, "computer resigns") == 0) {
\r
5780 switch (gameMode) {
\r
5781 case MachinePlaysBlack:
\r
5782 case IcsPlayingBlack:
\r
5783 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
5785 case MachinePlaysWhite:
\r
5786 case IcsPlayingWhite:
\r
5787 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
5789 case TwoMachinesPlay:
\r
5790 if (cps->twoMachinesColor[0] == 'w')
\r
5791 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
5793 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
5796 /* can't happen */
\r
5800 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
5801 switch (gameMode) {
\r
5802 case MachinePlaysBlack:
\r
5803 case IcsPlayingBlack:
\r
5804 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5806 case MachinePlaysWhite:
\r
5807 case IcsPlayingWhite:
\r
5808 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
5810 case TwoMachinesPlay:
\r
5811 if (cps->twoMachinesColor[0] == 'w')
\r
5812 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5814 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5817 /* can't happen */
\r
5821 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
5822 switch (gameMode) {
\r
5823 case MachinePlaysBlack:
\r
5824 case IcsPlayingBlack:
\r
5825 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
5827 case MachinePlaysWhite:
\r
5828 case IcsPlayingWhite:
\r
5829 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
5831 case TwoMachinesPlay:
\r
5832 if (cps->twoMachinesColor[0] == 'w')
\r
5833 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5835 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5838 /* can't happen */
\r
5842 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
5843 if (WhiteOnMove(forwardMostMove)) {
\r
5844 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
5846 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
5849 } else if (strstr(message, "Draw") != NULL ||
\r
5850 strstr(message, "game is a draw") != NULL) {
\r
5851 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
5853 } else if (strstr(message, "offer") != NULL &&
\r
5854 strstr(message, "draw") != NULL) {
\r
5856 if (appData.zippyPlay && first.initDone) {
\r
5857 /* Relay offer to ICS */
\r
5858 SendToICS(ics_prefix);
\r
5859 SendToICS("draw\n");
\r
5862 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
5863 if (gameMode == TwoMachinesPlay) {
\r
5864 if (cps->other->offeredDraw) {
\r
5865 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5866 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
5867 /* until after we also have move, to see if it is really claim */
\r
5871 if (cps->other->sendDrawOffers) {
\r
5872 SendToProgram("draw\n", cps->other);
\r
5876 } else if (gameMode == MachinePlaysWhite ||
\r
5877 gameMode == MachinePlaysBlack) {
\r
5878 if (userOfferedDraw) {
\r
5879 DisplayInformation("Machine accepts your draw offer");
\r
5880 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
5882 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
5889 * Look for thinking output
\r
5891 if ( appData.showThinking) {
\r
5892 int plylev, mvleft, mvtot, curscore, time;
\r
5893 char mvname[MOVE_LEN];
\r
5894 unsigned long nodes;
\r
5896 int ignore = FALSE;
\r
5897 int prefixHint = FALSE;
\r
5898 mvname[0] = NULLCHAR;
\r
5900 switch (gameMode) {
\r
5901 case MachinePlaysBlack:
\r
5902 case IcsPlayingBlack:
\r
5903 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5905 case MachinePlaysWhite:
\r
5906 case IcsPlayingWhite:
\r
5907 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
5912 case TwoMachinesPlay:
\r
5913 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
5923 buf1[0] = NULLCHAR;
\r
5924 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
5925 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
5927 if (plyext != ' ' && plyext != '\t') {
\r
5931 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
5932 if( cps->scoreIsAbsolute &&
\r
5933 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
5935 curscore = -curscore;
\r
5939 programStats.depth = plylev;
\r
5940 programStats.nodes = nodes;
\r
5941 programStats.time = time;
\r
5942 programStats.score = curscore;
\r
5943 programStats.got_only_move = 0;
\r
5945 /* Buffer overflow protection */
\r
5946 if (buf1[0] != NULLCHAR) {
\r
5947 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
5948 && appData.debugMode) {
\r
5950 "PV is too long; using the first %d bytes.\n",
\r
5951 sizeof(programStats.movelist) - 1);
\r
5954 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
5956 sprintf(programStats.movelist, " no PV\n");
\r
5959 if (programStats.seen_stat) {
\r
5960 programStats.ok_to_send = 1;
\r
5963 if (strchr(programStats.movelist, '(') != NULL) {
\r
5964 programStats.line_is_book = 1;
\r
5965 programStats.nr_moves = 0;
\r
5966 programStats.moves_left = 0;
\r
5968 programStats.line_is_book = 0;
\r
5971 SendProgramStatsToFrontend( cps, &programStats );
\r
5974 [AS] Protect the thinkOutput buffer from overflow... this
\r
5975 is only useful if buf1 hasn't overflowed first!
\r
5977 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
5979 (gameMode == TwoMachinesPlay ?
\r
5980 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
5981 ((double) curscore) / 100.0,
\r
5982 prefixHint ? lastHint : "",
\r
5983 prefixHint ? " " : "" );
\r
5985 if( buf1[0] != NULLCHAR ) {
\r
5986 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
5988 if( strlen(buf1) > max_len ) {
\r
5989 if( appData.debugMode) {
\r
5990 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
5992 buf1[max_len+1] = '\0';
\r
5995 strcat( thinkOutput, buf1 );
\r
5998 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
5999 DisplayMove(currentMove - 1);
\r
6000 DisplayAnalysis();
\r
6004 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6005 /* crafty (9.25+) says "(only move) <move>"
\r
6006 * if there is only 1 legal move
\r
6008 sscanf(p, "(only move) %s", buf1);
\r
6009 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6010 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6011 programStats.depth = 1;
\r
6012 programStats.nr_moves = 1;
\r
6013 programStats.moves_left = 1;
\r
6014 programStats.nodes = 1;
\r
6015 programStats.time = 1;
\r
6016 programStats.got_only_move = 1;
\r
6018 /* Not really, but we also use this member to
\r
6019 mean "line isn't going to change" (Crafty
\r
6020 isn't searching, so stats won't change) */
\r
6021 programStats.line_is_book = 1;
\r
6023 SendProgramStatsToFrontend( cps, &programStats );
\r
6025 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6026 DisplayMove(currentMove - 1);
\r
6027 DisplayAnalysis();
\r
6030 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
6031 &time, &nodes, &plylev, &mvleft,
\r
6032 &mvtot, mvname) >= 5) {
\r
6033 /* The stat01: line is from Crafty (9.29+) in response
\r
6034 to the "." command */
\r
6035 programStats.seen_stat = 1;
\r
6036 cps->maybeThinking = TRUE;
\r
6038 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6041 programStats.depth = plylev;
\r
6042 programStats.time = time;
\r
6043 programStats.nodes = nodes;
\r
6044 programStats.moves_left = mvleft;
\r
6045 programStats.nr_moves = mvtot;
\r
6046 strcpy(programStats.move_name, mvname);
\r
6047 programStats.ok_to_send = 1;
\r
6048 programStats.movelist[0] = '\0';
\r
6050 SendProgramStatsToFrontend( cps, &programStats );
\r
6052 DisplayAnalysis();
\r
6055 } else if (strncmp(message,"++",2) == 0) {
\r
6056 /* Crafty 9.29+ outputs this */
\r
6057 programStats.got_fail = 2;
\r
6060 } else if (strncmp(message,"--",2) == 0) {
\r
6061 /* Crafty 9.29+ outputs this */
\r
6062 programStats.got_fail = 1;
\r
6065 } else if (thinkOutput[0] != NULLCHAR &&
\r
6066 strncmp(message, " ", 4) == 0) {
\r
6067 unsigned message_len;
\r
6070 while (*p && *p == ' ') p++;
\r
6072 message_len = strlen( p );
\r
6074 /* [AS] Avoid buffer overflow */
\r
6075 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6076 strcat(thinkOutput, " ");
\r
6077 strcat(thinkOutput, p);
\r
6080 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6081 strcat(programStats.movelist, " ");
\r
6082 strcat(programStats.movelist, p);
\r
6085 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6086 DisplayMove(currentMove - 1);
\r
6087 DisplayAnalysis();
\r
6093 buf1[0] = NULLCHAR;
\r
6095 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6096 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6098 ChessProgramStats cpstats;
\r
6100 if (plyext != ' ' && plyext != '\t') {
\r
6104 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6105 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6106 curscore = -curscore;
\r
6109 cpstats.depth = plylev;
\r
6110 cpstats.nodes = nodes;
\r
6111 cpstats.time = time;
\r
6112 cpstats.score = curscore;
\r
6113 cpstats.got_only_move = 0;
\r
6114 cpstats.movelist[0] = '\0';
\r
6116 if (buf1[0] != NULLCHAR) {
\r
6117 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6120 cpstats.ok_to_send = 0;
\r
6121 cpstats.line_is_book = 0;
\r
6122 cpstats.nr_moves = 0;
\r
6123 cpstats.moves_left = 0;
\r
6125 SendProgramStatsToFrontend( cps, &cpstats );
\r
6132 /* Parse a game score from the character string "game", and
\r
6133 record it as the history of the current game. The game
\r
6134 score is NOT assumed to start from the standard position.
\r
6135 The display is not updated in any way.
\r
6138 ParseGameHistory(game)
\r
6141 ChessMove moveType;
\r
6142 int fromX, fromY, toX, toY, boardIndex;
\r
6145 char buf[MSG_SIZ];
\r
6147 if (appData.debugMode)
\r
6148 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6150 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6151 gameInfo.site = StrSave(appData.icsHost);
\r
6152 gameInfo.date = PGNDate();
\r
6153 gameInfo.round = StrSave("-");
\r
6155 /* Parse out names of players */
\r
6156 while (*game == ' ') game++;
\r
6158 while (*game != ' ') *p++ = *game++;
\r
6160 gameInfo.white = StrSave(buf);
\r
6161 while (*game == ' ') game++;
\r
6163 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6165 gameInfo.black = StrSave(buf);
\r
6168 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6171 yyboardindex = boardIndex;
\r
6172 moveType = (ChessMove) yylex();
\r
6173 switch (moveType) {
\r
6174 case IllegalMove: /* maybe suicide chess, etc. */
\r
6175 if (appData.debugMode) {
\r
6176 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6177 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6178 setbuf(debugFP, NULL);
\r
6180 case WhitePromotionChancellor:
\r
6181 case BlackPromotionChancellor:
\r
6182 case WhitePromotionArchbishop:
\r
6183 case BlackPromotionArchbishop:
\r
6184 case WhitePromotionQueen:
\r
6185 case BlackPromotionQueen:
\r
6186 case WhitePromotionRook:
\r
6187 case BlackPromotionRook:
\r
6188 case WhitePromotionBishop:
\r
6189 case BlackPromotionBishop:
\r
6190 case WhitePromotionKnight:
\r
6191 case BlackPromotionKnight:
\r
6192 case WhitePromotionKing:
\r
6193 case BlackPromotionKing:
\r
6195 case WhiteCapturesEnPassant:
\r
6196 case BlackCapturesEnPassant:
\r
6197 case WhiteKingSideCastle:
\r
6198 case WhiteQueenSideCastle:
\r
6199 case BlackKingSideCastle:
\r
6200 case BlackQueenSideCastle:
\r
6201 case WhiteKingSideCastleWild:
\r
6202 case WhiteQueenSideCastleWild:
\r
6203 case BlackKingSideCastleWild:
\r
6204 case BlackQueenSideCastleWild:
\r
6206 case WhiteHSideCastleFR:
\r
6207 case WhiteASideCastleFR:
\r
6208 case BlackHSideCastleFR:
\r
6209 case BlackASideCastleFR:
\r
6211 fromX = currentMoveString[0] - AAA;
\r
6212 fromY = currentMoveString[1] - ONE;
\r
6213 toX = currentMoveString[2] - AAA;
\r
6214 toY = currentMoveString[3] - ONE;
\r
6215 promoChar = currentMoveString[4];
\r
6219 fromX = moveType == WhiteDrop ?
\r
6220 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6221 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6222 fromY = DROP_RANK;
\r
6223 toX = currentMoveString[2] - AAA;
\r
6224 toY = currentMoveString[3] - ONE;
\r
6225 promoChar = NULLCHAR;
\r
6227 case AmbiguousMove:
\r
6229 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
6230 if (appData.debugMode) {
\r
6231 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6232 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6233 setbuf(debugFP, NULL);
\r
6235 DisplayError(buf, 0);
\r
6237 case ImpossibleMove:
\r
6239 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
6240 if (appData.debugMode) {
\r
6241 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6242 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6243 setbuf(debugFP, NULL);
\r
6245 DisplayError(buf, 0);
\r
6247 case (ChessMove) 0: /* end of file */
\r
6248 if (boardIndex < backwardMostMove) {
\r
6249 /* Oops, gap. How did that happen? */
\r
6250 DisplayError("Gap in move list", 0);
\r
6253 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6254 if (boardIndex > forwardMostMove) {
\r
6255 forwardMostMove = boardIndex;
\r
6259 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6260 strcat(parseList[boardIndex-1], " ");
\r
6261 strcat(parseList[boardIndex-1], yy_text);
\r
6273 case GameUnfinished:
\r
6274 if (gameMode == IcsExamining) {
\r
6275 if (boardIndex < backwardMostMove) {
\r
6276 /* Oops, gap. How did that happen? */
\r
6279 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6282 gameInfo.result = moveType;
\r
6283 p = strchr(yy_text, '{');
\r
6284 if (p == NULL) p = strchr(yy_text, '(');
\r
6287 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6289 q = strchr(p, *p == '{' ? '}' : ')');
\r
6290 if (q != NULL) *q = NULLCHAR;
\r
6293 gameInfo.resultDetails = StrSave(p);
\r
6296 if (boardIndex >= forwardMostMove &&
\r
6297 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6298 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6301 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6302 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6303 parseList[boardIndex]);
\r
6304 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6305 /* currentMoveString is set as a side-effect of yylex */
\r
6306 strcpy(moveList[boardIndex], currentMoveString);
\r
6307 strcat(moveList[boardIndex], "\n");
\r
6309 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6310 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6311 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6313 case MT_STALEMATE:
\r
6317 if(gameInfo.variant != VariantShogi)
\r
6318 strcat(parseList[boardIndex - 1], "+");
\r
6320 case MT_CHECKMATE:
\r
6321 strcat(parseList[boardIndex - 1], "#");
\r
6328 /* Apply a move to the given board */
\r
6330 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
6331 int fromX, fromY, toX, toY;
\r
6335 ChessSquare captured = board[toY][toX], piece, king; int p;
\r
6337 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6338 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
6339 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
6342 epStatus[p] = EP_NONE;
\r
6344 if( board[toY][toX] != EmptySquare )
\r
6345 epStatus[p] = EP_CAPTURE;
\r
6347 if( board[fromY][fromX] == WhitePawn ) {
\r
6348 epStatus[p] = EP_PAWN_MOVE;
\r
6349 if( toY-fromY==2 &&
\r
6350 (toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn ||
\r
6351 toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn ) )
\r
6352 epStatus[p] = toX;
\r
6354 if( board[fromY][fromX] == BlackPawn ) {
\r
6355 epStatus[p] = EP_PAWN_MOVE;
\r
6356 if( toY-fromY== -2 &&
\r
6357 (toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn ||
\r
6358 toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn ) )
\r
6359 epStatus[p] = toX;
\r
6362 for(i=0; i<nrCastlingRights; i++) {
\r
6363 castlingRights[p][i] = castlingRights[p-1][i];
\r
6364 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
6365 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
6366 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
6371 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
6372 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
6373 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
6375 if (fromX == toX && fromY == toY) return;
\r
6377 if (fromY == DROP_RANK) {
\r
6378 /* must be first */
\r
6379 piece = board[toY][toX] = (ChessSquare) fromX;
\r
6381 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
6382 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
6383 if(gameInfo.variant == VariantKnightmate)
\r
6384 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
6386 /* Code added by Tord: */
\r
6387 /* FRC castling assumed when king captures friendly rook. */
\r
6388 if (board[fromY][fromX] == WhiteKing &&
\r
6389 board[toY][toX] == WhiteRook) {
\r
6390 board[fromY][fromX] = EmptySquare;
\r
6391 board[toY][toX] = EmptySquare;
\r
6393 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
6395 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
6397 } else if (board[fromY][fromX] == BlackKing &&
\r
6398 board[toY][toX] == BlackRook) {
\r
6399 board[fromY][fromX] = EmptySquare;
\r
6400 board[toY][toX] = EmptySquare;
\r
6402 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
6404 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
6406 /* End of code added by Tord */
\r
6408 } else if (board[fromY][fromX] == king
\r
6409 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6410 && toY == fromY && toX > fromX+1) {
\r
6411 board[fromY][fromX] = EmptySquare;
\r
6412 board[toY][toX] = king;
\r
6413 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6414 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6415 } else if (board[fromY][fromX] == king
\r
6416 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6417 && toY == fromY && toX < fromX-1) {
\r
6418 board[fromY][fromX] = EmptySquare;
\r
6419 board[toY][toX] = king;
\r
6420 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6421 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6422 } else if (board[fromY][fromX] == WhitePawn
\r
6423 && toY == BOARD_HEIGHT-1
\r
6424 && gameInfo.variant != VariantXiangqi
\r
6426 /* white pawn promotion */
\r
6427 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6428 if (board[toY][toX] == EmptySquare) {
\r
6429 board[toY][toX] = WhiteQueen;
\r
6431 if(gameInfo.variant==VariantBughouse ||
\r
6432 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6433 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6434 board[fromY][fromX] = EmptySquare;
\r
6435 } else if ((fromY == BOARD_HEIGHT-4)
\r
6437 && gameInfo.variant != VariantXiangqi
\r
6438 && (board[fromY][fromX] == WhitePawn)
\r
6439 && (board[toY][toX] == EmptySquare)) {
\r
6440 board[fromY][fromX] = EmptySquare;
\r
6441 board[toY][toX] = WhitePawn;
\r
6442 captured = board[toY - 1][toX];
\r
6443 board[toY - 1][toX] = EmptySquare;
\r
6444 } else if (board[fromY][fromX] == king
\r
6445 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6446 && toY == fromY && toX > fromX+1) {
\r
6447 board[fromY][fromX] = EmptySquare;
\r
6448 board[toY][toX] = king;
\r
6449 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6450 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6451 } else if (board[fromY][fromX] == king
\r
6452 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6453 && toY == fromY && toX < fromX-1) {
\r
6454 board[fromY][fromX] = EmptySquare;
\r
6455 board[toY][toX] = king;
\r
6456 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6457 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6458 } else if (fromY == 7 && fromX == 3
\r
6459 && board[fromY][fromX] == BlackKing
\r
6460 && toY == 7 && toX == 5) {
\r
6461 board[fromY][fromX] = EmptySquare;
\r
6462 board[toY][toX] = BlackKing;
\r
6463 board[fromY][7] = EmptySquare;
\r
6464 board[toY][4] = BlackRook;
\r
6465 } else if (fromY == 7 && fromX == 3
\r
6466 && board[fromY][fromX] == BlackKing
\r
6467 && toY == 7 && toX == 1) {
\r
6468 board[fromY][fromX] = EmptySquare;
\r
6469 board[toY][toX] = BlackKing;
\r
6470 board[fromY][0] = EmptySquare;
\r
6471 board[toY][2] = BlackRook;
\r
6472 } else if (board[fromY][fromX] == BlackPawn
\r
6474 && gameInfo.variant != VariantXiangqi
\r
6476 /* black pawn promotion */
\r
6477 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6478 if (board[0][toX] == EmptySquare) {
\r
6479 board[0][toX] = BlackQueen;
\r
6481 if(gameInfo.variant==VariantBughouse ||
\r
6482 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6483 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6484 board[fromY][fromX] = EmptySquare;
\r
6485 } else if ((fromY == 3)
\r
6487 && gameInfo.variant != VariantXiangqi
\r
6488 && (board[fromY][fromX] == BlackPawn)
\r
6489 && (board[toY][toX] == EmptySquare)) {
\r
6490 board[fromY][fromX] = EmptySquare;
\r
6491 board[toY][toX] = BlackPawn;
\r
6492 captured = board[toY + 1][toX];
\r
6493 board[toY + 1][toX] = EmptySquare;
\r
6495 board[toY][toX] = board[fromY][fromX];
\r
6496 board[fromY][fromX] = EmptySquare;
\r
6499 /* [HGM] now we promote for Shogi, if needed */
\r
6500 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
6501 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6504 if (gameInfo.holdingsWidth != 0) {
\r
6506 /* !!A lot more code needs to be written to support holdings */
\r
6507 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
6508 /* penultimate board files, so they are automaticlly stored */
\r
6509 /* in the game history. */
\r
6510 if (fromY == DROP_RANK) {
\r
6511 /* Delete from holdings, by decreasing count */
\r
6512 /* and erasing image if necessary */
\r
6514 if(p < (int) BlackPawn) { /* white drop */
\r
6515 p -= (int)WhitePawn;
\r
6516 if(p >= gameInfo.holdingsSize) p = 0;
\r
6517 if(--board[p][BOARD_WIDTH-2] == 0)
\r
6518 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
6519 } else { /* black drop */
\r
6520 p -= (int)BlackPawn;
\r
6521 if(p >= gameInfo.holdingsSize) p = 0;
\r
6522 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
6523 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
6526 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
6527 && gameInfo.variant != VariantBughouse ) {
\r
6528 /* Add to holdings, if holdings exist */
\r
6529 p = (int) captured;
\r
6530 if (p >= (int) BlackPawn) {
\r
6531 p -= (int)BlackPawn;
\r
6532 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6533 /* in Shogi restore piece to its original first */
\r
6534 captured = (ChessSquare) (DEMOTED captured);
\r
6537 p = PieceToNumber((ChessSquare)p);
\r
6538 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
6539 board[p][BOARD_WIDTH-2]++;
\r
6540 board[p][BOARD_WIDTH-1] =
\r
6541 BLACK_TO_WHITE captured;
\r
6543 p -= (int)WhitePawn;
\r
6544 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
6545 captured = (ChessSquare) (DEMOTED captured);
\r
6548 p = PieceToNumber((ChessSquare)p);
\r
6549 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
6550 board[BOARD_HEIGHT-1-p][1]++;
\r
6551 board[BOARD_HEIGHT-1-p][0] =
\r
6552 WHITE_TO_BLACK captured;
\r
6556 } else if (gameInfo.variant == VariantAtomic) {
\r
6557 if (captured != EmptySquare) {
\r
6559 for (y = toY-1; y <= toY+1; y++) {
\r
6560 for (x = toX-1; x <= toX+1; x++) {
\r
6561 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
6562 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
6563 board[y][x] = EmptySquare;
\r
6567 board[toY][toX] = EmptySquare;
\r
6570 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
6571 /* [HGM] Shogi promotions */
\r
6572 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
6577 /* Updates forwardMostMove */
\r
6579 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
6580 int fromX, fromY, toX, toY;
\r
6583 forwardMostMove++;
\r
6585 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */
\r
6586 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
6587 piece = boards[forwardMostMove-1][fromY][fromX];
\r
6588 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
6589 if(gameInfo.variant == VariantKnightmate)
\r
6590 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
6591 if(forwardMostMove == 1) {
\r
6592 if(blackPlaysFirst)
\r
6593 fprintf(serverMoves, "%s;", second.tidy);
\r
6594 fprintf(serverMoves, "%s;", first.tidy);
\r
6595 if(!blackPlaysFirst)
\r
6596 fprintf(serverMoves, "%s;", second.tidy);
\r
6597 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
6598 lastLoadFlag = loadFlag;
\r
6599 // print base move
\r
6600 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
6601 // print castling suffix
\r
6602 if( toY == fromY && piece == king ) {
\r
6604 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
6606 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
6609 if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||
\r
6610 boards[forwardMostMove-1][fromY][fromX] == BlackPawn ) &&
\r
6611 boards[forwardMostMove-1][toY][toX] == EmptySquare
\r
6613 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
6614 // promotion suffix
\r
6615 if(promoChar != NULLCHAR)
\r
6616 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
6618 fprintf(serverMoves, "/%d/%d",
\r
6619 pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);
\r
6620 if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;
\r
6621 else timeLeft = blackTimeRemaining/1000;
\r
6622 fprintf(serverMoves, "/%d", timeLeft);
\r
6624 fflush(serverMoves);
\r
6627 if (forwardMostMove >= MAX_MOVES) {
\r
6628 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
6633 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
6634 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
6635 if (commentList[forwardMostMove] != NULL) {
\r
6636 free(commentList[forwardMostMove]);
\r
6637 commentList[forwardMostMove] = NULL;
\r
6639 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
6640 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
6641 gameInfo.result = GameUnfinished;
\r
6642 if (gameInfo.resultDetails != NULL) {
\r
6643 free(gameInfo.resultDetails);
\r
6644 gameInfo.resultDetails = NULL;
\r
6646 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
6647 moveList[forwardMostMove - 1]);
\r
6648 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
6649 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
6650 fromY, fromX, toY, toX, promoChar,
\r
6651 parseList[forwardMostMove - 1]);
\r
6652 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
6653 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
6654 castlingRights[forwardMostMove]) ) {
\r
6656 case MT_STALEMATE:
\r
6660 if(gameInfo.variant != VariantShogi)
\r
6661 strcat(parseList[forwardMostMove - 1], "+");
\r
6663 case MT_CHECKMATE:
\r
6664 strcat(parseList[forwardMostMove - 1], "#");
\r
6667 if (appData.debugMode) {
\r
6668 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
6673 /* Updates currentMove if not pausing */
\r
6675 ShowMove(fromX, fromY, toX, toY)
\r
6677 int instant = (gameMode == PlayFromGameFile) ?
\r
6678 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
6679 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
6681 if (forwardMostMove == currentMove + 1) {
\r
6682 AnimateMove(boards[forwardMostMove - 1],
\r
6683 fromX, fromY, toX, toY);
\r
6685 if (appData.highlightLastMove) {
\r
6686 SetHighlights(fromX, fromY, toX, toY);
\r
6689 currentMove = forwardMostMove;
\r
6692 if (instant) return;
\r
6694 DisplayMove(currentMove - 1);
\r
6695 DrawPosition(FALSE, boards[currentMove]);
\r
6696 DisplayBothClocks();
\r
6697 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
6702 InitChessProgram(cps, setup)
\r
6703 ChessProgramState *cps;
\r
6704 int setup; /* [HGM] needed to setup FRC opening position */
\r
6706 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
6707 if (appData.noChessProgram) return;
\r
6708 hintRequested = FALSE;
\r
6709 bookRequested = FALSE;
\r
6710 SendToProgram(cps->initString, cps);
\r
6711 if (gameInfo.variant != VariantNormal &&
\r
6712 gameInfo.variant != VariantLoadable
\r
6713 /* [HGM] also send variant if board size non-standard */
\r
6714 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8
\r
6716 char *v = VariantName(gameInfo.variant);
\r
6717 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
6718 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
6719 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
6720 DisplayFatalError(buf, 0, 1);
\r
6724 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
6725 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6726 if( gameInfo.variant == VariantXiangqi )
\r
6727 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
6728 if( gameInfo.variant == VariantShogi )
\r
6729 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
6730 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
6731 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
6732 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
6733 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6734 if( gameInfo.variant == VariantCourier )
\r
6735 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
6738 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
6739 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
6740 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
6741 if(StrStr(cps->variants, b) == NULL) {
\r
6742 // specific sized variant not known, check if general sizing allowed
\r
6743 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
6744 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
6745 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
6746 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
6747 DisplayFatalError(buf, 0, 1);
\r
6750 /* [HGM] here we really should compare with the maximum supported board size */
\r
6753 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
6754 sprintf(buf, "variant %s\n", b);
\r
6755 SendToProgram(buf, cps);
\r
6756 /* [HGM] send opening position in FRC to first engine */
\r
6757 if(setup /* cps == &first && gameInfo.variant == VariantFischeRandom */) {
\r
6758 SendToProgram("force\n", cps);
\r
6759 SendBoard(cps, 0);
\r
6760 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
6761 setboardSpoiledMachineBlack = 1;
\r
6764 if (cps->sendICS) {
\r
6765 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
6766 SendToProgram(buf, cps);
\r
6768 cps->maybeThinking = FALSE;
\r
6769 cps->offeredDraw = 0;
\r
6770 if (!appData.icsActive) {
\r
6771 SendTimeControl(cps, movesPerSession, timeControl,
\r
6772 timeIncrement, appData.searchDepth,
\r
6775 if (appData.showThinking) {
\r
6776 SendToProgram("post\n", cps);
\r
6778 SendToProgram("hard\n", cps);
\r
6779 if (!appData.ponderNextMove) {
\r
6780 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
6781 it without being sure what state we are in first. "hard"
\r
6782 is not a toggle, so that one is OK.
\r
6784 SendToProgram("easy\n", cps);
\r
6786 if (cps->usePing) {
\r
6787 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
6788 SendToProgram(buf, cps);
\r
6790 cps->initDone = TRUE;
\r
6795 StartChessProgram(cps)
\r
6796 ChessProgramState *cps;
\r
6798 char buf[MSG_SIZ];
\r
6801 if (appData.noChessProgram) return;
\r
6802 cps->initDone = FALSE;
\r
6804 if (strcmp(cps->host, "localhost") == 0) {
\r
6805 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
6806 } else if (*appData.remoteShell == NULLCHAR) {
\r
6807 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
6809 if (*appData.remoteUser == NULLCHAR) {
\r
6810 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
6813 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
6814 cps->host, appData.remoteUser, cps->program);
\r
6816 err = StartChildProcess(buf, "", &cps->pr);
\r
6820 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
6821 DisplayFatalError(buf, err, 1);
\r
6827 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
6828 if (cps->protocolVersion > 1) {
\r
6829 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
6830 SendToProgram(buf, cps);
\r
6832 SendToProgram("xboard\n", cps);
\r
6838 TwoMachinesEventIfReady P((void))
\r
6840 if (first.lastPing != first.lastPong) {
\r
6841 DisplayMessage("", "Waiting for first chess program");
\r
6842 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6845 if (second.lastPing != second.lastPong) {
\r
6846 DisplayMessage("", "Waiting for second chess program");
\r
6847 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
6851 TwoMachinesEvent();
\r
6855 NextMatchGame P((void))
\r
6857 Reset(FALSE, TRUE);
\r
6858 if (*appData.loadGameFile != NULLCHAR) {
\r
6859 LoadGameFromFile(appData.loadGameFile,
\r
6860 appData.loadGameIndex,
\r
6861 appData.loadGameFile, FALSE);
\r
6862 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
6863 LoadPositionFromFile(appData.loadPositionFile,
\r
6864 appData.loadPositionIndex,
\r
6865 appData.loadPositionFile);
\r
6867 TwoMachinesEventIfReady();
\r
6870 void UserAdjudicationEvent( int result )
\r
6872 ChessMove gameResult = GameIsDrawn;
\r
6874 if( result > 0 ) {
\r
6875 gameResult = WhiteWins;
\r
6877 else if( result < 0 ) {
\r
6878 gameResult = BlackWins;
\r
6881 if( gameMode == TwoMachinesPlay ) {
\r
6882 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
6888 GameEnds(result, resultDetails, whosays)
\r
6890 char *resultDetails;
\r
6893 GameMode nextGameMode;
\r
6895 char buf[MSG_SIZ];
\r
6897 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
6900 if (appData.debugMode) {
\r
6901 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
6902 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6905 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
6906 /* If we are playing on ICS, the server decides when the
\r
6907 game is over, but the engine can offer to draw, claim
\r
6908 a draw, or resign.
\r
6911 if (appData.zippyPlay && first.initDone) {
\r
6912 if (result == GameIsDrawn) {
\r
6913 /* In case draw still needs to be claimed */
\r
6914 SendToICS(ics_prefix);
\r
6915 SendToICS("draw\n");
\r
6916 } else if (StrCaseStr(resultDetails, "resign")) {
\r
6917 SendToICS(ics_prefix);
\r
6918 SendToICS("resign\n");
\r
6922 endingGame = 0; /* [HGM] crash */
\r
6926 /* If we're loading the game from a file, stop */
\r
6927 if (whosays == GE_FILE) {
\r
6928 (void) StopLoadGameTimer();
\r
6929 gameFileFP = NULL;
\r
6932 /* Cancel draw offers */
\r
6933 first.offeredDraw = second.offeredDraw = 0;
\r
6935 /* If this is an ICS game, only ICS can really say it's done;
\r
6936 if not, anyone can. */
\r
6937 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
6938 gameMode == IcsPlayingBlack ||
\r
6939 gameMode == IcsObserving ||
\r
6940 gameMode == IcsExamining);
\r
6942 if (!isIcsGame || whosays == GE_ICS) {
\r
6943 /* OK -- not an ICS game, or ICS said it was done */
\r
6945 if (appData.debugMode) {
\r
6946 fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",
\r
6947 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6949 if (!isIcsGame && !appData.noChessProgram)
\r
6950 SetUserThinkingEnables();
\r
6952 /* [HGM] if a machine claims the game end we verify this claim */
\r
6953 if( appData.testLegality && gameMode == TwoMachinesPlay &&
\r
6954 appData.testClaims && whosays >= GE_ENGINE1 ) {
\r
6957 if (appData.debugMode) {
\r
6958 fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",
\r
6959 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6961 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
6962 first.twoMachinesColor[0] :
\r
6963 second.twoMachinesColor[0] ;
\r
6964 if( gameInfo.holdingsWidth == 0 &&
\r
6965 (result == WhiteWins && claimer == 'w' ||
\r
6966 result == BlackWins && claimer == 'b' ) ) {
\r
6967 /* Xboard immediately adjudicates all mates, so win claims must be false */
\r
6968 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
6969 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6970 resultDetails = buf;
\r
6972 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
6973 && (forwardMostMove <= backwardMostMove ||
\r
6974 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
6975 (claimer=='b')==(forwardMostMove&1))
\r
6977 /* Draw that was not flagged by Xboard is false */
\r
6978 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
6979 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
6980 resultDetails = buf;
\r
6982 /* (Claiming a loss is accepted no questions asked!) */
\r
6985 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
6986 if(result==WhiteWins) c = '+';
\r
6987 if(result==BlackWins) c = '-';
\r
6988 if(resultDetails != NULL)
\r
6989 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
6991 if (appData.debugMode) {
\r
6992 fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",
\r
6993 result, resultDetails ? resultDetails : "(null)", whosays);
\r
6995 if (resultDetails != NULL) {
\r
6996 gameInfo.result = result;
\r
6997 gameInfo.resultDetails = StrSave(resultDetails);
\r
6999 /* display last move only if game was not loaded from file */
\r
7000 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7001 DisplayMove(currentMove - 1);
\r
7003 if (forwardMostMove != 0) {
\r
7004 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7005 if (*appData.saveGameFile != NULLCHAR) {
\r
7006 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7007 } else if (appData.autoSaveGames) {
\r
7010 if (*appData.savePositionFile != NULLCHAR) {
\r
7011 SavePositionToFile(appData.savePositionFile);
\r
7016 /* Tell program how game ended in case it is learning */
\r
7017 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7018 /* engine died and we got here through time loss. In that */
\r
7019 /* case we will get a fatal error writing the pipe, which */
\r
7020 /* would otherwise lose us the PGN. */
\r
7021 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7022 /* output during GameEnds should never be fatal anymore */
\r
7023 if (gameMode == MachinePlaysWhite ||
\r
7024 gameMode == MachinePlaysBlack ||
\r
7025 gameMode == TwoMachinesPlay ||
\r
7026 gameMode == IcsPlayingWhite ||
\r
7027 gameMode == IcsPlayingBlack ||
\r
7028 gameMode == BeginningOfGame) {
\r
7029 char buf[MSG_SIZ];
\r
7030 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7032 if (first.pr != NoProc) {
\r
7033 SendToProgram(buf, &first);
\r
7035 if (second.pr != NoProc &&
\r
7036 gameMode == TwoMachinesPlay) {
\r
7037 SendToProgram(buf, &second);
\r
7042 if (appData.icsActive) {
\r
7043 if (appData.quietPlay &&
\r
7044 (gameMode == IcsPlayingWhite ||
\r
7045 gameMode == IcsPlayingBlack)) {
\r
7046 SendToICS(ics_prefix);
\r
7047 SendToICS("set shout 1\n");
\r
7049 nextGameMode = IcsIdle;
\r
7050 ics_user_moved = FALSE;
\r
7051 /* clean up premove. It's ugly when the game has ended and the
\r
7052 * premove highlights are still on the board.
\r
7055 gotPremove = FALSE;
\r
7056 ClearPremoveHighlights();
\r
7057 DrawPosition(FALSE, boards[currentMove]);
\r
7059 if (whosays == GE_ICS) {
\r
7062 if (gameMode == IcsPlayingWhite)
\r
7063 PlayIcsWinSound();
\r
7064 else if(gameMode == IcsPlayingBlack)
\r
7065 PlayIcsLossSound();
\r
7068 if (gameMode == IcsPlayingBlack)
\r
7069 PlayIcsWinSound();
\r
7070 else if(gameMode == IcsPlayingWhite)
\r
7071 PlayIcsLossSound();
\r
7074 PlayIcsDrawSound();
\r
7077 PlayIcsUnfinishedSound();
\r
7080 } else if (gameMode == EditGame ||
\r
7081 gameMode == PlayFromGameFile ||
\r
7082 gameMode == AnalyzeMode ||
\r
7083 gameMode == AnalyzeFile) {
\r
7084 nextGameMode = gameMode;
\r
7086 nextGameMode = EndOfGame;
\r
7091 nextGameMode = gameMode;
\r
7094 if (appData.noChessProgram) {
\r
7095 gameMode = nextGameMode;
\r
7097 endingGame = 0; /* [HGM] crash */
\r
7101 if (first.reuse) {
\r
7102 /* Put first chess program into idle state */
\r
7103 if (first.pr != NoProc &&
\r
7104 (gameMode == MachinePlaysWhite ||
\r
7105 gameMode == MachinePlaysBlack ||
\r
7106 gameMode == TwoMachinesPlay ||
\r
7107 gameMode == IcsPlayingWhite ||
\r
7108 gameMode == IcsPlayingBlack ||
\r
7109 gameMode == BeginningOfGame)) {
\r
7110 SendToProgram("force\n", &first);
\r
7111 if (first.usePing) {
\r
7112 char buf[MSG_SIZ];
\r
7113 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7114 SendToProgram(buf, &first);
\r
7117 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7118 /* Kill off first chess program */
\r
7119 if (first.isr != NULL)
\r
7120 RemoveInputSource(first.isr);
\r
7123 if (first.pr != NoProc) {
\r
7124 ExitAnalyzeMode();
\r
7125 DoSleep( appData.delayBeforeQuit );
\r
7126 SendToProgram("quit\n", &first);
\r
7127 DoSleep( appData.delayAfterQuit );
\r
7128 DestroyChildProcess(first.pr, first.useSigterm);
\r
7130 first.pr = NoProc;
\r
7132 if (second.reuse) {
\r
7133 /* Put second chess program into idle state */
\r
7134 if (second.pr != NoProc &&
\r
7135 gameMode == TwoMachinesPlay) {
\r
7136 SendToProgram("force\n", &second);
\r
7137 if (second.usePing) {
\r
7138 char buf[MSG_SIZ];
\r
7139 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7140 SendToProgram(buf, &second);
\r
7143 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7144 /* Kill off second chess program */
\r
7145 if (second.isr != NULL)
\r
7146 RemoveInputSource(second.isr);
\r
7147 second.isr = NULL;
\r
7149 if (second.pr != NoProc) {
\r
7150 DoSleep( appData.delayBeforeQuit );
\r
7151 SendToProgram("quit\n", &second);
\r
7152 DoSleep( appData.delayAfterQuit );
\r
7153 DestroyChildProcess(second.pr, second.useSigterm);
\r
7155 second.pr = NoProc;
\r
7158 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7161 if (first.twoMachinesColor[0] == 'w') {
\r
7162 first.matchWins++;
\r
7164 second.matchWins++;
\r
7168 if (first.twoMachinesColor[0] == 'b') {
\r
7169 first.matchWins++;
\r
7171 second.matchWins++;
\r
7177 if (matchGame < appData.matchGames) {
\r
7179 tmp = first.twoMachinesColor;
\r
7180 first.twoMachinesColor = second.twoMachinesColor;
\r
7181 second.twoMachinesColor = tmp;
\r
7182 gameMode = nextGameMode;
\r
7184 if(appData.matchPause>10000 || appData.matchPause<10)
\r
7185 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
7186 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
7187 endingGame = 0; /* [HGM] crash */
\r
7190 char buf[MSG_SIZ];
\r
7191 gameMode = nextGameMode;
\r
7192 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
7193 first.tidy, second.tidy,
\r
7194 first.matchWins, second.matchWins,
\r
7195 appData.matchGames - (first.matchWins + second.matchWins));
\r
7196 DisplayFatalError(buf, 0, 0);
\r
7199 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7200 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
7201 ExitAnalyzeMode();
\r
7202 gameMode = nextGameMode;
\r
7204 endingGame = 0; /* [HGM] crash */
\r
7207 /* Assumes program was just initialized (initString sent).
\r
7208 Leaves program in force mode. */
\r
7210 FeedMovesToProgram(cps, upto)
\r
7211 ChessProgramState *cps;
\r
7216 if (appData.debugMode)
\r
7217 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
7218 startedFromSetupPosition ? "position and " : "",
\r
7219 backwardMostMove, upto, cps->which);
\r
7220 SendToProgram("force\n", cps);
\r
7221 if (startedFromSetupPosition) {
\r
7222 SendBoard(cps, backwardMostMove);
\r
7223 if (appData.debugMode) {
\r
7224 fprintf(debugFP, "feedMoves\n");
\r
7227 for (i = backwardMostMove; i < upto; i++) {
\r
7228 SendMoveToProgram(i, cps);
\r
7234 ResurrectChessProgram()
\r
7236 /* The chess program may have exited.
\r
7237 If so, restart it and feed it all the moves made so far. */
\r
7239 if (appData.noChessProgram || first.pr != NoProc) return;
\r
7241 StartChessProgram(&first);
\r
7242 if (appData.debugMode) {
\r
7243 fprintf(debugFP, "From ResurrectChessProgram\n");
\r
7245 InitChessProgram(&first, FALSE);
\r
7246 FeedMovesToProgram(&first, currentMove);
\r
7248 if (!first.sendTime) {
\r
7249 /* can't tell gnuchess what its clock should read,
\r
7250 so we bow to its notion. */
\r
7252 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
7253 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
7256 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7257 first.analysisSupport) {
\r
7258 SendToProgram("analyze\n", &first);
\r
7259 first.analyzing = TRUE;
\r
7264 * Button procedures
\r
7267 Reset(redraw, init)
\r
7272 if (appData.debugMode) {
\r
7273 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
7274 redraw, init, gameMode);
\r
7276 pausing = pauseExamInvalid = FALSE;
\r
7277 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
7279 whiteFlag = blackFlag = FALSE;
\r
7280 userOfferedDraw = FALSE;
\r
7281 hintRequested = bookRequested = FALSE;
\r
7282 first.maybeThinking = FALSE;
\r
7283 second.maybeThinking = FALSE;
\r
7284 thinkOutput[0] = NULLCHAR;
\r
7285 lastHint[0] = NULLCHAR;
\r
7286 ClearGameInfo(&gameInfo);
\r
7287 gameInfo.variant = StringToVariant(appData.variant);
\r
7288 ics_user_moved = ics_clock_paused = FALSE;
\r
7289 ics_getting_history = H_FALSE;
\r
7291 white_holding[0] = black_holding[0] = NULLCHAR;
\r
7292 ClearProgramStats();
\r
7295 ClearHighlights();
\r
7296 flipView = appData.flipView;
\r
7297 ClearPremoveHighlights();
\r
7298 gotPremove = FALSE;
\r
7299 alarmSounded = FALSE;
\r
7301 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
7302 if(appData.serverMovesName != NULL) {
\r
7303 /* [HGM] prepare to make moves file for broadcasting */
\r
7304 clock_t t = clock();
\r
7305 if(serverMoves != NULL) fclose(serverMoves);
\r
7306 serverMoves = fopen(appData.serverMovesName, "r");
\r
7307 if(serverMoves != NULL) {
\r
7308 fclose(serverMoves);
\r
7309 /* delay 15 sec before overwriting, so all clients can see end */
\r
7310 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
7312 serverMoves = fopen(appData.serverMovesName, "w");
\r
7315 ExitAnalyzeMode();
\r
7316 gameMode = BeginningOfGame;
\r
7318 InitPosition(redraw);
\r
7319 for (i = 0; i < MAX_MOVES; i++) {
\r
7320 if (commentList[i] != NULL) {
\r
7321 free(commentList[i]);
\r
7322 commentList[i] = NULL;
\r
7326 timeRemaining[0][0] = whiteTimeRemaining;
\r
7327 timeRemaining[1][0] = blackTimeRemaining;
\r
7328 if (first.pr == NULL) {
\r
7329 StartChessProgram(&first);
\r
7332 if (appData.debugMode) {
\r
7333 fprintf(debugFP, "From Reset\n");
\r
7335 InitChessProgram(&first, startedFromSetupPosition);}
\r
7337 DisplayMessage("", "");
\r
7338 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7342 AutoPlayGameLoop()
\r
7345 if (!AutoPlayOneMove())
\r
7347 if (matchMode || appData.timeDelay == 0)
\r
7349 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
7351 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
7360 int fromX, fromY, toX, toY;
\r
7362 if (appData.debugMode) {
\r
7363 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
7366 if (gameMode != PlayFromGameFile)
\r
7369 if (currentMove >= forwardMostMove) {
\r
7370 gameMode = EditGame;
\r
7373 /* [AS] Clear current move marker at the end of a game */
\r
7374 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
7379 toX = moveList[currentMove][2] - AAA;
\r
7380 toY = moveList[currentMove][3] - ONE;
\r
7382 if (moveList[currentMove][1] == '@') {
\r
7383 if (appData.highlightLastMove) {
\r
7384 SetHighlights(-1, -1, toX, toY);
\r
7387 fromX = moveList[currentMove][0] - AAA;
\r
7388 fromY = moveList[currentMove][1] - ONE;
\r
7390 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
7392 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
7394 if (appData.highlightLastMove) {
\r
7395 SetHighlights(fromX, fromY, toX, toY);
\r
7398 DisplayMove(currentMove);
\r
7399 SendMoveToProgram(currentMove++, &first);
\r
7400 DisplayBothClocks();
\r
7401 DrawPosition(FALSE, boards[currentMove]);
\r
7402 // [HGM] PV info: always display, routine tests if empty
\r
7403 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7409 LoadGameOneMove(readAhead)
\r
7410 ChessMove readAhead;
\r
7412 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
7413 char promoChar = NULLCHAR;
\r
7414 ChessMove moveType;
\r
7415 char move[MSG_SIZ];
\r
7418 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
7419 gameMode != AnalyzeMode && gameMode != Training) {
\r
7420 gameFileFP = NULL;
\r
7424 yyboardindex = forwardMostMove;
\r
7425 if (readAhead != (ChessMove)0) {
\r
7426 moveType = readAhead;
\r
7428 if (gameFileFP == NULL)
\r
7430 moveType = (ChessMove) yylex();
\r
7434 switch (moveType) {
\r
7436 if (appData.debugMode)
\r
7437 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
7439 if (*p == '{' || *p == '[' || *p == '(') {
\r
7440 p[strlen(p) - 1] = NULLCHAR;
\r
7444 /* append the comment but don't display it */
\r
7445 while (*p == '\n') p++;
\r
7446 AppendComment(currentMove, p);
\r
7449 case WhiteCapturesEnPassant:
\r
7450 case BlackCapturesEnPassant:
\r
7451 case WhitePromotionChancellor:
\r
7452 case BlackPromotionChancellor:
\r
7453 case WhitePromotionArchbishop:
\r
7454 case BlackPromotionArchbishop:
\r
7455 case WhitePromotionQueen:
\r
7456 case BlackPromotionQueen:
\r
7457 case WhitePromotionRook:
\r
7458 case BlackPromotionRook:
\r
7459 case WhitePromotionBishop:
\r
7460 case BlackPromotionBishop:
\r
7461 case WhitePromotionKnight:
\r
7462 case BlackPromotionKnight:
\r
7463 case WhitePromotionKing:
\r
7464 case BlackPromotionKing:
\r
7466 case WhiteKingSideCastle:
\r
7467 case WhiteQueenSideCastle:
\r
7468 case BlackKingSideCastle:
\r
7469 case BlackQueenSideCastle:
\r
7470 case WhiteKingSideCastleWild:
\r
7471 case WhiteQueenSideCastleWild:
\r
7472 case BlackKingSideCastleWild:
\r
7473 case BlackQueenSideCastleWild:
\r
7475 case WhiteHSideCastleFR:
\r
7476 case WhiteASideCastleFR:
\r
7477 case BlackHSideCastleFR:
\r
7478 case BlackASideCastleFR:
\r
7480 if (appData.debugMode)
\r
7481 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7482 fromX = currentMoveString[0] - AAA;
\r
7483 fromY = currentMoveString[1] - ONE;
\r
7484 toX = currentMoveString[2] - AAA;
\r
7485 toY = currentMoveString[3] - ONE;
\r
7486 promoChar = currentMoveString[4];
\r
7491 if (appData.debugMode)
\r
7492 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
7493 fromX = moveType == WhiteDrop ?
\r
7494 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
7495 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
7496 fromY = DROP_RANK;
\r
7497 toX = currentMoveString[2] - AAA;
\r
7498 toY = currentMoveString[3] - ONE;
\r
7504 case GameUnfinished:
\r
7505 if (appData.debugMode)
\r
7506 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
7507 p = strchr(yy_text, '{');
\r
7508 if (p == NULL) p = strchr(yy_text, '(');
\r
7511 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
7513 q = strchr(p, *p == '{' ? '}' : ')');
\r
7514 if (q != NULL) *q = NULLCHAR;
\r
7517 GameEnds(moveType, p, GE_FILE);
\r
7519 if (cmailMsgLoaded) {
\r
7520 ClearHighlights();
\r
7521 flipView = WhiteOnMove(currentMove);
\r
7522 if (moveType == GameUnfinished) flipView = !flipView;
\r
7523 if (appData.debugMode)
\r
7524 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
7528 case (ChessMove) 0: /* end of file */
\r
7529 if (appData.debugMode)
\r
7530 fprintf(debugFP, "Parser hit end of file\n");
\r
7531 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7532 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7536 case MT_CHECKMATE:
\r
7537 if (WhiteOnMove(currentMove)) {
\r
7538 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7540 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7543 case MT_STALEMATE:
\r
7544 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7550 case MoveNumberOne:
\r
7551 if (lastLoadGameStart == GNUChessGame) {
\r
7552 /* GNUChessGames have numbers, but they aren't move numbers */
\r
7553 if (appData.debugMode)
\r
7554 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7555 yy_text, (int) moveType);
\r
7556 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7558 /* else fall thru */
\r
7561 case GNUChessGame:
\r
7563 /* Reached start of next game in file */
\r
7564 if (appData.debugMode)
\r
7565 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
7566 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7567 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7571 case MT_CHECKMATE:
\r
7572 if (WhiteOnMove(currentMove)) {
\r
7573 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
7575 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
7578 case MT_STALEMATE:
\r
7579 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
7585 case PositionDiagram: /* should not happen; ignore */
\r
7586 case ElapsedTime: /* ignore */
\r
7587 case NAG: /* ignore */
\r
7588 if (appData.debugMode)
\r
7589 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
7590 yy_text, (int) moveType);
\r
7591 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
7594 if (appData.testLegality) {
\r
7595 if (appData.debugMode)
\r
7596 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
7597 sprintf(move, "Illegal move: %d.%s%s",
\r
7598 (forwardMostMove / 2) + 1,
\r
7599 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7600 DisplayError(move, 0);
\r
7603 if (appData.debugMode)
\r
7604 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
7605 yy_text, currentMoveString);
\r
7606 fromX = currentMoveString[0] - AAA;
\r
7607 fromY = currentMoveString[1] - ONE;
\r
7608 toX = currentMoveString[2] - AAA;
\r
7609 toY = currentMoveString[3] - ONE;
\r
7610 promoChar = currentMoveString[4];
\r
7614 case AmbiguousMove:
\r
7615 if (appData.debugMode)
\r
7616 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
7617 sprintf(move, "Ambiguous move: %d.%s%s",
\r
7618 (forwardMostMove / 2) + 1,
\r
7619 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7620 DisplayError(move, 0);
\r
7625 case ImpossibleMove:
\r
7626 if (appData.debugMode)
\r
7627 fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);
\r
7628 sprintf(move, "Illegal move: %d.%s%s",
\r
7629 (forwardMostMove / 2) + 1,
\r
7630 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
7631 DisplayError(move, 0);
\r
7637 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
7638 DrawPosition(FALSE, boards[currentMove]);
\r
7639 DisplayBothClocks();
\r
7640 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
7641 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7643 (void) StopLoadGameTimer();
\r
7644 gameFileFP = NULL;
\r
7645 cmailOldMove = forwardMostMove;
\r
7648 /* currentMoveString is set as a side-effect of yylex */
\r
7649 strcat(currentMoveString, "\n");
\r
7650 strcpy(moveList[forwardMostMove], currentMoveString);
\r
7652 thinkOutput[0] = NULLCHAR;
\r
7653 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7654 currentMove = forwardMostMove;
\r
7659 /* Load the nth game from the given file */
\r
7661 LoadGameFromFile(filename, n, title, useList)
\r
7665 /*Boolean*/ int useList;
\r
7668 char buf[MSG_SIZ];
\r
7670 if (strcmp(filename, "-") == 0) {
\r
7674 f = fopen(filename, "rb");
\r
7676 sprintf(buf, "Can't open \"%s\"", filename);
\r
7677 DisplayError(buf, errno);
\r
7681 if (fseek(f, 0, 0) == -1) {
\r
7682 /* f is not seekable; probably a pipe */
\r
7685 if (useList && n == 0) {
\r
7686 int error = GameListBuild(f);
\r
7688 DisplayError("Cannot build game list", error);
\r
7689 } else if (!ListEmpty(&gameList) &&
\r
7690 ((ListGame *) gameList.tailPred)->number > 1) {
\r
7691 GameListPopUp(f, title);
\r
7694 GameListDestroy();
\r
7697 if (n == 0) n = 1;
\r
7698 return LoadGame(f, n, title, FALSE);
\r
7703 MakeRegisteredMove()
\r
7705 int fromX, fromY, toX, toY;
\r
7707 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7708 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
7711 if (appData.debugMode)
\r
7712 fprintf(debugFP, "Restoring %s for game %d\n",
\r
7713 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
7715 thinkOutput[0] = NULLCHAR;
\r
7716 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
7717 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
7718 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
7719 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
7720 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
7721 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
7722 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
7723 ShowMove(fromX, fromY, toX, toY);
\r
7725 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
7726 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
7731 case MT_CHECKMATE:
\r
7732 if (WhiteOnMove(currentMove)) {
\r
7733 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
7735 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
7739 case MT_STALEMATE:
\r
7740 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
7746 case CMAIL_RESIGN:
\r
7747 if (WhiteOnMove(currentMove)) {
\r
7748 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
7750 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
7754 case CMAIL_ACCEPT:
\r
7755 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
7766 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
7768 CmailLoadGame(f, gameNumber, title, useList)
\r
7776 if (gameNumber > nCmailGames) {
\r
7777 DisplayError("No more games in this message", 0);
\r
7780 if (f == lastLoadGameFP) {
\r
7781 int offset = gameNumber - lastLoadGameNumber;
\r
7782 if (offset == 0) {
\r
7783 cmailMsg[0] = NULLCHAR;
\r
7784 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
7785 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
7786 nCmailMovesRegistered--;
\r
7788 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
7789 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
7790 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
7793 if (! RegisterMove()) return FALSE;
\r
7797 retVal = LoadGame(f, gameNumber, title, useList);
\r
7799 /* Make move registered during previous look at this game, if any */
\r
7800 MakeRegisteredMove();
\r
7802 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
7803 commentList[currentMove]
\r
7804 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
7805 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
7811 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
7813 ReloadGame(offset)
\r
7816 int gameNumber = lastLoadGameNumber + offset;
\r
7817 if (lastLoadGameFP == NULL) {
\r
7818 DisplayError("No game has been loaded yet", 0);
\r
7821 if (gameNumber <= 0) {
\r
7822 DisplayError("Can't back up any further", 0);
\r
7825 if (cmailMsgLoaded) {
\r
7826 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
7827 lastLoadGameTitle, lastLoadGameUseList);
\r
7829 return LoadGame(lastLoadGameFP, gameNumber,
\r
7830 lastLoadGameTitle, lastLoadGameUseList);
\r
7836 /* Load the nth game from open file f */
\r
7838 LoadGame(f, gameNumber, title, useList)
\r
7845 char buf[MSG_SIZ];
\r
7846 int gn = gameNumber;
\r
7847 ListGame *lg = NULL;
\r
7848 int numPGNTags = 0;
\r
7850 GameMode oldGameMode;
\r
7851 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
7853 if (appData.debugMode)
\r
7854 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
7856 if (gameMode == Training )
\r
7857 SetTrainingModeOff();
\r
7859 oldGameMode = gameMode;
\r
7860 if (gameMode != BeginningOfGame) {
\r
7861 Reset(FALSE, TRUE);
\r
7865 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
7866 fclose(lastLoadGameFP);
\r
7870 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
7873 fseek(f, lg->offset, 0);
\r
7874 GameListHighlight(gameNumber);
\r
7878 DisplayError("Game number out of range", 0);
\r
7882 GameListDestroy();
\r
7883 if (fseek(f, 0, 0) == -1) {
\r
7884 if (f == lastLoadGameFP ?
\r
7885 gameNumber == lastLoadGameNumber + 1 :
\r
7886 gameNumber == 1) {
\r
7889 DisplayError("Can't seek on game file", 0);
\r
7894 lastLoadGameFP = f;
\r
7895 lastLoadGameNumber = gameNumber;
\r
7896 strcpy(lastLoadGameTitle, title);
\r
7897 lastLoadGameUseList = useList;
\r
7901 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
7902 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
7903 lg->gameInfo.black);
\r
7904 DisplayTitle(buf);
\r
7905 } else if (*title != NULLCHAR) {
\r
7906 if (gameNumber > 1) {
\r
7907 sprintf(buf, "%s %d", title, gameNumber);
\r
7908 DisplayTitle(buf);
\r
7910 DisplayTitle(title);
\r
7914 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
7915 gameMode = PlayFromGameFile;
\r
7919 currentMove = forwardMostMove = backwardMostMove = 0;
\r
7920 CopyBoard(boards[0], initialPosition);
\r
7924 * Skip the first gn-1 games in the file.
\r
7925 * Also skip over anything that precedes an identifiable
\r
7926 * start of game marker, to avoid being confused by
\r
7927 * garbage at the start of the file. Currently
\r
7928 * recognized start of game markers are the move number "1",
\r
7929 * the pattern "gnuchess .* game", the pattern
\r
7930 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
7931 * A game that starts with one of the latter two patterns
\r
7932 * will also have a move number 1, possibly
\r
7933 * following a position diagram.
\r
7934 * 5-4-02: Let's try being more lenient and allowing a game to
\r
7935 * start with an unnumbered move. Does that break anything?
\r
7937 cm = lastLoadGameStart = (ChessMove) 0;
\r
7939 yyboardindex = forwardMostMove;
\r
7940 cm = (ChessMove) yylex();
\r
7942 case (ChessMove) 0:
\r
7943 if (cmailMsgLoaded) {
\r
7944 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
7946 Reset(TRUE, TRUE);
\r
7947 DisplayError("Game not found in file", 0);
\r
7951 case GNUChessGame:
\r
7954 lastLoadGameStart = cm;
\r
7957 case MoveNumberOne:
\r
7958 switch (lastLoadGameStart) {
\r
7959 case GNUChessGame:
\r
7963 case MoveNumberOne:
\r
7964 case (ChessMove) 0:
\r
7965 gn--; /* count this game */
\r
7966 lastLoadGameStart = cm;
\r
7975 switch (lastLoadGameStart) {
\r
7976 case GNUChessGame:
\r
7978 case MoveNumberOne:
\r
7979 case (ChessMove) 0:
\r
7980 gn--; /* count this game */
\r
7981 lastLoadGameStart = cm;
\r
7984 lastLoadGameStart = cm; /* game counted already */
\r
7992 yyboardindex = forwardMostMove;
\r
7993 cm = (ChessMove) yylex();
\r
7994 } while (cm == PGNTag || cm == Comment);
\r
8001 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8002 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8003 != CMAIL_OLD_RESULT) {
\r
8004 nCmailResults ++ ;
\r
8005 cmailResult[ CMAIL_MAX_GAMES
\r
8006 - gn - 1] = CMAIL_OLD_RESULT;
\r
8012 /* Only a NormalMove can be at the start of a game
\r
8013 * without a position diagram. */
\r
8014 if (lastLoadGameStart == (ChessMove) 0) {
\r
8016 lastLoadGameStart = MoveNumberOne;
\r
8025 if (appData.debugMode)
\r
8026 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8028 if (cm == XBoardGame) {
\r
8029 /* Skip any header junk before position diagram and/or move 1 */
\r
8031 yyboardindex = forwardMostMove;
\r
8032 cm = (ChessMove) yylex();
\r
8034 if (cm == (ChessMove) 0 ||
\r
8035 cm == GNUChessGame || cm == XBoardGame) {
\r
8036 /* Empty game; pretend end-of-file and handle later */
\r
8037 cm = (ChessMove) 0;
\r
8041 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8042 cm == PGNTag || cm == Comment)
\r
8045 } else if (cm == GNUChessGame) {
\r
8046 if (gameInfo.event != NULL) {
\r
8047 free(gameInfo.event);
\r
8049 gameInfo.event = StrSave(yy_text);
\r
8052 startedFromSetupPosition = FALSE;
\r
8053 while (cm == PGNTag) {
\r
8054 if (appData.debugMode)
\r
8055 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8056 err = ParsePGNTag(yy_text, &gameInfo);
\r
8057 if (!err) numPGNTags++;
\r
8059 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8060 if(gameInfo.variant != oldVariant) {
\r
8061 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8062 InitPosition(TRUE);
\r
8063 oldVariant = gameInfo.variant;
\r
8064 if (appData.debugMode)
\r
8065 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8069 if (gameInfo.fen != NULL) {
\r
8070 Board initial_position;
\r
8071 startedFromSetupPosition = TRUE;
\r
8072 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8073 Reset(TRUE, TRUE);
\r
8074 DisplayError("Bad FEN position in file", 0);
\r
8077 CopyBoard(boards[0], initial_position);
\r
8078 /* [HGM] copy FEN attributes as well */
\r
8080 initialRulePlies = FENrulePlies;
\r
8081 epStatus[0] = FENepStatus;
\r
8082 for( i=0; i< nrCastlingRights; i++ )
\r
8083 castlingRights[0][i] = FENcastlingRights[i];
\r
8085 if (blackPlaysFirst) {
\r
8086 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8087 CopyBoard(boards[1], initial_position);
\r
8088 strcpy(moveList[0], "");
\r
8089 strcpy(parseList[0], "");
\r
8090 timeRemaining[0][1] = whiteTimeRemaining;
\r
8091 timeRemaining[1][1] = blackTimeRemaining;
\r
8092 if (commentList[0] != NULL) {
\r
8093 commentList[1] = commentList[0];
\r
8094 commentList[0] = NULL;
\r
8097 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8099 yyboardindex = forwardMostMove;
\r
8100 free(gameInfo.fen);
\r
8101 gameInfo.fen = NULL;
\r
8104 yyboardindex = forwardMostMove;
\r
8105 cm = (ChessMove) yylex();
\r
8107 /* Handle comments interspersed among the tags */
\r
8108 while (cm == Comment) {
\r
8110 if (appData.debugMode)
\r
8111 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8113 if (*p == '{' || *p == '[' || *p == '(') {
\r
8114 p[strlen(p) - 1] = NULLCHAR;
\r
8117 while (*p == '\n') p++;
\r
8118 AppendComment(currentMove, p);
\r
8119 yyboardindex = forwardMostMove;
\r
8120 cm = (ChessMove) yylex();
\r
8124 /* don't rely on existence of Event tag since if game was
\r
8125 * pasted from clipboard the Event tag may not exist
\r
8127 if (numPGNTags > 0){
\r
8129 if (gameInfo.variant == VariantNormal) {
\r
8130 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8133 if( appData.autoDisplayTags ) {
\r
8134 tags = PGNTags(&gameInfo);
\r
8135 TagsPopUp(tags, CmailMsg());
\r
8140 /* Make something up, but don't display it now */
\r
8145 if (cm == PositionDiagram) {
\r
8148 Board initial_position;
\r
8150 if (appData.debugMode)
\r
8151 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8153 if (!startedFromSetupPosition) {
\r
8155 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8156 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8166 initial_position[i][j++] = CharToPiece(*p);
\r
8169 while (*p == ' ' || *p == '\t' ||
\r
8170 *p == '\n' || *p == '\r') p++;
\r
8172 if (strncmp(p, "black", strlen("black"))==0)
\r
8173 blackPlaysFirst = TRUE;
\r
8175 blackPlaysFirst = FALSE;
\r
8176 startedFromSetupPosition = TRUE;
\r
8178 CopyBoard(boards[0], initial_position);
\r
8179 if (blackPlaysFirst) {
\r
8180 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8181 CopyBoard(boards[1], initial_position);
\r
8182 strcpy(moveList[0], "");
\r
8183 strcpy(parseList[0], "");
\r
8184 timeRemaining[0][1] = whiteTimeRemaining;
\r
8185 timeRemaining[1][1] = blackTimeRemaining;
\r
8186 if (commentList[0] != NULL) {
\r
8187 commentList[1] = commentList[0];
\r
8188 commentList[0] = NULL;
\r
8191 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8194 yyboardindex = forwardMostMove;
\r
8195 cm = (ChessMove) yylex();
\r
8198 if (first.pr == NoProc) {
\r
8199 StartChessProgram(&first);
\r
8201 if (appData.debugMode) {
\r
8202 fprintf(debugFP, "From LoadGame\n");
\r
8204 InitChessProgram(&first, FALSE);
\r
8205 SendToProgram("force\n", &first);
\r
8206 if (startedFromSetupPosition) {
\r
8207 SendBoard(&first, forwardMostMove);
\r
8208 if (appData.debugMode) {
\r
8209 fprintf(debugFP, "Load Game\n");
\r
8211 DisplayBothClocks();
\r
8214 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
8215 loadFlag = appData.suppressLoadMoves;
\r
8217 while (cm == Comment) {
\r
8219 if (appData.debugMode)
\r
8220 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8222 if (*p == '{' || *p == '[' || *p == '(') {
\r
8223 p[strlen(p) - 1] = NULLCHAR;
\r
8226 while (*p == '\n') p++;
\r
8227 AppendComment(currentMove, p);
\r
8228 yyboardindex = forwardMostMove;
\r
8229 cm = (ChessMove) yylex();
\r
8232 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
8233 cm == WhiteWins || cm == BlackWins ||
\r
8234 cm == GameIsDrawn || cm == GameUnfinished) {
\r
8235 DisplayMessage("", "No moves in game");
\r
8236 if (cmailMsgLoaded) {
\r
8237 if (appData.debugMode)
\r
8238 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
8239 ClearHighlights();
\r
8242 DrawPosition(FALSE, boards[currentMove]);
\r
8243 DisplayBothClocks();
\r
8244 gameMode = EditGame;
\r
8246 gameFileFP = NULL;
\r
8251 // [HGM] PV info: routine tests if comment empty
\r
8252 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
8253 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8255 if (!matchMode && appData.timeDelay != 0)
\r
8256 DrawPosition(FALSE, boards[currentMove]);
\r
8258 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
8259 programStats.ok_to_send = 1;
\r
8262 /* if the first token after the PGN tags is a move
\r
8263 * and not move number 1, retrieve it from the parser
\r
8265 if (cm != MoveNumberOne)
\r
8266 LoadGameOneMove(cm);
\r
8268 /* load the remaining moves from the file */
\r
8269 while (LoadGameOneMove((ChessMove)0)) {
\r
8270 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
8271 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
8274 /* rewind to the start of the game */
\r
8275 currentMove = backwardMostMove;
\r
8277 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8279 if (oldGameMode == AnalyzeFile ||
\r
8280 oldGameMode == AnalyzeMode) {
\r
8281 AnalyzeFileEvent();
\r
8284 if (matchMode || appData.timeDelay == 0) {
\r
8286 gameMode = EditGame;
\r
8288 } else if (appData.timeDelay > 0) {
\r
8289 AutoPlayGameLoop();
\r
8292 if (appData.debugMode)
\r
8293 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
8295 loadFlag = 0; /* [HGM] true game starts */
\r
8299 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
8301 ReloadPosition(offset)
\r
8304 int positionNumber = lastLoadPositionNumber + offset;
\r
8305 if (lastLoadPositionFP == NULL) {
\r
8306 DisplayError("No position has been loaded yet", 0);
\r
8309 if (positionNumber <= 0) {
\r
8310 DisplayError("Can't back up any further", 0);
\r
8313 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
8314 lastLoadPositionTitle);
\r
8317 /* Load the nth position from the given file */
\r
8319 LoadPositionFromFile(filename, n, title)
\r
8325 char buf[MSG_SIZ];
\r
8327 if (strcmp(filename, "-") == 0) {
\r
8328 return LoadPosition(stdin, n, "stdin");
\r
8330 f = fopen(filename, "rb");
\r
8332 sprintf(buf, "Can't open \"%s\"", filename);
\r
8333 DisplayError(buf, errno);
\r
8336 return LoadPosition(f, n, title);
\r
8341 /* Load the nth position from the given open file, and close it */
\r
8343 LoadPosition(f, positionNumber, title)
\r
8345 int positionNumber;
\r
8348 char *p, line[MSG_SIZ];
\r
8349 Board initial_position;
\r
8350 int i, j, fenMode, pn;
\r
8352 if (gameMode == Training )
\r
8353 SetTrainingModeOff();
\r
8355 if (gameMode != BeginningOfGame) {
\r
8356 Reset(FALSE, TRUE);
\r
8358 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
8359 fclose(lastLoadPositionFP);
\r
8361 if (positionNumber == 0) positionNumber = 1;
\r
8362 lastLoadPositionFP = f;
\r
8363 lastLoadPositionNumber = positionNumber;
\r
8364 strcpy(lastLoadPositionTitle, title);
\r
8365 if (first.pr == NoProc) {
\r
8366 StartChessProgram(&first);
\r
8367 if (appData.debugMode) {
\r
8368 fprintf(debugFP, "From LoadPosition\n");
\r
8370 InitChessProgram(&first, FALSE);
\r
8372 pn = positionNumber;
\r
8373 if (positionNumber < 0) {
\r
8374 /* Negative position number means to seek to that byte offset */
\r
8375 if (fseek(f, -positionNumber, 0) == -1) {
\r
8376 DisplayError("Can't seek on position file", 0);
\r
8381 if (fseek(f, 0, 0) == -1) {
\r
8382 if (f == lastLoadPositionFP ?
\r
8383 positionNumber == lastLoadPositionNumber + 1 :
\r
8384 positionNumber == 1) {
\r
8387 DisplayError("Can't seek on position file", 0);
\r
8392 /* See if this file is FEN or old-style xboard */
\r
8393 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
8394 DisplayError("Position not found in file", 0);
\r
8398 switch (line[0]) {
\r
8399 case '#': case 'x':
\r
8403 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
8404 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
8405 case '1': case '2': case '3': case '4': case '5': case '6':
\r
8406 case '7': case '8': case '9':
\r
8407 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
8408 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
8409 case 'C': case 'W': case 'c': case 'w':
\r
8414 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
8415 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
8419 if (fenMode || line[0] == '#') pn--;
\r
8421 /* skip postions before number pn */
\r
8422 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
8423 Reset(TRUE, TRUE);
\r
8424 DisplayError("Position not found in file", 0);
\r
8427 if (fenMode || line[0] == '#') pn--;
\r
8432 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
8433 DisplayError("Bad FEN position in file", 0);
\r
8437 (void) fgets(line, MSG_SIZ, f);
\r
8438 (void) fgets(line, MSG_SIZ, f);
\r
8440 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
8441 (void) fgets(line, MSG_SIZ, f);
\r
8442 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
8445 initial_position[i][j++] = CharToPiece(*p);
\r
8449 blackPlaysFirst = FALSE;
\r
8451 (void) fgets(line, MSG_SIZ, f);
\r
8452 if (strncmp(line, "black", strlen("black"))==0)
\r
8453 blackPlaysFirst = TRUE;
\r
8456 startedFromSetupPosition = TRUE;
\r
8458 SendToProgram("force\n", &first);
\r
8459 CopyBoard(boards[0], initial_position);
\r
8460 /* [HGM] copy FEN attributes as well */
\r
8462 initialRulePlies = FENrulePlies;
\r
8463 epStatus[0] = FENepStatus;
\r
8464 for( i=0; i< nrCastlingRights; i++ )
\r
8465 castlingRights[0][i] = FENcastlingRights[i];
\r
8467 if (blackPlaysFirst) {
\r
8468 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8469 strcpy(moveList[0], "");
\r
8470 strcpy(parseList[0], "");
\r
8471 CopyBoard(boards[1], initial_position);
\r
8472 DisplayMessage("", "Black to play");
\r
8474 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8475 DisplayMessage("", "White to play");
\r
8477 SendBoard(&first, forwardMostMove);
\r
8478 if (appData.debugMode) {
\r
8479 fprintf(debugFP, "Load Position\n");
\r
8482 if (positionNumber > 1) {
\r
8483 sprintf(line, "%s %d", title, positionNumber);
\r
8484 DisplayTitle(line);
\r
8486 DisplayTitle(title);
\r
8488 gameMode = EditGame;
\r
8491 timeRemaining[0][1] = whiteTimeRemaining;
\r
8492 timeRemaining[1][1] = blackTimeRemaining;
\r
8493 DrawPosition(FALSE, boards[currentMove]);
\r
8500 CopyPlayerNameIntoFileName(dest, src)
\r
8501 char **dest, *src;
\r
8503 while (*src != NULLCHAR && *src != ',') {
\r
8504 if (*src == ' ') {
\r
8508 *(*dest)++ = *src++;
\r
8513 char *DefaultFileName(ext)
\r
8516 static char def[MSG_SIZ];
\r
8519 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
8521 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
8523 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
8527 def[0] = NULLCHAR;
\r
8532 /* Save the current game to the given file */
\r
8534 SaveGameToFile(filename, append)
\r
8539 char buf[MSG_SIZ];
\r
8541 if (strcmp(filename, "-") == 0) {
\r
8542 return SaveGame(stdout, 0, NULL);
\r
8544 f = fopen(filename, append ? "a" : "w");
\r
8546 sprintf(buf, "Can't open \"%s\"", filename);
\r
8547 DisplayError(buf, errno);
\r
8550 return SaveGame(f, 0, NULL);
\r
8559 static char buf[MSG_SIZ];
\r
8562 p = strchr(str, ' ');
\r
8563 if (p == NULL) return str;
\r
8564 strncpy(buf, str, p - str);
\r
8565 buf[p - str] = NULLCHAR;
\r
8569 #define PGN_MAX_LINE 75
\r
8571 #define PGN_SIDE_WHITE 0
\r
8572 #define PGN_SIDE_BLACK 1
\r
8575 static int FindFirstMoveOutOfBook( int side )
\r
8579 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
8580 int index = backwardMostMove;
\r
8581 int has_book_hit = 0;
\r
8583 if( (index % 2) != side ) {
\r
8587 while( index < forwardMostMove ) {
\r
8588 /* Check to see if engine is in book */
\r
8589 int depth = pvInfoList[index].depth;
\r
8590 int score = pvInfoList[index].score;
\r
8593 if( depth <= 2 ) {
\r
8596 else if( score == 0 && depth == 63 ) {
\r
8597 in_book = 1; /* Zappa */
\r
8599 else if( score == 2 && depth == 99 ) {
\r
8600 in_book = 1; /* Abrok */
\r
8603 has_book_hit += in_book;
\r
8619 void GetOutOfBookInfo( char * buf )
\r
8623 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8625 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
8626 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
8630 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
8631 for( i=0; i<2; i++ ) {
\r
8635 if( i > 0 && oob[0] >= 0 ) {
\r
8636 strcat( buf, " " );
\r
8639 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
8640 sprintf( buf+strlen(buf), "%s%.2f",
\r
8641 pvInfoList[idx].score >= 0 ? "+" : "",
\r
8642 pvInfoList[idx].score / 100.0 );
\r
8648 /* Save game in PGN style and close the file */
\r
8653 int i, offset, linelen, newblock;
\r
8657 int movelen, numlen, blank;
\r
8658 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
8660 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8662 tm = time((time_t *) NULL);
\r
8664 PrintPGNTags(f, &gameInfo);
\r
8666 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8667 char *fen = PositionToFEN(backwardMostMove, 1);
\r
8668 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
8669 fprintf(f, "\n{--------------\n");
\r
8670 PrintPosition(f, backwardMostMove);
\r
8671 fprintf(f, "--------------}\n");
\r
8675 /* [AS] Out of book annotation */
\r
8676 if( appData.saveOutOfBookInfo ) {
\r
8679 GetOutOfBookInfo( buf );
\r
8681 if( buf[0] != '\0' ) {
\r
8682 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
8689 i = backwardMostMove;
\r
8693 while (i < forwardMostMove) {
\r
8694 /* Print comments preceding this move */
\r
8695 if (commentList[i] != NULL) {
\r
8696 if (linelen > 0) fprintf(f, "\n");
\r
8697 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8702 /* Format move number */
\r
8703 if ((i % 2) == 0) {
\r
8704 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
8707 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
8709 numtext[0] = NULLCHAR;
\r
8712 numlen = strlen(numtext);
\r
8715 /* Print move number */
\r
8716 blank = linelen > 0 && numlen > 0;
\r
8717 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
8726 fprintf(f, numtext);
\r
8727 linelen += numlen;
\r
8730 movetext = SavePart(parseList[i]);
\r
8732 /* [AS] Add PV info if present */
\r
8733 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
8734 /* [HGM] add time */
\r
8735 char buf[MSG_SIZ]; int seconds = 0;
\r
8737 if(i >= backwardMostMove) {
\r
8738 /* take the time that changed */
\r
8739 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];
\r
8741 seconds = timeRemaining[1][i] - timeRemaining[1][i+1];
\r
8744 seconds = pvInfoList[i].time/100;
\r
8745 if (appData.debugMode) {
\r
8746 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
8747 timeRemaining[0][i+1], timeRemaining[0][i],
\r
8748 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
8752 if( seconds < 0 ) buf[0] = 0; else
\r
8753 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);
\r
8754 else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
8756 sprintf( move_buffer, "%s {%s%.2f/%d%s}",
\r
8758 pvInfoList[i].score >= 0 ? "+" : "",
\r
8759 pvInfoList[i].score / 100.0,
\r
8760 pvInfoList[i].depth,
\r
8762 movetext = move_buffer;
\r
8765 movelen = strlen(movetext);
\r
8768 blank = linelen > 0 && movelen > 0;
\r
8769 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
8778 fprintf(f, movetext);
\r
8779 linelen += movelen;
\r
8784 /* Start a new line */
\r
8785 if (linelen > 0) fprintf(f, "\n");
\r
8787 /* Print comments after last move */
\r
8788 if (commentList[i] != NULL) {
\r
8789 fprintf(f, "{\n%s}\n", commentList[i]);
\r
8792 /* Print result */
\r
8793 if (gameInfo.resultDetails != NULL &&
\r
8794 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8795 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
8796 PGNResult(gameInfo.result));
\r
8798 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8805 /* Save game in old style and close the file */
\r
8807 SaveGameOldStyle(f)
\r
8813 tm = time((time_t *) NULL);
\r
8815 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
8816 PrintOpponents(f);
\r
8818 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
8819 fprintf(f, "\n[--------------\n");
\r
8820 PrintPosition(f, backwardMostMove);
\r
8821 fprintf(f, "--------------]\n");
\r
8826 i = backwardMostMove;
\r
8827 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
8829 while (i < forwardMostMove) {
\r
8830 if (commentList[i] != NULL) {
\r
8831 fprintf(f, "[%s]\n", commentList[i]);
\r
8834 if ((i % 2) == 1) {
\r
8835 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
8838 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
8840 if (commentList[i] != NULL) {
\r
8844 if (i >= forwardMostMove) {
\r
8848 fprintf(f, "%s\n", parseList[i]);
\r
8853 if (commentList[i] != NULL) {
\r
8854 fprintf(f, "[%s]\n", commentList[i]);
\r
8857 /* This isn't really the old style, but it's close enough */
\r
8858 if (gameInfo.resultDetails != NULL &&
\r
8859 gameInfo.resultDetails[0] != NULLCHAR) {
\r
8860 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
8861 gameInfo.resultDetails);
\r
8863 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
8870 /* Save the current game to open file f and close the file */
\r
8872 SaveGame(f, dummy, dummy2)
\r
8877 if (gameMode == EditPosition) EditPositionDone();
\r
8878 if (appData.oldSaveStyle)
\r
8879 return SaveGameOldStyle(f);
\r
8881 return SaveGamePGN(f);
\r
8884 /* Save the current position to the given file */
\r
8886 SavePositionToFile(filename)
\r
8890 char buf[MSG_SIZ];
\r
8892 if (strcmp(filename, "-") == 0) {
\r
8893 return SavePosition(stdout, 0, NULL);
\r
8895 f = fopen(filename, "a");
\r
8897 sprintf(buf, "Can't open \"%s\"", filename);
\r
8898 DisplayError(buf, errno);
\r
8901 SavePosition(f, 0, NULL);
\r
8907 /* Save the current position to the given open file and close the file */
\r
8909 SavePosition(f, dummy, dummy2)
\r
8917 if (appData.oldSaveStyle) {
\r
8918 tm = time((time_t *) NULL);
\r
8920 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
8921 PrintOpponents(f);
\r
8922 fprintf(f, "[--------------\n");
\r
8923 PrintPosition(f, currentMove);
\r
8924 fprintf(f, "--------------]\n");
\r
8926 fen = PositionToFEN(currentMove, 1);
\r
8927 fprintf(f, "%s\n", fen);
\r
8935 ReloadCmailMsgEvent(unregister)
\r
8939 static char *inFilename = NULL;
\r
8940 static char *outFilename;
\r
8942 struct stat inbuf, outbuf;
\r
8945 /* Any registered moves are unregistered if unregister is set, */
\r
8946 /* i.e. invoked by the signal handler */
\r
8948 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8949 cmailMoveRegistered[i] = FALSE;
\r
8950 if (cmailCommentList[i] != NULL) {
\r
8951 free(cmailCommentList[i]);
\r
8952 cmailCommentList[i] = NULL;
\r
8955 nCmailMovesRegistered = 0;
\r
8958 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
8959 cmailResult[i] = CMAIL_NOT_RESULT;
\r
8961 nCmailResults = 0;
\r
8963 if (inFilename == NULL) {
\r
8964 /* Because the filenames are static they only get malloced once */
\r
8965 /* and they never get freed */
\r
8966 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
8967 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
8969 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
8970 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
8973 status = stat(outFilename, &outbuf);
\r
8975 cmailMailedMove = FALSE;
\r
8977 status = stat(inFilename, &inbuf);
\r
8978 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
8981 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
8982 counts the games, notes how each one terminated, etc.
\r
8984 It would be nice to remove this kludge and instead gather all
\r
8985 the information while building the game list. (And to keep it
\r
8986 in the game list nodes instead of having a bunch of fixed-size
\r
8987 parallel arrays.) Note this will require getting each game's
\r
8988 termination from the PGN tags, as the game list builder does
\r
8989 not process the game moves. --mann
\r
8991 cmailMsgLoaded = TRUE;
\r
8992 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
8994 /* Load first game in the file or popup game menu */
\r
8995 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
8997 #endif /* !WIN32 */
\r
9005 char string[MSG_SIZ];
\r
9007 if ( cmailMailedMove
\r
9008 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9009 return TRUE; /* Allow free viewing */
\r
9012 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9013 /* with the move registered when the conditions for registering no */
\r
9015 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9016 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9017 nCmailMovesRegistered --;
\r
9019 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9021 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9022 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9026 if (cmailOldMove == -1) {
\r
9027 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
9031 if (currentMove > cmailOldMove + 1) {
\r
9032 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
9036 if (currentMove < cmailOldMove) {
\r
9037 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
9041 if (forwardMostMove > currentMove) {
\r
9042 /* Silently truncate extra moves */
\r
9046 if ( (currentMove == cmailOldMove + 1)
\r
9047 || ( (currentMove == cmailOldMove)
\r
9048 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9049 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9050 if (gameInfo.result != GameUnfinished) {
\r
9051 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9054 if (commentList[currentMove] != NULL) {
\r
9055 cmailCommentList[lastLoadGameNumber - 1]
\r
9056 = StrSave(commentList[currentMove]);
\r
9058 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9060 if (appData.debugMode)
\r
9061 fprintf(debugFP, "Saving %s for game %d\n",
\r
9062 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9065 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9067 f = fopen(string, "w");
\r
9068 if (appData.oldSaveStyle) {
\r
9069 SaveGameOldStyle(f); /* also closes the file */
\r
9071 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9072 f = fopen(string, "w");
\r
9073 SavePosition(f, 0, NULL); /* also closes the file */
\r
9075 fprintf(f, "{--------------\n");
\r
9076 PrintPosition(f, currentMove);
\r
9077 fprintf(f, "--------------}\n\n");
\r
9079 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9082 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9083 nCmailMovesRegistered ++;
\r
9084 } else if (nCmailGames == 1) {
\r
9085 DisplayError("You have not made a move yet", 0);
\r
9096 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9097 FILE *commandOutput;
\r
9098 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9099 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9105 if (! cmailMsgLoaded) {
\r
9106 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
9110 if (nCmailGames == nCmailResults) {
\r
9111 DisplayError("No unfinished games", 0);
\r
9115 #if CMAIL_PROHIBIT_REMAIL
\r
9116 if (cmailMailedMove) {
\r
9117 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
9118 DisplayError(msg, 0);
\r
9123 if (! (cmailMailedMove || RegisterMove())) return;
\r
9125 if ( cmailMailedMove
\r
9126 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9127 sprintf(string, partCommandString,
\r
9128 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9129 commandOutput = popen(string, "rb");
\r
9131 if (commandOutput == NULL) {
\r
9132 DisplayError("Failed to invoke cmail", 0);
\r
9134 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9135 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9137 if (nBuffers > 1) {
\r
9138 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9139 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9140 nBytes = MSG_SIZ - 1;
\r
9142 (void) memcpy(msg, buffer, nBytes);
\r
9144 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9146 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9147 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9150 for (i = 0; i < nCmailGames; i ++) {
\r
9151 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9156 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9158 sprintf(buffer, "%s/%s.%s.archive",
\r
9160 appData.cmailGameName,
\r
9162 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
9163 cmailMsgLoaded = FALSE;
\r
9167 DisplayInformation(msg);
\r
9168 pclose(commandOutput);
\r
9171 if ((*cmailMsg) != '\0') {
\r
9172 DisplayInformation(cmailMsg);
\r
9177 #endif /* !WIN32 */
\r
9186 int prependComma = 0;
\r
9188 char string[MSG_SIZ]; /* Space for game-list */
\r
9191 if (!cmailMsgLoaded) return "";
\r
9193 if (cmailMailedMove) {
\r
9194 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
9196 /* Create a list of games left */
\r
9197 sprintf(string, "[");
\r
9198 for (i = 0; i < nCmailGames; i ++) {
\r
9199 if (! ( cmailMoveRegistered[i]
\r
9200 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
9201 if (prependComma) {
\r
9202 sprintf(number, ",%d", i + 1);
\r
9204 sprintf(number, "%d", i + 1);
\r
9208 strcat(string, number);
\r
9211 strcat(string, "]");
\r
9213 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
9214 switch (nCmailGames) {
\r
9217 "Still need to make move for game\n");
\r
9222 "Still need to make moves for both games\n");
\r
9227 "Still need to make moves for all %d games\n",
\r
9232 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
9235 "Still need to make a move for game %s\n",
\r
9240 if (nCmailResults == nCmailGames) {
\r
9241 sprintf(cmailMsg, "No unfinished games\n");
\r
9243 sprintf(cmailMsg, "Ready to send mail\n");
\r
9249 "Still need to make moves for games %s\n",
\r
9255 #endif /* WIN32 */
\r
9261 if (gameMode == Training)
\r
9262 SetTrainingModeOff();
\r
9264 Reset(TRUE, TRUE);
\r
9265 cmailMsgLoaded = FALSE;
\r
9266 if (appData.icsActive) {
\r
9267 SendToICS(ics_prefix);
\r
9268 SendToICS("refresh\n");
\r
9277 if (exiting > 2) {
\r
9278 /* Give up on clean exit */
\r
9281 if (exiting > 1) {
\r
9282 /* Keep trying for clean exit */
\r
9286 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
9288 if (telnetISR != NULL) {
\r
9289 RemoveInputSource(telnetISR);
\r
9291 if (icsPR != NoProc) {
\r
9292 DestroyChildProcess(icsPR, TRUE);
\r
9295 /* Save game if resource set and not already saved by GameEnds() */
\r
9296 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
9297 && forwardMostMove > 0) {
\r
9298 if (*appData.saveGameFile != NULLCHAR) {
\r
9299 SaveGameToFile(appData.saveGameFile, TRUE);
\r
9300 } else if (appData.autoSaveGames) {
\r
9303 if (*appData.savePositionFile != NULLCHAR) {
\r
9304 SavePositionToFile(appData.savePositionFile);
\r
9307 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9309 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
9310 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER);
\r
9312 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
9313 /* make sure this other one finishes before killing it! */
\r
9314 if(endingGame) { int count = 0;
\r
9315 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
9316 while(endingGame && count++ < 10) DoSleep(1);
\r
9317 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
9320 /* Kill off chess programs */
\r
9321 if (first.pr != NoProc) {
\r
9322 ExitAnalyzeMode();
\r
9324 DoSleep( appData.delayBeforeQuit );
\r
9325 SendToProgram("quit\n", &first);
\r
9326 DoSleep( appData.delayAfterQuit );
\r
9327 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
9329 if (second.pr != NoProc) {
\r
9330 DoSleep( appData.delayBeforeQuit );
\r
9331 SendToProgram("quit\n", &second);
\r
9332 DoSleep( appData.delayAfterQuit );
\r
9333 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
9335 if (first.isr != NULL) {
\r
9336 RemoveInputSource(first.isr);
\r
9338 if (second.isr != NULL) {
\r
9339 RemoveInputSource(second.isr);
\r
9342 ShutDownFrontEnd();
\r
9349 if (appData.debugMode)
\r
9350 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
9354 if (gameMode == MachinePlaysWhite ||
\r
9355 gameMode == MachinePlaysBlack) {
\r
9358 DisplayBothClocks();
\r
9360 if (gameMode == PlayFromGameFile) {
\r
9361 if (appData.timeDelay >= 0)
\r
9362 AutoPlayGameLoop();
\r
9363 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
9364 Reset(FALSE, TRUE);
\r
9365 SendToICS(ics_prefix);
\r
9366 SendToICS("refresh\n");
\r
9367 } else if (currentMove < forwardMostMove) {
\r
9368 ForwardInner(forwardMostMove);
\r
9370 pauseExamInvalid = FALSE;
\r
9372 switch (gameMode) {
\r
9375 case IcsExamining:
\r
9376 pauseExamForwardMostMove = forwardMostMove;
\r
9377 pauseExamInvalid = FALSE;
\r
9378 /* fall through */
\r
9379 case IcsObserving:
\r
9380 case IcsPlayingWhite:
\r
9381 case IcsPlayingBlack:
\r
9385 case PlayFromGameFile:
\r
9386 (void) StopLoadGameTimer();
\r
9390 case BeginningOfGame:
\r
9391 if (appData.icsActive) return;
\r
9392 /* else fall through */
\r
9393 case MachinePlaysWhite:
\r
9394 case MachinePlaysBlack:
\r
9395 case TwoMachinesPlay:
\r
9396 if (forwardMostMove == 0)
\r
9397 return; /* don't pause if no one has moved */
\r
9398 if ((gameMode == MachinePlaysWhite &&
\r
9399 !WhiteOnMove(forwardMostMove)) ||
\r
9400 (gameMode == MachinePlaysBlack &&
\r
9401 WhiteOnMove(forwardMostMove))) {
\r
9412 EditCommentEvent()
\r
9414 char title[MSG_SIZ];
\r
9416 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
9417 strcpy(title, "Edit comment");
\r
9419 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
9420 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
9421 parseList[currentMove - 1]);
\r
9424 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
9431 char *tags = PGNTags(&gameInfo);
\r
9432 EditTagsPopUp(tags);
\r
9437 AnalyzeModeEvent()
\r
9439 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
9442 if (gameMode != AnalyzeFile) {
\r
9444 if (gameMode != EditGame) return;
\r
9445 ResurrectChessProgram();
\r
9446 SendToProgram("analyze\n", &first);
\r
9447 first.analyzing = TRUE;
\r
9448 /*first.maybeThinking = TRUE;*/
\r
9449 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9450 AnalysisPopUp("Analysis",
\r
9451 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9453 gameMode = AnalyzeMode;
\r
9458 StartAnalysisClock();
\r
9459 GetTimeMark(&lastNodeCountTime);
\r
9460 lastNodeCount = 0;
\r
9464 AnalyzeFileEvent()
\r
9466 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
9469 if (gameMode != AnalyzeMode) {
\r
9471 if (gameMode != EditGame) return;
\r
9472 ResurrectChessProgram();
\r
9473 SendToProgram("analyze\n", &first);
\r
9474 first.analyzing = TRUE;
\r
9475 /*first.maybeThinking = TRUE;*/
\r
9476 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
9477 AnalysisPopUp("Analysis",
\r
9478 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
9480 gameMode = AnalyzeFile;
\r
9485 StartAnalysisClock();
\r
9486 GetTimeMark(&lastNodeCountTime);
\r
9487 lastNodeCount = 0;
\r
9491 MachineWhiteEvent()
\r
9493 char buf[MSG_SIZ];
\r
9495 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
9499 if (gameMode == PlayFromGameFile ||
\r
9500 gameMode == TwoMachinesPlay ||
\r
9501 gameMode == Training ||
\r
9502 gameMode == AnalyzeMode ||
\r
9503 gameMode == EndOfGame)
\r
9506 if (gameMode == EditPosition)
\r
9507 EditPositionDone();
\r
9509 if (!WhiteOnMove(currentMove)) {
\r
9510 DisplayError("It is not White's turn", 0);
\r
9514 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9515 ExitAnalyzeMode();
\r
9517 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9518 gameMode == AnalyzeFile)
\r
9521 ResurrectChessProgram(); /* in case it isn't running */
\r
9522 gameMode = MachinePlaysWhite;
\r
9526 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9527 DisplayTitle(buf);
\r
9528 if (first.sendName) {
\r
9529 sprintf(buf, "name %s\n", gameInfo.black);
\r
9530 SendToProgram(buf, &first);
\r
9532 if (first.sendTime) {
\r
9533 if (first.useColors) {
\r
9534 SendToProgram("black\n", &first); /*gnu kludge*/
\r
9536 SendTimeRemaining(&first, TRUE);
\r
9538 if (first.useColors) {
\r
9539 SendToProgram("white\ngo\n", &first);
\r
9541 SendToProgram("go\n", &first);
\r
9543 SetMachineThinkingEnables();
\r
9544 first.maybeThinking = TRUE;
\r
9547 if (appData.autoFlipView && !flipView) {
\r
9548 flipView = !flipView;
\r
9549 DrawPosition(FALSE, NULL);
\r
9554 MachineBlackEvent()
\r
9556 char buf[MSG_SIZ];
\r
9558 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
9562 if (gameMode == PlayFromGameFile ||
\r
9563 gameMode == TwoMachinesPlay ||
\r
9564 gameMode == Training ||
\r
9565 gameMode == AnalyzeMode ||
\r
9566 gameMode == EndOfGame)
\r
9569 if (gameMode == EditPosition)
\r
9570 EditPositionDone();
\r
9572 if (WhiteOnMove(currentMove)) {
\r
9573 DisplayError("It is not Black's turn", 0);
\r
9577 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
9578 ExitAnalyzeMode();
\r
9580 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
9581 gameMode == AnalyzeFile)
\r
9584 ResurrectChessProgram(); /* in case it isn't running */
\r
9585 gameMode = MachinePlaysBlack;
\r
9589 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9590 DisplayTitle(buf);
\r
9591 if (first.sendName) {
\r
9592 sprintf(buf, "name %s\n", gameInfo.white);
\r
9593 SendToProgram(buf, &first);
\r
9595 if (first.sendTime) {
\r
9596 if (first.useColors) {
\r
9597 SendToProgram("white\n", &first); /*gnu kludge*/
\r
9599 SendTimeRemaining(&first, FALSE);
\r
9601 if (first.useColors) {
\r
9602 SendToProgram("black\ngo\n", &first);
\r
9604 SendToProgram("go\n", &first);
\r
9606 SetMachineThinkingEnables();
\r
9607 first.maybeThinking = TRUE;
\r
9610 if (appData.autoFlipView && flipView) {
\r
9611 flipView = !flipView;
\r
9612 DrawPosition(FALSE, NULL);
\r
9618 DisplayTwoMachinesTitle()
\r
9620 char buf[MSG_SIZ];
\r
9621 if (appData.matchGames > 0) {
\r
9622 if (first.twoMachinesColor[0] == 'w') {
\r
9623 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9624 gameInfo.white, gameInfo.black,
\r
9625 first.matchWins, second.matchWins,
\r
9626 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9628 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
9629 gameInfo.white, gameInfo.black,
\r
9630 second.matchWins, first.matchWins,
\r
9631 matchGame - 1 - (first.matchWins + second.matchWins));
\r
9634 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
9636 DisplayTitle(buf);
\r
9640 TwoMachinesEvent P((void))
\r
9643 char buf[MSG_SIZ];
\r
9644 ChessProgramState *onmove;
\r
9646 if (appData.noChessProgram) return;
\r
9648 switch (gameMode) {
\r
9649 case TwoMachinesPlay:
\r
9651 case MachinePlaysWhite:
\r
9652 case MachinePlaysBlack:
\r
9653 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
9654 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
9657 /* fall through */
\r
9658 case BeginningOfGame:
\r
9659 case PlayFromGameFile:
\r
9662 if (gameMode != EditGame) return;
\r
9664 case EditPosition:
\r
9665 EditPositionDone();
\r
9669 ExitAnalyzeMode();
\r
9676 forwardMostMove = currentMove;
\r
9677 ResurrectChessProgram(); /* in case first program isn't running */
\r
9679 if (second.pr == NULL) {
\r
9680 StartChessProgram(&second);
\r
9681 if (second.protocolVersion == 1) {
\r
9682 TwoMachinesEventIfReady();
\r
9684 /* kludge: allow timeout for initial "feature" command */
\r
9686 DisplayMessage("", "Starting second chess program");
\r
9687 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
9691 DisplayMessage("", "");
\r
9692 if (appData.debugMode) {
\r
9693 fprintf(debugFP, "From TwoMachines\n");
\r
9695 InitChessProgram(&second, FALSE);
\r
9696 SendToProgram("force\n", &second);
\r
9697 if (startedFromSetupPosition) {
\r
9698 SendBoard(&second, backwardMostMove);
\r
9699 if (appData.debugMode) {
\r
9700 fprintf(debugFP, "Two Machines\n");
\r
9703 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
9704 SendMoveToProgram(i, &second);
\r
9707 gameMode = TwoMachinesPlay;
\r
9711 DisplayTwoMachinesTitle();
\r
9713 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
9719 SendToProgram(first.computerString, &first);
\r
9720 if (first.sendName) {
\r
9721 sprintf(buf, "name %s\n", second.tidy);
\r
9722 SendToProgram(buf, &first);
\r
9724 SendToProgram(second.computerString, &second);
\r
9725 if (second.sendName) {
\r
9726 sprintf(buf, "name %s\n", first.tidy);
\r
9727 SendToProgram(buf, &second);
\r
9730 if (!first.sendTime || !second.sendTime) {
\r
9732 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9733 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9735 if (onmove->sendTime) {
\r
9736 if (onmove->useColors) {
\r
9737 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
9739 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
9741 if (onmove->useColors) {
\r
9742 SendToProgram(onmove->twoMachinesColor, onmove);
\r
9744 SendToProgram("go\n", onmove);
\r
9745 onmove->maybeThinking = TRUE;
\r
9746 SetMachineThinkingEnables();
\r
9754 if (gameMode == Training) {
\r
9755 SetTrainingModeOff();
\r
9756 gameMode = PlayFromGameFile;
\r
9757 DisplayMessage("", "Training mode off");
\r
9759 gameMode = Training;
\r
9760 animateTraining = appData.animate;
\r
9762 /* make sure we are not already at the end of the game */
\r
9763 if (currentMove < forwardMostMove) {
\r
9764 SetTrainingModeOn();
\r
9765 DisplayMessage("", "Training mode on");
\r
9767 gameMode = PlayFromGameFile;
\r
9768 DisplayError("Already at end of game", 0);
\r
9777 if (!appData.icsActive) return;
\r
9778 switch (gameMode) {
\r
9779 case IcsPlayingWhite:
\r
9780 case IcsPlayingBlack:
\r
9781 case IcsObserving:
\r
9783 case BeginningOfGame:
\r
9784 case IcsExamining:
\r
9790 case EditPosition:
\r
9791 EditPositionDone();
\r
9796 ExitAnalyzeMode();
\r
9804 gameMode = IcsIdle;
\r
9815 switch (gameMode) {
\r
9817 SetTrainingModeOff();
\r
9819 case MachinePlaysWhite:
\r
9820 case MachinePlaysBlack:
\r
9821 case BeginningOfGame:
\r
9822 SendToProgram("force\n", &first);
\r
9823 SetUserThinkingEnables();
\r
9825 case PlayFromGameFile:
\r
9826 (void) StopLoadGameTimer();
\r
9827 if (gameFileFP != NULL) {
\r
9828 gameFileFP = NULL;
\r
9831 case EditPosition:
\r
9832 EditPositionDone();
\r
9836 ExitAnalyzeMode();
\r
9837 SendToProgram("force\n", &first);
\r
9839 case TwoMachinesPlay:
\r
9840 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9841 ResurrectChessProgram();
\r
9842 SetUserThinkingEnables();
\r
9845 ResurrectChessProgram();
\r
9847 case IcsPlayingBlack:
\r
9848 case IcsPlayingWhite:
\r
9849 DisplayError("Warning: You are still playing a game", 0);
\r
9851 case IcsObserving:
\r
9852 DisplayError("Warning: You are still observing a game", 0);
\r
9854 case IcsExamining:
\r
9855 DisplayError("Warning: You are still examining a game", 0);
\r
9866 first.offeredDraw = second.offeredDraw = 0;
\r
9868 if (gameMode == PlayFromGameFile) {
\r
9869 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9870 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9874 if (gameMode == MachinePlaysWhite ||
\r
9875 gameMode == MachinePlaysBlack ||
\r
9876 gameMode == TwoMachinesPlay ||
\r
9877 gameMode == EndOfGame) {
\r
9878 i = forwardMostMove;
\r
9879 while (i > currentMove) {
\r
9880 SendToProgram("undo\n", &first);
\r
9883 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
9884 blackTimeRemaining = timeRemaining[1][currentMove];
\r
9885 DisplayBothClocks();
\r
9886 if (whiteFlag || blackFlag) {
\r
9887 whiteFlag = blackFlag = 0;
\r
9892 gameMode = EditGame;
\r
9899 EditPositionEvent()
\r
9901 if (gameMode == EditPosition) {
\r
9907 if (gameMode != EditGame) return;
\r
9909 gameMode = EditPosition;
\r
9912 if (currentMove > 0)
\r
9913 CopyBoard(boards[0], boards[currentMove]);
\r
9915 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
9917 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9918 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9925 if (first.analysisSupport && first.analyzing) {
\r
9926 SendToProgram("exit\n", &first);
\r
9927 first.analyzing = FALSE;
\r
9929 AnalysisPopDown();
\r
9930 thinkOutput[0] = NULLCHAR;
\r
9934 EditPositionDone()
\r
9936 startedFromSetupPosition = TRUE;
\r
9937 if (appData.debugMode) {
\r
9938 fprintf(debugFP, "From EditPosition\n");
\r
9940 InitChessProgram(&first, FALSE);
\r
9941 SendToProgram("force\n", &first);
\r
9942 if (blackPlaysFirst) {
\r
9943 strcpy(moveList[0], "");
\r
9944 strcpy(parseList[0], "");
\r
9945 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9946 CopyBoard(boards[1], boards[0]);
\r
9948 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9950 SendBoard(&first, forwardMostMove);
\r
9951 if (appData.debugMode) {
\r
9952 fprintf(debugFP, "EditPosDone\n");
\r
9955 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9956 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9957 gameMode = EditGame;
\r
9959 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9960 ClearHighlights(); /* [AS] */
\r
9963 /* Pause for `ms' milliseconds */
\r
9964 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9974 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
9977 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
9979 SendMultiLineToICS(buf)
\r
9982 char temp[MSG_SIZ+1], *p;
\r
9985 len = strlen(buf);
\r
9986 if (len > MSG_SIZ)
\r
9989 strncpy(temp, buf, len);
\r
9994 if (*p == '\n' || *p == '\r')
\r
9999 strcat(temp, "\n");
\r
10001 SendToPlayer(temp, strlen(temp));
\r
10005 SetWhiteToPlayEvent()
\r
10007 if (gameMode == EditPosition) {
\r
10008 blackPlaysFirst = FALSE;
\r
10009 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10010 } else if (gameMode == IcsExamining) {
\r
10011 SendToICS(ics_prefix);
\r
10012 SendToICS("tomove white\n");
\r
10017 SetBlackToPlayEvent()
\r
10019 if (gameMode == EditPosition) {
\r
10020 blackPlaysFirst = TRUE;
\r
10021 currentMove = 1; /* kludge */
\r
10022 DisplayBothClocks();
\r
10024 } else if (gameMode == IcsExamining) {
\r
10025 SendToICS(ics_prefix);
\r
10026 SendToICS("tomove black\n");
\r
10031 EditPositionMenuEvent(selection, x, y)
\r
10032 ChessSquare selection;
\r
10035 char buf[MSG_SIZ];
\r
10036 ChessSquare piece = boards[0][y][x];
\r
10038 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10040 switch (selection) {
\r
10042 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10043 SendToICS(ics_prefix);
\r
10044 SendToICS("bsetup clear\n");
\r
10045 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10046 SendToICS(ics_prefix);
\r
10047 SendToICS("clearboard\n");
\r
10049 for (x = 0; x < BOARD_WIDTH; x++) {
\r
10050 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10051 if (gameMode == IcsExamining) {
\r
10052 if (boards[currentMove][y][x] != EmptySquare) {
\r
10053 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10054 AAA + x, ONE + y);
\r
10058 boards[0][y][x] = EmptySquare;
\r
10063 if (gameMode == EditPosition) {
\r
10064 DrawPosition(FALSE, boards[0]);
\r
10069 SetWhiteToPlayEvent();
\r
10073 SetBlackToPlayEvent();
\r
10076 case EmptySquare:
\r
10077 if (gameMode == IcsExamining) {
\r
10078 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10081 boards[0][y][x] = EmptySquare;
\r
10082 DrawPosition(FALSE, boards[0]);
\r
10086 case PromotePiece:
\r
10087 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10088 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10089 selection = (ChessSquare) (PROMOTED piece);
\r
10090 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10091 else selection = (ChessSquare)((int)piece - 1);
\r
10092 goto defaultlabel;
\r
10094 case DemotePiece:
\r
10095 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10096 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10097 selection = (ChessSquare) (DEMOTED piece);
\r
10098 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10099 else selection = (ChessSquare)((int)piece + 1);
\r
10100 goto defaultlabel;
\r
10104 if(gameInfo.variant == VariantShatranj ||
\r
10105 gameInfo.variant == VariantXiangqi ||
\r
10106 gameInfo.variant == VariantCourier )
\r
10107 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10108 goto defaultlabel;
\r
10112 if(gameInfo.variant == VariantXiangqi)
\r
10113 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
10114 if(gameInfo.variant == VariantKnightmate)
\r
10115 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
10118 if (gameMode == IcsExamining) {
\r
10119 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
10120 PieceToChar(selection), AAA + x, ONE + y);
\r
10123 boards[0][y][x] = selection;
\r
10124 DrawPosition(FALSE, boards[0]);
\r
10132 DropMenuEvent(selection, x, y)
\r
10133 ChessSquare selection;
\r
10136 ChessMove moveType;
\r
10138 switch (gameMode) {
\r
10139 case IcsPlayingWhite:
\r
10140 case MachinePlaysBlack:
\r
10141 if (!WhiteOnMove(currentMove)) {
\r
10142 DisplayMoveError("It is Black's turn");
\r
10145 moveType = WhiteDrop;
\r
10147 case IcsPlayingBlack:
\r
10148 case MachinePlaysWhite:
\r
10149 if (WhiteOnMove(currentMove)) {
\r
10150 DisplayMoveError("It is White's turn");
\r
10153 moveType = BlackDrop;
\r
10156 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
10162 if (moveType == BlackDrop && selection < BlackPawn) {
\r
10163 selection = (ChessSquare) ((int) selection
\r
10164 + (int) BlackPawn - (int) WhitePawn);
\r
10166 if (boards[currentMove][y][x] != EmptySquare) {
\r
10167 DisplayMoveError("That square is occupied");
\r
10171 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
10177 /* Accept a pending offer of any kind from opponent */
\r
10179 if (appData.icsActive) {
\r
10180 SendToICS(ics_prefix);
\r
10181 SendToICS("accept\n");
\r
10182 } else if (cmailMsgLoaded) {
\r
10183 if (currentMove == cmailOldMove &&
\r
10184 commentList[cmailOldMove] != NULL &&
\r
10185 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10186 "Black offers a draw" : "White offers a draw")) {
\r
10188 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10189 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10191 DisplayError("There is no pending offer on this move", 0);
\r
10192 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10195 /* Not used for offers from chess program */
\r
10202 /* Decline a pending offer of any kind from opponent */
\r
10204 if (appData.icsActive) {
\r
10205 SendToICS(ics_prefix);
\r
10206 SendToICS("decline\n");
\r
10207 } else if (cmailMsgLoaded) {
\r
10208 if (currentMove == cmailOldMove &&
\r
10209 commentList[cmailOldMove] != NULL &&
\r
10210 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10211 "Black offers a draw" : "White offers a draw")) {
\r
10213 AppendComment(cmailOldMove, "Draw declined");
\r
10214 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
10215 #endif /*NOTDEF*/
\r
10217 DisplayError("There is no pending offer on this move", 0);
\r
10220 /* Not used for offers from chess program */
\r
10227 /* Issue ICS rematch command */
\r
10228 if (appData.icsActive) {
\r
10229 SendToICS(ics_prefix);
\r
10230 SendToICS("rematch\n");
\r
10237 /* Call your opponent's flag (claim a win on time) */
\r
10238 if (appData.icsActive) {
\r
10239 SendToICS(ics_prefix);
\r
10240 SendToICS("flag\n");
\r
10242 switch (gameMode) {
\r
10245 case MachinePlaysWhite:
\r
10248 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10251 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
10253 DisplayError("Your opponent is not out of time", 0);
\r
10256 case MachinePlaysBlack:
\r
10259 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10262 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
10264 DisplayError("Your opponent is not out of time", 0);
\r
10274 /* Offer draw or accept pending draw offer from opponent */
\r
10276 if (appData.icsActive) {
\r
10277 /* Note: tournament rules require draw offers to be
\r
10278 made after you make your move but before you punch
\r
10279 your clock. Currently ICS doesn't let you do that;
\r
10280 instead, you immediately punch your clock after making
\r
10281 a move, but you can offer a draw at any time. */
\r
10283 SendToICS(ics_prefix);
\r
10284 SendToICS("draw\n");
\r
10285 } else if (cmailMsgLoaded) {
\r
10286 if (currentMove == cmailOldMove &&
\r
10287 commentList[cmailOldMove] != NULL &&
\r
10288 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10289 "Black offers a draw" : "White offers a draw")) {
\r
10290 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10291 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10292 } else if (currentMove == cmailOldMove + 1) {
\r
10293 char *offer = WhiteOnMove(cmailOldMove) ?
\r
10294 "White offers a draw" : "Black offers a draw";
\r
10295 AppendComment(currentMove, offer);
\r
10296 DisplayComment(currentMove - 1, offer);
\r
10297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
10299 DisplayError("You must make your move before offering a draw", 0);
\r
10300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10302 } else if (first.offeredDraw) {
\r
10303 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
10305 if (first.sendDrawOffers) {
\r
10306 SendToProgram("draw\n", &first);
\r
10307 userOfferedDraw = TRUE;
\r
10315 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
10317 if (appData.icsActive) {
\r
10318 SendToICS(ics_prefix);
\r
10319 SendToICS("adjourn\n");
\r
10321 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
10329 /* Offer Abort or accept pending Abort offer from opponent */
\r
10331 if (appData.icsActive) {
\r
10332 SendToICS(ics_prefix);
\r
10333 SendToICS("abort\n");
\r
10335 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
10342 /* Resign. You can do this even if it's not your turn. */
\r
10344 if (appData.icsActive) {
\r
10345 SendToICS(ics_prefix);
\r
10346 SendToICS("resign\n");
\r
10348 switch (gameMode) {
\r
10349 case MachinePlaysWhite:
\r
10350 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
10352 case MachinePlaysBlack:
\r
10353 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
10356 if (cmailMsgLoaded) {
\r
10358 if (WhiteOnMove(cmailOldMove)) {
\r
10359 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
10361 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
10363 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
10374 StopObservingEvent()
\r
10376 /* Stop observing current games */
\r
10377 SendToICS(ics_prefix);
\r
10378 SendToICS("unobserve\n");
\r
10382 StopExaminingEvent()
\r
10384 /* Stop observing current game */
\r
10385 SendToICS(ics_prefix);
\r
10386 SendToICS("unexamine\n");
\r
10390 ForwardInner(target)
\r
10395 if (appData.debugMode)
\r
10396 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
10397 target, currentMove, forwardMostMove);
\r
10399 if (gameMode == EditPosition)
\r
10402 if (gameMode == PlayFromGameFile && !pausing)
\r
10405 if (gameMode == IcsExamining && pausing)
\r
10406 limit = pauseExamForwardMostMove;
\r
10408 limit = forwardMostMove;
\r
10410 if (target > limit) target = limit;
\r
10412 if (target > 0 && moveList[target - 1][0]) {
\r
10413 int fromX, fromY, toX, toY;
\r
10414 toX = moveList[target - 1][2] - AAA;
\r
10415 toY = moveList[target - 1][3] - ONE;
\r
10416 if (moveList[target - 1][1] == '@') {
\r
10417 if (appData.highlightLastMove) {
\r
10418 SetHighlights(-1, -1, toX, toY);
\r
10421 fromX = moveList[target - 1][0] - AAA;
\r
10422 fromY = moveList[target - 1][1] - ONE;
\r
10423 if (target == currentMove + 1) {
\r
10424 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
10426 if (appData.highlightLastMove) {
\r
10427 SetHighlights(fromX, fromY, toX, toY);
\r
10431 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10432 gameMode == Training || gameMode == PlayFromGameFile ||
\r
10433 gameMode == AnalyzeFile) {
\r
10434 while (currentMove < target) {
\r
10435 SendMoveToProgram(currentMove++, &first);
\r
10438 currentMove = target;
\r
10441 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10442 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10443 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10445 DisplayBothClocks();
\r
10446 DisplayMove(currentMove - 1);
\r
10447 DrawPosition(FALSE, boards[currentMove]);
\r
10448 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10449 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
10450 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10458 if (gameMode == IcsExamining && !pausing) {
\r
10459 SendToICS(ics_prefix);
\r
10460 SendToICS("forward\n");
\r
10462 ForwardInner(currentMove + 1);
\r
10469 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10470 /* to optimze, we temporarily turn off analysis mode while we feed
\r
10471 * the remaining moves to the engine. Otherwise we get analysis output
\r
10472 * after each move.
\r
10474 if (first.analysisSupport) {
\r
10475 SendToProgram("exit\nforce\n", &first);
\r
10476 first.analyzing = FALSE;
\r
10480 if (gameMode == IcsExamining && !pausing) {
\r
10481 SendToICS(ics_prefix);
\r
10482 SendToICS("forward 999999\n");
\r
10484 ForwardInner(forwardMostMove);
\r
10487 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10488 /* we have fed all the moves, so reactivate analysis mode */
\r
10489 SendToProgram("analyze\n", &first);
\r
10490 first.analyzing = TRUE;
\r
10491 /*first.maybeThinking = TRUE;*/
\r
10492 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10497 BackwardInner(target)
\r
10500 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
10502 if (appData.debugMode)
\r
10503 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
10504 target, currentMove, forwardMostMove);
\r
10506 if (gameMode == EditPosition) return;
\r
10507 if (currentMove <= backwardMostMove) {
\r
10508 ClearHighlights();
\r
10509 DrawPosition(full_redraw, boards[currentMove]);
\r
10512 if (gameMode == PlayFromGameFile && !pausing)
\r
10515 if (moveList[target][0]) {
\r
10516 int fromX, fromY, toX, toY;
\r
10517 toX = moveList[target][2] - AAA;
\r
10518 toY = moveList[target][3] - ONE;
\r
10519 if (moveList[target][1] == '@') {
\r
10520 if (appData.highlightLastMove) {
\r
10521 SetHighlights(-1, -1, toX, toY);
\r
10524 fromX = moveList[target][0] - AAA;
\r
10525 fromY = moveList[target][1] - ONE;
\r
10526 if (target == currentMove - 1) {
\r
10527 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
10529 if (appData.highlightLastMove) {
\r
10530 SetHighlights(fromX, fromY, toX, toY);
\r
10534 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
10535 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
10536 while (currentMove > target) {
\r
10537 SendToProgram("undo\n", &first);
\r
10541 currentMove = target;
\r
10544 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
10545 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10546 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10548 DisplayBothClocks();
\r
10549 DisplayMove(currentMove - 1);
\r
10550 DrawPosition(full_redraw, boards[currentMove]);
\r
10551 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
10552 // [HGM] PV info: routine tests if comment empty
\r
10553 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
10559 if (gameMode == IcsExamining && !pausing) {
\r
10560 SendToICS(ics_prefix);
\r
10561 SendToICS("backward\n");
\r
10563 BackwardInner(currentMove - 1);
\r
10570 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10571 /* to optimze, we temporarily turn off analysis mode while we undo
\r
10572 * all the moves. Otherwise we get analysis output after each undo.
\r
10574 if (first.analysisSupport) {
\r
10575 SendToProgram("exit\nforce\n", &first);
\r
10576 first.analyzing = FALSE;
\r
10580 if (gameMode == IcsExamining && !pausing) {
\r
10581 SendToICS(ics_prefix);
\r
10582 SendToICS("backward 999999\n");
\r
10584 BackwardInner(backwardMostMove);
\r
10587 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
10588 /* we have fed all the moves, so reactivate analysis mode */
\r
10589 SendToProgram("analyze\n", &first);
\r
10590 first.analyzing = TRUE;
\r
10591 /*first.maybeThinking = TRUE;*/
\r
10592 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10597 ToNrEvent(int to)
\r
10599 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
10600 if (to >= forwardMostMove) to = forwardMostMove;
\r
10601 if (to <= backwardMostMove) to = backwardMostMove;
\r
10602 if (to < currentMove) {
\r
10603 BackwardInner(to);
\r
10605 ForwardInner(to);
\r
10612 if (gameMode != IcsExamining) {
\r
10613 DisplayError("You are not examining a game", 0);
\r
10617 DisplayError("You can't revert while pausing", 0);
\r
10620 SendToICS(ics_prefix);
\r
10621 SendToICS("revert\n");
\r
10625 RetractMoveEvent()
\r
10627 switch (gameMode) {
\r
10628 case MachinePlaysWhite:
\r
10629 case MachinePlaysBlack:
\r
10630 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10631 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10634 if (forwardMostMove < 2) return;
\r
10635 currentMove = forwardMostMove = forwardMostMove - 2;
\r
10636 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10637 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10638 DisplayBothClocks();
\r
10639 DisplayMove(currentMove - 1);
\r
10640 ClearHighlights();/*!! could figure this out*/
\r
10641 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
10642 SendToProgram("remove\n", &first);
\r
10643 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
10646 case BeginningOfGame:
\r
10650 case IcsPlayingWhite:
\r
10651 case IcsPlayingBlack:
\r
10652 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
10653 SendToICS(ics_prefix);
\r
10654 SendToICS("takeback 2\n");
\r
10656 SendToICS(ics_prefix);
\r
10657 SendToICS("takeback 1\n");
\r
10666 ChessProgramState *cps;
\r
10668 switch (gameMode) {
\r
10669 case MachinePlaysWhite:
\r
10670 if (!WhiteOnMove(forwardMostMove)) {
\r
10671 DisplayError("It is your turn", 0);
\r
10676 case MachinePlaysBlack:
\r
10677 if (WhiteOnMove(forwardMostMove)) {
\r
10678 DisplayError("It is your turn", 0);
\r
10683 case TwoMachinesPlay:
\r
10684 if (WhiteOnMove(forwardMostMove) ==
\r
10685 (first.twoMachinesColor[0] == 'w')) {
\r
10691 case BeginningOfGame:
\r
10695 SendToProgram("?\n", cps);
\r
10699 TruncateGameEvent()
\r
10702 if (gameMode != EditGame) return;
\r
10709 if (forwardMostMove > currentMove) {
\r
10710 if (gameInfo.resultDetails != NULL) {
\r
10711 free(gameInfo.resultDetails);
\r
10712 gameInfo.resultDetails = NULL;
\r
10713 gameInfo.result = GameUnfinished;
\r
10715 forwardMostMove = currentMove;
\r
10716 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
10724 if (appData.noChessProgram) return;
\r
10725 switch (gameMode) {
\r
10726 case MachinePlaysWhite:
\r
10727 if (WhiteOnMove(forwardMostMove)) {
\r
10728 DisplayError("Wait until your turn", 0);
\r
10732 case BeginningOfGame:
\r
10733 case MachinePlaysBlack:
\r
10734 if (!WhiteOnMove(forwardMostMove)) {
\r
10735 DisplayError("Wait until your turn", 0);
\r
10740 DisplayError("No hint available", 0);
\r
10743 SendToProgram("hint\n", &first);
\r
10744 hintRequested = TRUE;
\r
10750 if (appData.noChessProgram) return;
\r
10751 switch (gameMode) {
\r
10752 case MachinePlaysWhite:
\r
10753 if (WhiteOnMove(forwardMostMove)) {
\r
10754 DisplayError("Wait until your turn", 0);
\r
10758 case BeginningOfGame:
\r
10759 case MachinePlaysBlack:
\r
10760 if (!WhiteOnMove(forwardMostMove)) {
\r
10761 DisplayError("Wait until your turn", 0);
\r
10765 case EditPosition:
\r
10766 EditPositionDone();
\r
10768 case TwoMachinesPlay:
\r
10773 SendToProgram("bk\n", &first);
\r
10774 bookOutput[0] = NULLCHAR;
\r
10775 bookRequested = TRUE;
\r
10781 char *tags = PGNTags(&gameInfo);
\r
10782 TagsPopUp(tags, CmailMsg());
\r
10786 /* end button procedures */
\r
10789 PrintPosition(fp, move)
\r
10795 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
10796 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
10797 char c = PieceToChar(boards[move][i][j]);
\r
10798 fputc(c == 'x' ? '.' : c, fp);
\r
10799 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
10802 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
10803 fprintf(fp, "white to play\n");
\r
10805 fprintf(fp, "black to play\n");
\r
10809 PrintOpponents(fp)
\r
10812 if (gameInfo.white != NULL) {
\r
10813 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
10815 fprintf(fp, "\n");
\r
10819 /* Find last component of program's own name, using some heuristics */
\r
10821 TidyProgramName(prog, host, buf)
\r
10822 char *prog, *host, buf[MSG_SIZ];
\r
10825 int local = (strcmp(host, "localhost") == 0);
\r
10826 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
10828 while (*p == ' ') p++;
\r
10831 if (*prog == '"' || *prog == '\'') {
\r
10832 q = strchr(prog + 1, *prog);
\r
10834 q = strchr(prog, ' ');
\r
10836 if (q == NULL) q = prog + strlen(prog);
\r
10838 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
10840 if(p == prog && *p == '"') p++;
\r
10841 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
10842 memcpy(buf, p, q - p);
\r
10843 buf[q - p] = NULLCHAR;
\r
10845 strcat(buf, "@");
\r
10846 strcat(buf, host);
\r
10851 TimeControlTagValue()
\r
10853 char buf[MSG_SIZ];
\r
10854 if (!appData.clockMode) {
\r
10855 strcpy(buf, "-");
\r
10856 } else if (movesPerSession > 0) {
\r
10857 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
10858 } else if (timeIncrement == 0) {
\r
10859 sprintf(buf, "%ld", timeControl/1000);
\r
10861 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
10863 return StrSave(buf);
\r
10869 /* This routine is used only for certain modes */
\r
10870 VariantClass v = gameInfo.variant;
\r
10871 ClearGameInfo(&gameInfo);
\r
10872 gameInfo.variant = v;
\r
10874 switch (gameMode) {
\r
10875 case MachinePlaysWhite:
\r
10876 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10877 gameInfo.site = StrSave(HostName());
\r
10878 gameInfo.date = PGNDate();
\r
10879 gameInfo.round = StrSave("-");
\r
10880 gameInfo.white = StrSave(first.tidy);
\r
10881 gameInfo.black = StrSave(UserName());
\r
10882 gameInfo.timeControl = TimeControlTagValue();
\r
10885 case MachinePlaysBlack:
\r
10886 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10887 gameInfo.site = StrSave(HostName());
\r
10888 gameInfo.date = PGNDate();
\r
10889 gameInfo.round = StrSave("-");
\r
10890 gameInfo.white = StrSave(UserName());
\r
10891 gameInfo.black = StrSave(first.tidy);
\r
10892 gameInfo.timeControl = TimeControlTagValue();
\r
10895 case TwoMachinesPlay:
\r
10896 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
10897 gameInfo.site = StrSave(HostName());
\r
10898 gameInfo.date = PGNDate();
\r
10899 if (matchGame > 0) {
\r
10900 char buf[MSG_SIZ];
\r
10901 sprintf(buf, "%d", matchGame);
\r
10902 gameInfo.round = StrSave(buf);
\r
10904 gameInfo.round = StrSave("-");
\r
10906 if (first.twoMachinesColor[0] == 'w') {
\r
10907 gameInfo.white = StrSave(first.tidy);
\r
10908 gameInfo.black = StrSave(second.tidy);
\r
10910 gameInfo.white = StrSave(second.tidy);
\r
10911 gameInfo.black = StrSave(first.tidy);
\r
10913 gameInfo.timeControl = TimeControlTagValue();
\r
10917 gameInfo.event = StrSave("Edited game");
\r
10918 gameInfo.site = StrSave(HostName());
\r
10919 gameInfo.date = PGNDate();
\r
10920 gameInfo.round = StrSave("-");
\r
10921 gameInfo.white = StrSave("-");
\r
10922 gameInfo.black = StrSave("-");
\r
10925 case EditPosition:
\r
10926 gameInfo.event = StrSave("Edited position");
\r
10927 gameInfo.site = StrSave(HostName());
\r
10928 gameInfo.date = PGNDate();
\r
10929 gameInfo.round = StrSave("-");
\r
10930 gameInfo.white = StrSave("-");
\r
10931 gameInfo.black = StrSave("-");
\r
10934 case IcsPlayingWhite:
\r
10935 case IcsPlayingBlack:
\r
10936 case IcsObserving:
\r
10937 case IcsExamining:
\r
10940 case PlayFromGameFile:
\r
10941 gameInfo.event = StrSave("Game from non-PGN file");
\r
10942 gameInfo.site = StrSave(HostName());
\r
10943 gameInfo.date = PGNDate();
\r
10944 gameInfo.round = StrSave("-");
\r
10945 gameInfo.white = StrSave("?");
\r
10946 gameInfo.black = StrSave("?");
\r
10955 ReplaceComment(index, text)
\r
10961 while (*text == '\n') text++;
\r
10962 len = strlen(text);
\r
10963 while (len > 0 && text[len - 1] == '\n') len--;
\r
10965 if (commentList[index] != NULL)
\r
10966 free(commentList[index]);
\r
10969 commentList[index] = NULL;
\r
10972 commentList[index] = (char *) malloc(len + 2);
\r
10973 strncpy(commentList[index], text, len);
\r
10974 commentList[index][len] = '\n';
\r
10975 commentList[index][len + 1] = NULLCHAR;
\r
10988 if (ch == '\r') continue;
\r
10990 } while (ch != '\0');
\r
10994 AppendComment(index, text)
\r
11001 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11004 while (*text == '\n') text++;
\r
11005 len = strlen(text);
\r
11006 while (len > 0 && text[len - 1] == '\n') len--;
\r
11008 if (len == 0) return;
\r
11010 if (commentList[index] != NULL) {
\r
11011 old = commentList[index];
\r
11012 oldlen = strlen(old);
\r
11013 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11014 strcpy(commentList[index], old);
\r
11016 strncpy(&commentList[index][oldlen], text, len);
\r
11017 commentList[index][oldlen + len] = '\n';
\r
11018 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11020 commentList[index] = (char *) malloc(len + 2);
\r
11021 strncpy(commentList[index], text, len);
\r
11022 commentList[index][len] = '\n';
\r
11023 commentList[index][len + 1] = NULLCHAR;
\r
11027 static char * FindStr( char * text, char * sub_text )
\r
11029 char * result = strstr( text, sub_text );
\r
11031 if( result != NULL ) {
\r
11032 result += strlen( sub_text );
\r
11038 /* [AS] Try to extract PV info from PGN comment */
\r
11039 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11040 char *GetInfoFromComment( int index, char * text )
\r
11042 char * sep = text;
\r
11044 if( text != NULL && index > 0 ) {
\r
11047 int time = -1, sec = 0;
\r
11048 char * s_eval = FindStr( text, "[%eval " );
\r
11049 char * s_emt = FindStr( text, "[%emt " );
\r
11051 if( s_eval != NULL || s_emt != NULL ) {
\r
11055 if( s_eval != NULL ) {
\r
11056 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11060 if( delim != ']' ) {
\r
11065 if( s_emt != NULL ) {
\r
11069 /* We expect something like: [+|-]nnn.nn/dd */
\r
11070 int score_lo = 0;
\r
11072 sep = strchr( text, '/' );
\r
11073 if( sep == NULL || sep < (text+4) ) {
\r
11077 time = -1; sec = -1;
\r
11078 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11079 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11080 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11084 if( score_lo < 0 || score_lo >= 100 ) {
\r
11088 if(sec >= 0) time = 60*time + sec;
\r
11089 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11091 /* [HGM] PV time: now locate end of PV info */
\r
11092 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11094 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11096 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11097 while(*sep == ' ') sep++;
\r
11100 if( depth <= 0 ) {
\r
11108 pvInfoList[index-1].depth = depth;
\r
11109 pvInfoList[index-1].score = score;
\r
11110 pvInfoList[index-1].time = time;
\r
11116 SendToProgram(message, cps)
\r
11118 ChessProgramState *cps;
\r
11120 int count, outCount, error;
\r
11121 char buf[MSG_SIZ];
\r
11123 if (cps->pr == NULL) return;
\r
11126 if (appData.debugMode) {
\r
11128 GetTimeMark(&now);
\r
11129 fprintf(debugFP, "%ld >%-6s: %s",
\r
11130 SubtractTimeMarks(&now, &programStartTime),
\r
11131 cps->which, message);
\r
11134 count = strlen(message);
\r
11135 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
11136 if (outCount < count && !exiting
\r
11137 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
11138 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
11139 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11140 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11141 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11142 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11144 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11146 gameInfo.resultDetails = buf;
\r
11148 DisplayFatalError(buf, error, 1);
\r
11153 ReceiveFromProgram(isr, closure, message, count, error)
\r
11154 InputSourceRef isr;
\r
11155 VOIDSTAR closure;
\r
11161 char buf[MSG_SIZ];
\r
11162 ChessProgramState *cps = (ChessProgramState *)closure;
\r
11164 if (isr != cps->isr) return; /* Killed intentionally */
\r
11165 if (count <= 0) {
\r
11166 if (count == 0) {
\r
11168 "Error: %s chess program (%s) exited unexpectedly",
\r
11169 cps->which, cps->program);
\r
11170 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11171 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11172 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11173 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11175 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11177 gameInfo.resultDetails = buf;
\r
11179 RemoveInputSource(cps->isr);
\r
11180 DisplayFatalError(buf, 0, 1);
\r
11183 "Error reading from %s chess program (%s)",
\r
11184 cps->which, cps->program);
\r
11185 RemoveInputSource(cps->isr);
\r
11187 /* [AS] Program is misbehaving badly... kill it */
\r
11188 if( count == -2 ) {
\r
11189 DestroyChildProcess( cps->pr, 9 );
\r
11190 cps->pr = NoProc;
\r
11193 DisplayFatalError(buf, error, 1);
\r
11198 if ((end_str = strchr(message, '\r')) != NULL)
\r
11199 *end_str = NULLCHAR;
\r
11200 if ((end_str = strchr(message, '\n')) != NULL)
\r
11201 *end_str = NULLCHAR;
\r
11203 if (appData.debugMode) {
\r
11205 GetTimeMark(&now);
\r
11206 fprintf(debugFP, "%ld <%-6s: %s\n",
\r
11207 SubtractTimeMarks(&now, &programStartTime),
\r
11208 cps->which, message);
\r
11210 HandleMachineMove(message, cps);
\r
11215 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
11216 ChessProgramState *cps;
\r
11217 int mps, inc, sd, st;
\r
11220 char buf[MSG_SIZ];
\r
11223 if( timeControl_2 > 0 ) {
\r
11224 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
11225 tc = timeControl_2;
\r
11228 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
11229 inc /= cps->timeOdds;
\r
11230 st /= cps->timeOdds;
\r
11232 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
11235 /* Set exact time per move, normally using st command */
\r
11236 if (cps->stKludge) {
\r
11237 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
11238 seconds = st % 60;
\r
11239 if (seconds == 0) {
\r
11240 sprintf(buf, "level 1 %d\n", st/60);
\r
11242 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
11245 sprintf(buf, "st %d\n", st);
\r
11248 /* Set conventional or incremental time control, using level command */
\r
11249 if (seconds == 0) {
\r
11250 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
11251 Fixed in later versions, but still avoid :seconds
\r
11252 when seconds is 0. */
\r
11253 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
11255 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
11256 seconds, inc/1000);
\r
11259 SendToProgram(buf, cps);
\r
11261 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
11262 /* Orthogonally, limit search to given depth */
\r
11264 if (cps->sdKludge) {
\r
11265 sprintf(buf, "depth\n%d\n", sd);
\r
11267 sprintf(buf, "sd %d\n", sd);
\r
11269 SendToProgram(buf, cps);
\r
11273 ChessProgramState *WhitePlayer()
\r
11274 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
11276 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
\r
11282 SendTimeRemaining(cps, machineWhite)
\r
11283 ChessProgramState *cps;
\r
11284 int /*boolean*/ machineWhite;
\r
11286 char message[MSG_SIZ];
\r
11287 long time, otime;
\r
11289 /* Note: this routine must be called when the clocks are stopped
\r
11290 or when they have *just* been set or switched; otherwise
\r
11291 it will be off by the time since the current tick started.
\r
11293 if (machineWhite) {
\r
11294 time = whiteTimeRemaining / 10;
\r
11295 otime = blackTimeRemaining / 10;
\r
11297 time = blackTimeRemaining / 10;
\r
11298 otime = whiteTimeRemaining / 10;
\r
11300 /* [HGM] translate opponent's time by time-odds factor */
\r
11301 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
11302 if (appData.debugMode) {
\r
11303 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
11306 if (time <= 0) time = 1;
\r
11307 if (otime <= 0) otime = 1;
\r
11309 sprintf(message, "time %ld\n", time);
\r
11310 SendToProgram(message, cps);
\r
11312 sprintf(message, "otim %ld\n", otime);
\r
11313 SendToProgram(message, cps);
\r
11317 BoolFeature(p, name, loc, cps)
\r
11321 ChessProgramState *cps;
\r
11323 char buf[MSG_SIZ];
\r
11324 int len = strlen(name);
\r
11326 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
11328 sscanf(*p, "%d", &val);
\r
11329 *loc = (val != 0);
\r
11330 while (**p && **p != ' ') (*p)++;
\r
11331 sprintf(buf, "accepted %s\n", name);
\r
11332 SendToProgram(buf, cps);
\r
11339 IntFeature(p, name, loc, cps)
\r
11343 ChessProgramState *cps;
\r
11345 char buf[MSG_SIZ];
\r
11346 int len = strlen(name);
\r
11347 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
11349 sscanf(*p, "%d", loc);
\r
11350 while (**p && **p != ' ') (*p)++;
\r
11351 sprintf(buf, "accepted %s\n", name);
\r
11352 SendToProgram(buf, cps);
\r
11359 StringFeature(p, name, loc, cps)
\r
11363 ChessProgramState *cps;
\r
11365 char buf[MSG_SIZ];
\r
11366 int len = strlen(name);
\r
11367 if (strncmp((*p), name, len) == 0
\r
11368 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
11370 sscanf(*p, "%[^\"]", loc);
\r
11371 while (**p && **p != '\"') (*p)++;
\r
11372 if (**p == '\"') (*p)++;
\r
11373 sprintf(buf, "accepted %s\n", name);
\r
11374 SendToProgram(buf, cps);
\r
11381 FeatureDone(cps, val)
\r
11382 ChessProgramState* cps;
\r
11385 DelayedEventCallback cb = GetDelayedEvent();
\r
11386 if ((cb == InitBackEnd3 && cps == &first) ||
\r
11387 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
11388 CancelDelayedEvent();
\r
11389 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
11391 cps->initDone = val;
\r
11394 /* Parse feature command from engine */
\r
11396 ParseFeatures(args, cps)
\r
11398 ChessProgramState *cps;
\r
11403 char buf[MSG_SIZ];
\r
11406 while (*p == ' ') p++;
\r
11407 if (*p == NULLCHAR) return;
\r
11409 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
11410 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
11411 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
11412 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
11413 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
11414 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
11415 /* Engine can disable reuse, but can't enable it if user said no */
\r
11416 if (!val) cps->reuse = FALSE;
\r
11419 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
11420 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
11421 if (gameMode == TwoMachinesPlay) {
\r
11422 DisplayTwoMachinesTitle();
\r
11424 DisplayTitle("");
\r
11428 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
11429 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
11430 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
11431 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
11432 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
11433 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
11434 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
11435 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
11436 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
11437 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
11438 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
11439 if (IntFeature(&p, "done", &val, cps)) {
\r
11440 FeatureDone(cps, val);
\r
11443 /* Added by Tord: */
\r
11444 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
11445 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
11446 /* End of additions by Tord */
\r
11448 /* unknown feature: complain and skip */
\r
11450 while (*q && *q != '=') q++;
\r
11451 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
11452 SendToProgram(buf, cps);
\r
11456 if (*p == '\"') {
\r
11458 while (*p && *p != '\"') p++;
\r
11459 if (*p == '\"') p++;
\r
11461 while (*p && *p != ' ') p++;
\r
11469 PeriodicUpdatesEvent(newState)
\r
11472 if (newState == appData.periodicUpdates)
\r
11475 appData.periodicUpdates=newState;
\r
11477 /* Display type changes, so update it now */
\r
11478 DisplayAnalysis();
\r
11480 /* Get the ball rolling again... */
\r
11482 AnalysisPeriodicEvent(1);
\r
11483 StartAnalysisClock();
\r
11488 PonderNextMoveEvent(newState)
\r
11491 if (newState == appData.ponderNextMove) return;
\r
11492 if (gameMode == EditPosition) EditPositionDone();
\r
11494 SendToProgram("hard\n", &first);
\r
11495 if (gameMode == TwoMachinesPlay) {
\r
11496 SendToProgram("hard\n", &second);
\r
11499 SendToProgram("easy\n", &first);
\r
11500 thinkOutput[0] = NULLCHAR;
\r
11501 if (gameMode == TwoMachinesPlay) {
\r
11502 SendToProgram("easy\n", &second);
\r
11505 appData.ponderNextMove = newState;
\r
11509 ShowThinkingEvent(newState)
\r
11512 if (newState == appData.showThinking) return;
\r
11513 if (gameMode == EditPosition) EditPositionDone();
\r
11515 SendToProgram("post\n", &first);
\r
11516 if (gameMode == TwoMachinesPlay) {
\r
11517 SendToProgram("post\n", &second);
\r
11520 SendToProgram("nopost\n", &first);
\r
11521 thinkOutput[0] = NULLCHAR;
\r
11522 if (gameMode == TwoMachinesPlay) {
\r
11523 SendToProgram("nopost\n", &second);
\r
11526 appData.showThinking = newState;
\r
11530 AskQuestionEvent(title, question, replyPrefix, which)
\r
11531 char *title; char *question; char *replyPrefix; char *which;
\r
11533 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
11534 if (pr == NoProc) return;
\r
11535 AskQuestion(title, question, replyPrefix, pr);
\r
11539 DisplayMove(moveNumber)
\r
11542 char message[MSG_SIZ];
\r
11543 char res[MSG_SIZ];
\r
11544 char cpThinkOutput[MSG_SIZ];
\r
11546 if (moveNumber == forwardMostMove - 1 ||
\r
11547 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11549 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
11551 if (strchr(cpThinkOutput, '\n')) {
\r
11552 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
11555 *cpThinkOutput = NULLCHAR;
\r
11558 /* [AS] Hide thinking from human user */
\r
11559 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
11560 *cpThinkOutput = NULLCHAR;
\r
11561 if( thinkOutput[0] != NULLCHAR ) {
\r
11564 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
11565 cpThinkOutput[i] = '.';
\r
11567 cpThinkOutput[i] = NULLCHAR;
\r
11568 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
11572 if (moveNumber == forwardMostMove - 1 &&
\r
11573 gameInfo.resultDetails != NULL) {
\r
11574 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
11575 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
11577 sprintf(res, " {%s} %s",
\r
11578 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
11581 res[0] = NULLCHAR;
\r
11584 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11585 DisplayMessage(res, cpThinkOutput);
\r
11587 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
11588 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11589 parseList[moveNumber], res);
\r
11590 DisplayMessage(message, cpThinkOutput);
\r
11595 DisplayAnalysisText(text)
\r
11598 char buf[MSG_SIZ];
\r
11600 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11601 sprintf(buf, "Analysis (%s)", first.tidy);
\r
11602 AnalysisPopUp(buf, text);
\r
11607 only_one_move(str)
\r
11610 while (*str && isspace(*str)) ++str;
\r
11611 while (*str && !isspace(*str)) ++str;
\r
11612 if (!*str) return 1;
\r
11613 while (*str && isspace(*str)) ++str;
\r
11614 if (!*str) return 1;
\r
11619 DisplayAnalysis()
\r
11621 char buf[MSG_SIZ];
\r
11622 char lst[MSG_SIZ / 2];
\r
11624 static char *xtra[] = { "", " (--)", " (++)" };
\r
11627 if (programStats.time == 0) {
\r
11628 programStats.time = 1;
\r
11631 if (programStats.got_only_move) {
\r
11632 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
11634 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
11636 nps = (((double)programStats.nodes) /
\r
11637 (((double)programStats.time)/100.0));
\r
11639 cs = programStats.time % 100;
\r
11640 s = programStats.time / 100;
\r
11641 h = (s / (60*60));
\r
11646 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
11647 if (programStats.move_name[0] != NULLCHAR) {
\r
11648 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11649 programStats.depth,
\r
11650 programStats.nr_moves-programStats.moves_left,
\r
11651 programStats.nr_moves, programStats.move_name,
\r
11652 ((float)programStats.score)/100.0, lst,
\r
11653 only_one_move(lst)?
\r
11654 xtra[programStats.got_fail] : "",
\r
11655 programStats.nodes, (int)nps, h, m, s, cs);
\r
11657 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11658 programStats.depth,
\r
11659 programStats.nr_moves-programStats.moves_left,
\r
11660 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
11662 only_one_move(lst)?
\r
11663 xtra[programStats.got_fail] : "",
\r
11664 programStats.nodes, (int)nps, h, m, s, cs);
\r
11667 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
11668 programStats.depth,
\r
11669 ((float)programStats.score)/100.0,
\r
11671 only_one_move(lst)?
\r
11672 xtra[programStats.got_fail] : "",
\r
11673 programStats.nodes, (int)nps, h, m, s, cs);
\r
11676 DisplayAnalysisText(buf);
\r
11680 DisplayComment(moveNumber, text)
\r
11684 char title[MSG_SIZ];
\r
11685 char buf[8000]; // comment can be long!
\r
11686 int score, depth;
\r
11688 if( appData.autoDisplayComment ) {
\r
11689 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
11690 strcpy(title, "Comment");
\r
11692 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
11693 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
11694 parseList[moveNumber]);
\r
11696 } else title[0] = 0;
\r
11698 // [HGM] PV info: display PV info together with (or as) comment
\r
11699 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
11700 if(text == NULL) text = "";
\r
11701 score = pvInfoList[moveNumber].score;
\r
11702 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
11703 depth, pvInfoList[moveNumber].time, text);
\r
11704 CommentPopUp(title, buf);
\r
11706 if (text != NULL)
\r
11707 CommentPopUp(title, text);
\r
11710 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
11711 * might be busy thinking or pondering. It can be omitted if your
\r
11712 * gnuchess is configured to stop thinking immediately on any user
\r
11713 * input. However, that gnuchess feature depends on the FIONREAD
\r
11714 * ioctl, which does not work properly on some flavors of Unix.
\r
11718 ChessProgramState *cps;
\r
11721 if (!cps->useSigint) return;
\r
11722 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
11723 switch (gameMode) {
\r
11724 case MachinePlaysWhite:
\r
11725 case MachinePlaysBlack:
\r
11726 case TwoMachinesPlay:
\r
11727 case IcsPlayingWhite:
\r
11728 case IcsPlayingBlack:
\r
11729 case AnalyzeMode:
\r
11730 case AnalyzeFile:
\r
11731 /* Skip if we know it isn't thinking */
\r
11732 if (!cps->maybeThinking) return;
\r
11733 if (appData.debugMode)
\r
11734 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
11735 InterruptChildProcess(cps->pr);
\r
11736 cps->maybeThinking = FALSE;
\r
11741 #endif /*ATTENTION*/
\r
11747 if (whiteTimeRemaining <= 0) {
\r
11748 if (!whiteFlag) {
\r
11749 whiteFlag = TRUE;
\r
11750 if (appData.icsActive) {
\r
11751 if (appData.autoCallFlag &&
\r
11752 gameMode == IcsPlayingBlack && !blackFlag) {
\r
11753 SendToICS(ics_prefix);
\r
11754 SendToICS("flag\n");
\r
11758 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11760 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
11761 if (appData.autoCallFlag) {
\r
11762 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
11769 if (blackTimeRemaining <= 0) {
\r
11770 if (!blackFlag) {
\r
11771 blackFlag = TRUE;
\r
11772 if (appData.icsActive) {
\r
11773 if (appData.autoCallFlag &&
\r
11774 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
11775 SendToICS(ics_prefix);
\r
11776 SendToICS("flag\n");
\r
11780 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
11782 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
11783 if (appData.autoCallFlag) {
\r
11784 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
11795 CheckTimeControl()
\r
11797 if (!appData.clockMode || appData.icsActive ||
\r
11798 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
11801 * add time to clocks when time control is achieved ([HGM] now also used fot increment)
\r
11803 if ( !WhiteOnMove(forwardMostMove) )
\r
11804 /* White made time control */
\r
11805 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
11806 /* [HGM] time odds: correct new time quota for time odds! */
\r
11807 / WhitePlayer()->timeOdds;
\r
11809 /* Black made time control */
\r
11810 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
11811 / WhitePlayer()->other->timeOdds;
\r
11815 DisplayBothClocks()
\r
11817 int wom = gameMode == EditPosition ?
\r
11818 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
11819 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
11820 DisplayBlackClock(blackTimeRemaining, !wom);
\r
11824 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
11825 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
11826 to use other calls if you don't. Clocks will be less accurate if
\r
11827 you have neither ftime nor gettimeofday.
\r
11830 /* Get the current time as a TimeMark */
\r
11835 #if HAVE_GETTIMEOFDAY
\r
11837 struct timeval timeVal;
\r
11838 struct timezone timeZone;
\r
11840 gettimeofday(&timeVal, &timeZone);
\r
11841 tm->sec = (long) timeVal.tv_sec;
\r
11842 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
11844 #else /*!HAVE_GETTIMEOFDAY*/
\r
11847 #include <sys/timeb.h>
\r
11848 struct timeb timeB;
\r
11851 tm->sec = (long) timeB.time;
\r
11852 tm->ms = (int) timeB.millitm;
\r
11854 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
11855 tm->sec = (long) time(NULL);
\r
11861 /* Return the difference in milliseconds between two
\r
11862 time marks. We assume the difference will fit in a long!
\r
11865 SubtractTimeMarks(tm2, tm1)
\r
11866 TimeMark *tm2, *tm1;
\r
11868 return 1000L*(tm2->sec - tm1->sec) +
\r
11869 (long) (tm2->ms - tm1->ms);
\r
11874 * Code to manage the game clocks.
\r
11876 * In tournament play, black starts the clock and then white makes a move.
\r
11877 * We give the human user a slight advantage if he is playing white---the
\r
11878 * clocks don't run until he makes his first move, so it takes zero time.
\r
11879 * Also, we don't account for network lag, so we could get out of sync
\r
11880 * with GNU Chess's clock -- but then, referees are always right.
\r
11883 static TimeMark tickStartTM;
\r
11884 static long intendedTickLength;
\r
11887 NextTickLength(timeRemaining)
\r
11888 long timeRemaining;
\r
11890 long nominalTickLength, nextTickLength;
\r
11892 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
11893 nominalTickLength = 100L;
\r
11895 nominalTickLength = 1000L;
\r
11896 nextTickLength = timeRemaining % nominalTickLength;
\r
11897 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
11899 return nextTickLength;
\r
11902 /* Adjust clock one minute up or down */
\r
11904 AdjustClock(Boolean which, int dir)
\r
11906 if(which) blackTimeRemaining += 60000*dir;
\r
11907 else whiteTimeRemaining += 60000*dir;
\r
11908 DisplayBothClocks();
\r
11911 /* Stop clocks and reset to a fresh time control */
\r
11915 (void) StopClockTimer();
\r
11916 if (appData.icsActive) {
\r
11917 whiteTimeRemaining = blackTimeRemaining = 0;
\r
11918 } else { /* [HGM] correct new time quote for time odds */
\r
11919 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
11920 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
11922 if (whiteFlag || blackFlag) {
\r
11923 DisplayTitle("");
\r
11924 whiteFlag = blackFlag = FALSE;
\r
11926 DisplayBothClocks();
\r
11929 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
11931 /* Decrement running clock by amount of time that has passed */
\r
11933 DecrementClocks()
\r
11935 long timeRemaining;
\r
11936 long lastTickLength, fudge;
\r
11939 if (!appData.clockMode) return;
\r
11940 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
11942 GetTimeMark(&now);
\r
11944 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
11946 /* Fudge if we woke up a little too soon */
\r
11947 fudge = intendedTickLength - lastTickLength;
\r
11948 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
11950 if (WhiteOnMove(forwardMostMove)) {
\r
11951 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
11952 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
11953 WhiteOnMove(currentMove));
\r
11955 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
11956 DisplayBlackClock(blackTimeRemaining - fudge,
\r
11957 !WhiteOnMove(currentMove));
\r
11960 if (CheckFlags()) return;
\r
11962 tickStartTM = now;
\r
11963 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
11964 StartClockTimer(intendedTickLength);
\r
11966 /* if the time remaining has fallen below the alarm threshold, sound the
\r
11967 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
11968 * with increment) the time remaining has increased to a level above the
\r
11969 * threshold, reset the alarm so it can sound again.
\r
11972 if (appData.icsActive && appData.icsAlarm) {
\r
11974 /* make sure we are dealing with the user's clock */
\r
11975 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
11976 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
11979 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
11980 alarmSounded = FALSE;
\r
11981 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
11982 PlayAlarmSound();
\r
11983 alarmSounded = TRUE;
\r
11989 /* A player has just moved, so stop the previously running
\r
11990 clock and (if in clock mode) start the other one.
\r
11991 We redisplay both clocks in case we're in ICS mode, because
\r
11992 ICS gives us an update to both clocks after every move.
\r
11993 Note that this routine is called *after* forwardMostMove
\r
11994 is updated, so the last fractional tick must be subtracted
\r
11995 from the color that is *not* on move now.
\r
12000 long lastTickLength;
\r
12002 int flagged = FALSE;
\r
12004 GetTimeMark(&now);
\r
12006 if (StopClockTimer() && appData.clockMode) {
\r
12007 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12008 if (WhiteOnMove(forwardMostMove)) {
\r
12009 blackTimeRemaining -= lastTickLength;
\r
12010 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12011 if(pvInfoList[forwardMostMove-1].time == -1)
\r
12012 pvInfoList[forwardMostMove-1].time =
\r
12013 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
12015 whiteTimeRemaining -= lastTickLength;
\r
12016 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12017 if(pvInfoList[forwardMostMove-1].time == -1)
\r
12018 pvInfoList[forwardMostMove-1].time =
\r
12019 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
12021 flagged = CheckFlags();
\r
12023 CheckTimeControl();
\r
12025 if (flagged || !appData.clockMode) return;
\r
12027 switch (gameMode) {
\r
12028 case MachinePlaysBlack:
\r
12029 case MachinePlaysWhite:
\r
12030 case BeginningOfGame:
\r
12031 if (pausing) return;
\r
12035 case PlayFromGameFile:
\r
12036 case IcsExamining:
\r
12043 tickStartTM = now;
\r
12044 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12045 whiteTimeRemaining : blackTimeRemaining);
\r
12046 StartClockTimer(intendedTickLength);
\r
12050 /* Stop both clocks */
\r
12054 long lastTickLength;
\r
12057 if (!StopClockTimer()) return;
\r
12058 if (!appData.clockMode) return;
\r
12060 GetTimeMark(&now);
\r
12062 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12063 if (WhiteOnMove(forwardMostMove)) {
\r
12064 whiteTimeRemaining -= lastTickLength;
\r
12065 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
12067 blackTimeRemaining -= lastTickLength;
\r
12068 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
12073 /* Start clock of player on move. Time may have been reset, so
\r
12074 if clock is already running, stop and restart it. */
\r
12078 (void) StopClockTimer(); /* in case it was running already */
\r
12079 DisplayBothClocks();
\r
12080 if (CheckFlags()) return;
\r
12082 if (!appData.clockMode) return;
\r
12083 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12085 GetTimeMark(&tickStartTM);
\r
12086 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12087 whiteTimeRemaining : blackTimeRemaining);
\r
12088 StartClockTimer(intendedTickLength);
\r
12095 long second, minute, hour, day;
\r
12097 static char buf[32];
\r
12099 if (ms > 0 && ms <= 9900) {
\r
12100 /* convert milliseconds to tenths, rounding up */
\r
12101 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
12103 sprintf(buf, " %03.1f ", tenths/10.0);
\r
12107 /* convert milliseconds to seconds, rounding up */
\r
12108 /* use floating point to avoid strangeness of integer division
\r
12109 with negative dividends on many machines */
\r
12110 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
12112 if (second < 0) {
\r
12114 second = -second;
\r
12117 day = second / (60 * 60 * 24);
\r
12118 second = second % (60 * 60 * 24);
\r
12119 hour = second / (60 * 60);
\r
12120 second = second % (60 * 60);
\r
12121 minute = second / 60;
\r
12122 second = second % 60;
\r
12125 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
12126 sign, day, hour, minute, second);
\r
12127 else if (hour > 0)
\r
12128 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
12130 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
12137 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
12140 StrStr(string, match)
\r
12141 char *string, *match;
\r
12145 length = strlen(match);
\r
12147 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
12148 if (!strncmp(match, string, length))
\r
12155 StrCaseStr(string, match)
\r
12156 char *string, *match;
\r
12158 int i, j, length;
\r
12160 length = strlen(match);
\r
12162 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
12163 for (j = 0; j < length; j++) {
\r
12164 if (ToLower(match[j]) != ToLower(string[j]))
\r
12167 if (j == length) return string;
\r
12173 #ifndef _amigados
\r
12175 StrCaseCmp(s1, s2)
\r
12181 c1 = ToLower(*s1++);
\r
12182 c2 = ToLower(*s2++);
\r
12183 if (c1 > c2) return 1;
\r
12184 if (c1 < c2) return -1;
\r
12185 if (c1 == NULLCHAR) return 0;
\r
12194 return isupper(c) ? tolower(c) : c;
\r
12202 return islower(c) ? toupper(c) : c;
\r
12204 #endif /* !_amigados */
\r
12212 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
12219 StrSavePtr(s, savePtr)
\r
12220 char *s, **savePtr;
\r
12225 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
12226 strcpy(*savePtr, s);
\r
12228 return(*savePtr);
\r
12236 char buf[MSG_SIZ];
\r
12238 clock = time((time_t *)NULL);
\r
12239 tm = localtime(&clock);
\r
12240 sprintf(buf, "%04d.%02d.%02d",
\r
12241 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
12242 return StrSave(buf);
\r
12247 PositionToFEN(move, useFEN960)
\r
12251 int i, j, fromX, fromY, toX, toY;
\r
12256 ChessSquare piece;
\r
12258 whiteToPlay = (gameMode == EditPosition) ?
\r
12259 !blackPlaysFirst : (move % 2 == 0);
\r
12262 /* Piece placement data */
\r
12263 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
12265 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
12266 if (boards[move][i][j] == EmptySquare) {
\r
12268 } else { ChessSquare piece = boards[move][i][j];
\r
12269 if (emptycount > 0) {
\r
12270 if(emptycount<10) /* [HGM] can be >= 10 */
\r
12271 *p++ = '0' + emptycount;
\r
12272 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
12275 if(PieceToChar(piece) == '+') {
\r
12276 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
12278 piece = (ChessSquare)(DEMOTED piece);
\r
12280 *p++ = PieceToChar(piece);
\r
12281 if(p[-1] == '~') {
\r
12282 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
12283 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
12288 if (emptycount > 0) {
\r
12289 if(emptycount<10) /* [HGM] can be >= 10 */
\r
12290 *p++ = '0' + emptycount;
\r
12291 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
12298 /* [HGM] print Crazyhouse or Shogi holdings */
\r
12299 if( gameInfo.holdingsWidth ) {
\r
12300 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
12302 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
12303 piece = boards[move][i][BOARD_WIDTH-1];
\r
12304 if( piece != EmptySquare )
\r
12305 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
12306 *p++ = PieceToChar(piece);
\r
12308 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
12309 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
12310 if( piece != EmptySquare )
\r
12311 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
12312 *p++ = PieceToChar(piece);
\r
12315 if( q == p ) *p++ = '-';
\r
12320 /* Active color */
\r
12321 *p++ = whiteToPlay ? 'w' : 'b';
\r
12324 if(nrCastlingRights) {
\r
12326 if(gameInfo.variant == VariantFischeRandom) {
\r
12327 /* [HGM] write directly from rights */
\r
12328 if(castlingRights[move][2] >= 0 &&
\r
12329 castlingRights[move][0] >= 0 )
\r
12330 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
12331 if(castlingRights[move][2] >= 0 &&
\r
12332 castlingRights[move][1] >= 0 )
\r
12333 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
12334 if(castlingRights[move][5] >= 0 &&
\r
12335 castlingRights[move][3] >= 0 )
\r
12336 *p++ = castlingRights[move][3] + AAA;
\r
12337 if(castlingRights[move][5] >= 0 &&
\r
12338 castlingRights[move][4] >= 0 )
\r
12339 *p++ = castlingRights[move][4] + AAA;
\r
12342 /* [HGM] write true castling rights */
\r
12343 if( nrCastlingRights == 6 ) {
\r
12344 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
12345 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
12346 if(castlingRights[move][1] == BOARD_LEFT &&
\r
12347 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
12348 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
12349 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
12350 if(castlingRights[move][4] == BOARD_LEFT &&
\r
12351 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
12354 if (q == p) *p++ = '-'; /* No castling rights */
\r
12358 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
12359 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
12360 /* En passant target square */
\r
12361 if (move > backwardMostMove) {
\r
12362 fromX = moveList[move - 1][0] - AAA;
\r
12363 fromY = moveList[move - 1][1] - ONE;
\r
12364 toX = moveList[move - 1][2] - AAA;
\r
12365 toY = moveList[move - 1][3] - ONE;
\r
12366 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
12367 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
12368 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
12370 /* 2-square pawn move just happened */
\r
12371 *p++ = toX + AAA;
\r
12372 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
12382 /* [HGM] find reversible plies */
\r
12383 { int i = 0, j=move;
\r
12385 if (appData.debugMode) { int k;
\r
12386 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
12387 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
12388 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
12392 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
12393 if( j == backwardMostMove ) i += initialRulePlies;
\r
12394 sprintf(p, "%d ", i);
\r
12395 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
12397 /* Fullmove number */
\r
12398 sprintf(p, "%d", (move / 2) + 1);
\r
12400 return StrSave(buf);
\r
12404 ParseFEN(board, blackPlaysFirst, fen)
\r
12406 int *blackPlaysFirst;
\r
12412 ChessSquare piece;
\r
12416 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
12417 if(gameInfo.holdingsWidth) {
\r
12418 for(i=0; i<BOARD_HEIGHT; i++) {
\r
12419 board[i][0] = EmptySquare; /* black holdings */
\r
12420 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
12421 board[i][1] = (ChessSquare) 0; /* black counts */
\r
12422 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
12426 /* Piece placement data */
\r
12427 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
12430 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
12431 if (*p == '/') p++;
\r
12432 emptycount = gameInfo.boardWidth - j;
\r
12433 while (emptycount--)
\r
12434 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12436 #if(BOARD_SIZE >= 10)
\r
12437 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
12438 p++; emptycount=10;
\r
12439 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12440 while (emptycount--)
\r
12441 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12443 } else if (isdigit(*p)) {
\r
12444 emptycount = *p++ - '0';
\r
12445 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
12446 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
12447 while (emptycount--)
\r
12448 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
12449 } else if (*p == '+' || isalpha(*p)) {
\r
12450 if (j >= gameInfo.boardWidth) return FALSE;
\r
12452 piece = CharToPiece(*++p);
\r
12453 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
12454 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
12455 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
12456 } else piece = CharToPiece(*p++);
\r
12458 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
12459 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
12460 piece = (ChessSquare) (PROMOTED piece);
\r
12461 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
12464 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
12470 while (*p == '/' || *p == ' ') p++;
\r
12472 /* [HGM] look for Crazyhouse holdings here */
\r
12473 while(*p==' ') p++;
\r
12474 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
12475 if(*p == '[') p++;
\r
12476 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
12477 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
12478 /* if we would allow FEN reading to set board size, we would */
\r
12479 /* have to add holdings and shift the board read so far here */
\r
12480 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
12482 if((int) piece >= (int) BlackPawn ) {
\r
12483 i = (int)piece - (int)BlackPawn;
\r
12484 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12485 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
12486 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
12488 i = (int)piece - (int)WhitePawn;
\r
12489 if( i >= BOARD_HEIGHT ) return FALSE;
\r
12490 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
12491 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
12495 if(*p == ']') *p++;
\r
12498 while(*p == ' ') p++;
\r
12500 /* Active color */
\r
12503 *blackPlaysFirst = FALSE;
\r
12506 *blackPlaysFirst = TRUE;
\r
12512 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
12513 /* return the extra info in global variiables */
\r
12515 /* set defaults in case FEN is incomplete */
\r
12516 FENepStatus = EP_UNKNOWN;
\r
12517 for(i=0; i<nrCastlingRights; i++ ) {
\r
12518 FENcastlingRights[i] =
\r
12519 gameInfo.variant == VariantFischeRandom ? -1 : initialRights[i];
\r
12520 } /* assume possible unless obviously impossible */
\r
12521 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
12522 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
12523 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
12524 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
12525 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
12526 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
12527 FENrulePlies = 0;
\r
12529 while(*p==' ') p++;
\r
12530 if(nrCastlingRights) {
\r
12531 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
12532 /* castling indicator present, so default becomes no castlings */
\r
12533 for(i=0; i<nrCastlingRights; i++ ) {
\r
12534 FENcastlingRights[i] = -1;
\r
12537 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
12538 gameInfo.variant == VariantFischeRandom &&
\r
12539 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
12540 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
12541 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
12543 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
12544 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
12545 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
12549 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
12550 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
12551 FENcastlingRights[2] = whiteKingFile;
\r
12554 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
12555 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
12556 FENcastlingRights[2] = whiteKingFile;
\r
12559 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
12560 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
12561 FENcastlingRights[5] = blackKingFile;
\r
12564 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
12565 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
12566 FENcastlingRights[5] = blackKingFile;
\r
12569 default: /* FRC castlings */
\r
12570 if(c >= 'a') { /* black rights */
\r
12571 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
12572 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
12573 if(i == BOARD_RGHT) break;
\r
12574 FENcastlingRights[5] = i;
\r
12576 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
12577 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
12579 FENcastlingRights[3] = c;
\r
12581 FENcastlingRights[4] = c;
\r
12582 } else { /* white rights */
\r
12583 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
12584 if(board[0][i] == WhiteKing) break;
\r
12585 if(i == BOARD_RGHT) break;
\r
12586 FENcastlingRights[2] = i;
\r
12587 c -= AAA - 'a' + 'A';
\r
12588 if(board[0][c] >= WhiteKing) break;
\r
12590 FENcastlingRights[0] = c;
\r
12592 FENcastlingRights[1] = c;
\r
12596 if (appData.debugMode) {
\r
12597 fprintf(debugFP, "FEN castling rights:");
\r
12598 for(i=0; i<nrCastlingRights; i++)
\r
12599 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
12600 fprintf(debugFP, "\n");
\r
12603 while(*p==' ') p++;
\r
12606 /* read e.p. field in games that know e.p. capture */
\r
12607 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
12608 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
12610 p++; FENepStatus = EP_NONE;
\r
12612 char c = *p++ - AAA;
\r
12614 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
12615 if(*p >= '0' && *p <='9') *p++;
\r
12621 if(sscanf(p, "%d", &i) == 1) {
\r
12622 FENrulePlies = i; /* 50-move ply counter */
\r
12623 /* (The move number is still ignored) */
\r
12630 EditPositionPasteFEN(char *fen)
\r
12632 if (fen != NULL) {
\r
12633 Board initial_position;
\r
12635 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
12636 DisplayError("Bad FEN position in clipboard", 0);
\r
12639 int savedBlackPlaysFirst = blackPlaysFirst;
\r
12640 EditPositionEvent();
\r
12641 blackPlaysFirst = savedBlackPlaysFirst;
\r
12642 CopyBoard(boards[0], initial_position);
\r
12643 /* [HGM] copy FEN attributes as well */
\r
12645 initialRulePlies = FENrulePlies;
\r
12646 epStatus[0] = FENepStatus;
\r
12647 for( i=0; i<nrCastlingRights; i++ )
\r
12648 castlingRights[0][i] = FENcastlingRights[i];
\r
12650 EditPositionDone();
\r
12651 DisplayBothClocks();
\r
12652 DrawPosition(FALSE, boards[currentMove]);
\r