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 int 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
218 ChessProgramState *WhitePlayer();
\r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
221 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
222 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
223 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
224 extern char installDir[MSG_SIZ];
\r
226 extern int tinyLayout, smallLayout;
\r
227 static ChessProgramStats programStats;
\r
228 static int exiting = 0; /* [HGM] moved to top */
\r
229 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;
\r
230 extern int startedFromPositionFile;
\r
231 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
232 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
233 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
234 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
235 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
237 /* States for ics_getting_history */
\r
239 #define H_REQUESTED 1
\r
240 #define H_GOT_REQ_HEADER 2
\r
241 #define H_GOT_UNREQ_HEADER 3
\r
242 #define H_GETTING_MOVES 4
\r
243 #define H_GOT_UNWANTED_HEADER 5
\r
245 /* whosays values for GameEnds */
\r
247 #define GE_ENGINE 1
\r
248 #define GE_PLAYER 2
\r
250 #define GE_XBOARD 4
\r
251 #define GE_ENGINE1 5
\r
252 #define GE_ENGINE2 6
\r
254 /* Maximum number of games in a cmail message */
\r
255 #define CMAIL_MAX_GAMES 20
\r
257 /* Different types of move when calling RegisterMove */
\r
258 #define CMAIL_MOVE 0
\r
259 #define CMAIL_RESIGN 1
\r
260 #define CMAIL_DRAW 2
\r
261 #define CMAIL_ACCEPT 3
\r
263 /* Different types of result to remember for each game */
\r
264 #define CMAIL_NOT_RESULT 0
\r
265 #define CMAIL_OLD_RESULT 1
\r
266 #define CMAIL_NEW_RESULT 2
\r
268 /* Telnet protocol constants */
\r
269 #define TN_WILL 0373
\r
270 #define TN_WONT 0374
\r
272 #define TN_DONT 0376
\r
273 #define TN_IAC 0377
\r
274 #define TN_ECHO 0001
\r
275 #define TN_SGA 0003
\r
279 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
281 assert( dst != NULL );
\r
282 assert( src != NULL );
\r
283 assert( count > 0 );
\r
285 strncpy( dst, src, count );
\r
286 dst[ count-1 ] = '\0';
\r
290 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
294 assert( dst != NULL );
\r
295 assert( src != NULL );
\r
296 assert( count > 0 );
\r
298 dst_len = strlen(dst);
\r
300 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
302 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
307 /* Fake up flags for now, as we aren't keeping track of castling
\r
308 availability yet. [HGM] Change of logic: the flag now only
\r
309 indicates the type of castlings allowed by the rule of the game.
\r
310 The actual rights themselves are maintained in the array
\r
311 castlingRights, as part of the game history, and are not probed
\r
317 int flags = F_ALL_CASTLE_OK;
\r
318 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
319 switch (gameInfo.variant) {
\r
320 case VariantSuicide:
\r
321 case VariantGiveaway:
\r
322 flags |= F_IGNORE_CHECK;
\r
323 flags &= ~F_ALL_CASTLE_OK;
\r
325 case VariantAtomic:
\r
326 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
328 case VariantKriegspiel:
\r
329 flags |= F_KRIEGSPIEL_CAPTURE;
\r
331 case VariantCapaRandom:
\r
332 case VariantFischeRandom:
\r
333 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
334 case VariantNoCastle:
\r
335 case VariantShatranj:
\r
336 case VariantCourier:
\r
337 flags &= ~F_ALL_CASTLE_OK;
\r
345 FILE *gameFileFP, *debugFP;
\r
348 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
349 into a fixed-size buffer. Because of this, we must be prepared to
\r
350 receive strings as long as the size of the input buffer, which is currently
\r
351 set to 4K for Windows and 8K for the rest.
\r
352 So, we must either allocate sufficiently large buffers here, or
\r
353 reduce the size of the input buffer in the input reading part.
\r
356 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
357 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
358 char thinkOutput1[MSG_SIZ*10];
\r
360 ChessProgramState first, second;
\r
362 /* premove variables */
\r
363 int premoveToX = 0;
\r
364 int premoveToY = 0;
\r
365 int premoveFromX = 0;
\r
366 int premoveFromY = 0;
\r
367 int premovePromoChar = 0;
\r
368 int gotPremove = 0;
\r
369 Boolean alarmSounded;
\r
370 /* end premove variables */
\r
372 #define ICS_GENERIC 0
\r
375 #define ICS_CHESSNET 3 /* not really supported */
\r
376 int ics_type = ICS_GENERIC;
\r
377 char *ics_prefix = "$";
\r
379 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
380 int pauseExamForwardMostMove = 0;
\r
381 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
382 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
383 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
384 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
385 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
386 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
387 int whiteFlag = FALSE, blackFlag = FALSE;
\r
388 int userOfferedDraw = FALSE;
\r
389 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
390 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
391 int cmailMoveType[CMAIL_MAX_GAMES];
\r
392 long ics_clock_paused = 0;
\r
393 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
394 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
395 GameMode gameMode = BeginningOfGame;
\r
396 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
397 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
398 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
399 int hiddenThinkOutputState = 0; /* [AS] */
\r
400 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
401 int adjudicateLossPlies = 6;
\r
402 char white_holding[64], black_holding[64];
\r
403 TimeMark lastNodeCountTime;
\r
404 long lastNodeCount=0;
\r
405 int have_sent_ICS_logon = 0;
\r
406 int movesPerSession;
\r
407 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
408 long timeControl_2; /* [AS] Allow separate time controls */
\r
409 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
410 long timeRemaining[2][MAX_MOVES];
\r
412 TimeMark programStartTime;
\r
413 char ics_handle[MSG_SIZ];
\r
414 int have_set_title = 0;
\r
416 /* animateTraining preserves the state of appData.animate
\r
417 * when Training mode is activated. This allows the
\r
418 * response to be animated when appData.animate == TRUE and
\r
419 * appData.animateDragging == TRUE.
\r
421 Boolean animateTraining;
\r
427 Board boards[MAX_MOVES];
\r
428 /* [HGM] Following 7 needed for accurate legality tests: */
\r
429 char epStatus[MAX_MOVES];
\r
430 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
431 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
432 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
433 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
434 int initialRulePlies, FENrulePlies;
\r
436 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
438 int shuffleOpenings;
\r
440 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
441 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
442 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
443 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
444 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
447 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
448 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
449 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
450 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
451 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
454 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
455 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
456 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
457 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
458 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
461 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
462 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
463 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
464 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
465 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
468 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
469 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
470 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
472 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
476 #if (BOARD_SIZE>=10)
\r
477 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
478 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
479 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
480 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
481 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
484 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
486 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
487 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
488 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
491 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
492 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
493 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
494 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
495 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
498 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
499 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
500 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
501 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
502 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
506 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
508 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
510 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
513 #define GothicArray CapablancaArray
\r
517 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
519 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
520 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
521 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
524 #define FalconArray CapablancaArray
\r
527 #else // !(BOARD_SIZE>=10)
\r
528 #define XiangqiPosition FIDEArray
\r
529 #define CapablancaArray FIDEArray
\r
530 #define GothicArray FIDEArray
\r
531 #endif // !(BOARD_SIZE>=10)
\r
533 #if (BOARD_SIZE>=12)
\r
534 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
535 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
536 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
537 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
538 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
540 #else // !(BOARD_SIZE>=12)
\r
541 #define CourierArray CapablancaArray
\r
542 #endif // !(BOARD_SIZE>=12)
\r
545 Board initialPosition;
\r
548 /* Convert str to a rating. Checks for special cases of "----",
\r
550 "++++", etc. Also strips ()'s */
\r
552 string_to_rating(str)
\r
555 while(*str && !isdigit(*str)) ++str;
\r
557 return 0; /* One of the special "no rating" cases */
\r
563 ClearProgramStats()
\r
565 /* Init programStats */
\r
566 programStats.movelist[0] = 0;
\r
567 programStats.depth = 0;
\r
568 programStats.nr_moves = 0;
\r
569 programStats.moves_left = 0;
\r
570 programStats.nodes = 0;
\r
571 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
572 programStats.score = 0;
\r
573 programStats.got_only_move = 0;
\r
574 programStats.got_fail = 0;
\r
575 programStats.line_is_book = 0;
\r
581 int matched, min, sec;
\r
583 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
585 GetTimeMark(&programStartTime);
\r
587 ClearProgramStats();
\r
588 programStats.ok_to_send = 1;
\r
589 programStats.seen_stat = 0;
\r
592 * Initialize game list
\r
594 ListNew(&gameList);
\r
598 * Internet chess server status
\r
600 if (appData.icsActive) {
\r
601 appData.matchMode = FALSE;
\r
602 appData.matchGames = 0;
\r
604 appData.noChessProgram = !appData.zippyPlay;
\r
606 appData.zippyPlay = FALSE;
\r
607 appData.zippyTalk = FALSE;
\r
608 appData.noChessProgram = TRUE;
\r
610 if (*appData.icsHelper != NULLCHAR) {
\r
611 appData.useTelnet = TRUE;
\r
612 appData.telnetProgram = appData.icsHelper;
\r
615 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
618 /* [AS] Initialize pv info list [HGM] and game state */
\r
622 for( i=0; i<MAX_MOVES; i++ ) {
\r
623 pvInfoList[i].depth = -1;
\r
624 epStatus[i]=EP_NONE;
\r
625 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
630 * Parse timeControl resource
\r
632 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
633 appData.movesPerSession)) {
\r
635 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
636 DisplayFatalError(buf, 0, 2);
\r
640 * Parse searchTime resource
\r
642 if (*appData.searchTime != NULLCHAR) {
\r
643 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
644 if (matched == 1) {
\r
645 searchTime = min * 60;
\r
646 } else if (matched == 2) {
\r
647 searchTime = min * 60 + sec;
\r
650 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
651 DisplayFatalError(buf, 0, 2);
\r
655 /* [AS] Adjudication threshold */
\r
656 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
658 first.which = "first";
\r
659 second.which = "second";
\r
660 first.maybeThinking = second.maybeThinking = FALSE;
\r
661 first.pr = second.pr = NoProc;
\r
662 first.isr = second.isr = NULL;
\r
663 first.sendTime = second.sendTime = 2;
\r
664 first.sendDrawOffers = 1;
\r
665 if (appData.firstPlaysBlack) {
\r
666 first.twoMachinesColor = "black\n";
\r
667 second.twoMachinesColor = "white\n";
\r
669 first.twoMachinesColor = "white\n";
\r
670 second.twoMachinesColor = "black\n";
\r
672 first.program = appData.firstChessProgram;
\r
673 second.program = appData.secondChessProgram;
\r
674 first.host = appData.firstHost;
\r
675 second.host = appData.secondHost;
\r
676 first.dir = appData.firstDirectory;
\r
677 second.dir = appData.secondDirectory;
\r
678 first.other = &second;
\r
679 second.other = &first;
\r
680 first.initString = appData.initString;
\r
681 second.initString = appData.secondInitString;
\r
682 first.computerString = appData.firstComputerString;
\r
683 second.computerString = appData.secondComputerString;
\r
684 first.useSigint = second.useSigint = TRUE;
\r
685 first.useSigterm = second.useSigterm = TRUE;
\r
686 first.reuse = appData.reuseFirst;
\r
687 second.reuse = appData.reuseSecond;
\r
688 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
689 second.nps = appData.secondNPS;
\r
690 first.useSetboard = second.useSetboard = FALSE;
\r
691 first.useSAN = second.useSAN = FALSE;
\r
692 first.usePing = second.usePing = FALSE;
\r
693 first.lastPing = second.lastPing = 0;
\r
694 first.lastPong = second.lastPong = 0;
\r
695 first.usePlayother = second.usePlayother = FALSE;
\r
696 first.useColors = second.useColors = TRUE;
\r
697 first.useUsermove = second.useUsermove = FALSE;
\r
698 first.sendICS = second.sendICS = FALSE;
\r
699 first.sendName = second.sendName = appData.icsActive;
\r
700 first.sdKludge = second.sdKludge = FALSE;
\r
701 first.stKludge = second.stKludge = FALSE;
\r
702 TidyProgramName(first.program, first.host, first.tidy);
\r
703 TidyProgramName(second.program, second.host, second.tidy);
\r
704 first.matchWins = second.matchWins = 0;
\r
705 strcpy(first.variants, appData.variant);
\r
706 strcpy(second.variants, appData.variant);
\r
707 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
708 first.analyzing = second.analyzing = FALSE;
\r
709 first.initDone = second.initDone = FALSE;
\r
711 /* New features added by Tord: */
\r
712 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
713 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
714 /* End of new features added by Tord. */
\r
716 /* [HGM] time odds: set factor for each machine */
\r
717 first.timeOdds = appData.firstTimeOdds;
\r
718 second.timeOdds = appData.secondTimeOdds;
\r
720 if(appData.timeOddsMode) {
\r
721 norm = first.timeOdds;
\r
722 if(norm > second.timeOdds) norm = second.timeOdds;
\r
724 first.timeOdds /= norm;
\r
725 second.timeOdds /= norm;
\r
728 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
729 first.accumulateTC = appData.firstAccumulateTC;
\r
730 second.accumulateTC = appData.secondAccumulateTC;
\r
731 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
734 first.debug = second.debug = FALSE;
\r
735 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
737 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
738 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
739 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
740 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
741 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
742 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
744 if (appData.firstProtocolVersion > PROTOVER ||
\r
745 appData.firstProtocolVersion < 1) {
\r
747 sprintf(buf, "protocol version %d not supported",
\r
748 appData.firstProtocolVersion);
\r
749 DisplayFatalError(buf, 0, 2);
\r
751 first.protocolVersion = appData.firstProtocolVersion;
\r
754 if (appData.secondProtocolVersion > PROTOVER ||
\r
755 appData.secondProtocolVersion < 1) {
\r
757 sprintf(buf, "protocol version %d not supported",
\r
758 appData.secondProtocolVersion);
\r
759 DisplayFatalError(buf, 0, 2);
\r
761 second.protocolVersion = appData.secondProtocolVersion;
\r
764 if (appData.icsActive) {
\r
765 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
766 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
767 appData.clockMode = FALSE;
\r
768 first.sendTime = second.sendTime = 0;
\r
772 /* Override some settings from environment variables, for backward
\r
773 compatibility. Unfortunately it's not feasible to have the env
\r
774 vars just set defaults, at least in xboard. Ugh.
\r
776 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
781 if (appData.noChessProgram) {
\r
782 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
783 + strlen(PATCHLEVEL));
\r
784 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
789 while (*q != ' ' && *q != NULLCHAR) q++;
\r
791 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
792 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
793 + strlen(PATCHLEVEL) + (q - p));
\r
794 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
795 strncat(programVersion, p, q - p);
\r
797 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
798 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
799 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
800 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
804 if (!appData.icsActive) {
\r
806 /* Check for variants that are supported only in ICS mode,
\r
807 or not at all. Some that are accepted here nevertheless
\r
808 have bugs; see comments below.
\r
810 VariantClass variant = StringToVariant(appData.variant);
\r
812 case VariantBughouse: /* need four players and two boards */
\r
813 case VariantKriegspiel: /* need to hide pieces and move details */
\r
814 /* case VariantFischeRandom: (Fabien: moved below) */
\r
815 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
816 DisplayFatalError(buf, 0, 2);
\r
819 case VariantUnknown:
\r
820 case VariantLoadable:
\r
830 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
831 DisplayFatalError(buf, 0, 2);
\r
834 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
835 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
836 case VariantGothic: /* [HGM] should work */
\r
837 case VariantCapablanca: /* [HGM] should work */
\r
838 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
839 case VariantShogi: /* [HGM] drops not tested for legality */
\r
840 case VariantKnightmate: /* [HGM] should work */
\r
841 case VariantCylinder: /* [HGM] untested */
\r
842 case VariantFalcon: /* [HGM] untested */
\r
843 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
844 offboard interposition not understood */
\r
845 case VariantNormal: /* definitely works! */
\r
846 case VariantWildCastle: /* pieces not automatically shuffled */
\r
847 case VariantNoCastle: /* pieces not automatically shuffled */
\r
848 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
849 case VariantLosers: /* should work except for win condition,
\r
850 and doesn't know captures are mandatory */
\r
851 case VariantSuicide: /* should work except for win condition,
\r
852 and doesn't know captures are mandatory */
\r
853 case VariantGiveaway: /* should work except for win condition,
\r
854 and doesn't know captures are mandatory */
\r
855 case VariantTwoKings: /* should work */
\r
856 case VariantAtomic: /* should work except for win condition */
\r
857 case Variant3Check: /* should work except for win condition */
\r
858 case VariantShatranj: /* should work except for all win conditions */
\r
859 case VariantBerolina: /* might work if TestLegality is off */
\r
860 case VariantCapaRandom: /* should work */
\r
861 case VariantJanus: /* should work */
\r
862 case VariantSuper: /* experimental */
\r
867 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
868 InitEngineUCI( installDir, &second );
\r
871 int NextIntegerFromString( char ** str, long * value )
\r
876 while( *s == ' ' || *s == '\t' ) {
\r
882 if( *s >= '0' && *s <= '9' ) {
\r
883 while( *s >= '0' && *s <= '9' ) {
\r
884 *value = *value * 10 + (*s - '0');
\r
896 int NextTimeControlFromString( char ** str, long * value )
\r
899 int result = NextIntegerFromString( str, &temp );
\r
901 if( result == 0 ) {
\r
902 *value = temp * 60; /* Minutes */
\r
903 if( **str == ':' ) {
\r
905 result = NextIntegerFromString( str, &temp );
\r
906 *value += temp; /* Seconds */
\r
913 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
914 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
915 int result = -1; long temp, temp2;
\r
917 if(**str != '+') return -1; // old params remain in force!
\r
919 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
922 /* time only: incremental or sudden-death time control */
\r
923 if(**str == '+') { /* increment follows; read it */
\r
925 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
926 *inc = temp2 * 1000;
\r
928 *moves = 0; *tc = temp * 1000;
\r
930 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
932 (*str)++; /* classical time control */
\r
933 result = NextTimeControlFromString( str, &temp2);
\r
936 *tc = temp2 * 1000;
\r
942 int GetTimeQuota(int movenr)
\r
943 { /* [HGM] get time to add from the multi-session time-control string */
\r
944 int moves=1; /* kludge to force reading of first session */
\r
945 long time, increment;
\r
946 char *s = fullTimeControlString;
\r
948 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
950 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
951 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
952 if(movenr == -1) return time; /* last move before new session */
\r
953 if(!moves) return increment; /* current session is incremental */
\r
954 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
955 } while(movenr >= -1); /* try again for next session */
\r
957 return 0; // no new time quota on this move
\r
961 ParseTimeControl(tc, ti, mps)
\r
967 int matched, min, sec;
\r
969 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
970 if (matched == 1) {
\r
971 timeControl = min * 60 * 1000;
\r
972 } else if (matched == 2) {
\r
973 timeControl = (min * 60 + sec) * 1000;
\r
982 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
985 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
986 else sprintf(buf, "+%s+%d", tc, ti);
\r
989 sprintf(buf, "+%d/%s", mps, tc);
\r
990 else sprintf(buf, "+%s", tc);
\r
992 fullTimeControlString = StrSave(buf);
\r
994 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
999 /* Parse second time control */
\r
1002 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1010 timeControl_2 = tc2 * 1000;
\r
1013 timeControl_2 = 0;
\r
1020 timeControl = tc1 * 1000;
\r
1024 timeIncrement = ti * 1000; /* convert to ms */
\r
1025 movesPerSession = 0;
\r
1027 timeIncrement = 0;
\r
1028 movesPerSession = mps;
\r
1036 if (appData.debugMode) {
\r
1037 fprintf(debugFP, "%s\n", programVersion);
\r
1040 if (appData.matchGames > 0) {
\r
1041 appData.matchMode = TRUE;
\r
1042 } else if (appData.matchMode) {
\r
1043 appData.matchGames = 1;
\r
1045 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1046 appData.matchGames = appData.sameColorGames;
\r
1047 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1048 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1049 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1051 Reset(TRUE, FALSE);
\r
1052 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1055 /* kludge: allow timeout for initial "feature" commands */
\r
1057 DisplayMessage("", "Starting chess program");
\r
1058 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1063 InitBackEnd3 P((void))
\r
1065 GameMode initialMode;
\r
1066 char buf[MSG_SIZ];
\r
1069 InitChessProgram(&first, startedFromSetupPosition);
\r
1071 if (appData.icsActive) {
\r
1072 err = establish();
\r
1074 if (*appData.icsCommPort != NULLCHAR) {
\r
1075 sprintf(buf, "Could not open comm port %s",
\r
1076 appData.icsCommPort);
\r
1078 sprintf(buf, "Could not connect to host %s, port %s",
\r
1079 appData.icsHost, appData.icsPort);
\r
1081 DisplayFatalError(buf, err, 1);
\r
1086 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1088 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1089 } else if (appData.noChessProgram) {
\r
1095 if (*appData.cmailGameName != NULLCHAR) {
\r
1097 OpenLoopback(&cmailPR);
\r
1099 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1103 DisplayMessage("", "");
\r
1104 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1105 initialMode = BeginningOfGame;
\r
1106 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1107 initialMode = TwoMachinesPlay;
\r
1108 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1109 initialMode = AnalyzeFile;
\r
1110 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1111 initialMode = AnalyzeMode;
\r
1112 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1113 initialMode = MachinePlaysWhite;
\r
1114 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1115 initialMode = MachinePlaysBlack;
\r
1116 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1117 initialMode = EditGame;
\r
1118 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1119 initialMode = EditPosition;
\r
1120 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1121 initialMode = Training;
\r
1123 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
1124 DisplayFatalError(buf, 0, 2);
\r
1128 if (appData.matchMode) {
\r
1129 /* Set up machine vs. machine match */
\r
1130 if (appData.noChessProgram) {
\r
1131 DisplayFatalError("Can't have a match with no chess programs",
\r
1137 if (*appData.loadGameFile != NULLCHAR) {
\r
1138 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1139 if(index<0) lastIndex = index = 1;
\r
1140 if (!LoadGameFromFile(appData.loadGameFile,
\r
1142 appData.loadGameFile, FALSE)) {
\r
1143 DisplayFatalError("Bad game file", 0, 1);
\r
1146 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1147 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1148 if(index<0) lastIndex = index = 1;
\r
1149 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1151 appData.loadPositionFile)) {
\r
1152 DisplayFatalError("Bad position file", 0, 1);
\r
1156 TwoMachinesEvent();
\r
1157 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1158 /* Set up cmail mode */
\r
1159 ReloadCmailMsgEvent(TRUE);
\r
1161 /* Set up other modes */
\r
1162 if (initialMode == AnalyzeFile) {
\r
1163 if (*appData.loadGameFile == NULLCHAR) {
\r
1164 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1168 if (*appData.loadGameFile != NULLCHAR) {
\r
1169 (void) LoadGameFromFile(appData.loadGameFile,
\r
1170 appData.loadGameIndex,
\r
1171 appData.loadGameFile, TRUE);
\r
1172 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1173 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1174 appData.loadPositionIndex,
\r
1175 appData.loadPositionFile);
\r
1176 /* [HGM] try to make self-starting even after FEN load */
\r
1177 /* to allow automatic setup of fairy variants with wtm */
\r
1178 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1179 gameMode = BeginningOfGame;
\r
1180 setboardSpoiledMachineBlack = 1;
\r
1182 /* [HGM] loadPos: make that every new game uses the setup */
\r
1183 /* from file as long as we do not switch variant */
\r
1184 if(!blackPlaysFirst) { int i;
\r
1185 startedFromPositionFile = TRUE;
\r
1186 CopyBoard(filePosition, boards[0]);
\r
1187 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1190 if (initialMode == AnalyzeMode) {
\r
1191 if (appData.noChessProgram) {
\r
1192 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1195 if (appData.icsActive) {
\r
1196 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1199 AnalyzeModeEvent();
\r
1200 } else if (initialMode == AnalyzeFile) {
\r
1201 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1202 ShowThinkingEvent();
\r
1203 AnalyzeFileEvent();
\r
1204 AnalysisPeriodicEvent(1);
\r
1205 } else if (initialMode == MachinePlaysWhite) {
\r
1206 if (appData.noChessProgram) {
\r
1207 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1211 if (appData.icsActive) {
\r
1212 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1216 MachineWhiteEvent();
\r
1217 } else if (initialMode == MachinePlaysBlack) {
\r
1218 if (appData.noChessProgram) {
\r
1219 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1223 if (appData.icsActive) {
\r
1224 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1228 MachineBlackEvent();
\r
1229 } else if (initialMode == TwoMachinesPlay) {
\r
1230 if (appData.noChessProgram) {
\r
1231 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1235 if (appData.icsActive) {
\r
1236 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1240 TwoMachinesEvent();
\r
1241 } else if (initialMode == EditGame) {
\r
1243 } else if (initialMode == EditPosition) {
\r
1244 EditPositionEvent();
\r
1245 } else if (initialMode == Training) {
\r
1246 if (*appData.loadGameFile == NULLCHAR) {
\r
1247 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1256 * Establish will establish a contact to a remote host.port.
\r
1257 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1258 * used to talk to the host.
\r
1259 * Returns 0 if okay, error code if not.
\r
1264 char buf[MSG_SIZ];
\r
1266 if (*appData.icsCommPort != NULLCHAR) {
\r
1267 /* Talk to the host through a serial comm port */
\r
1268 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1270 } else if (*appData.gateway != NULLCHAR) {
\r
1271 if (*appData.remoteShell == NULLCHAR) {
\r
1272 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1273 sprintf(buf, "%s %s %s",
\r
1274 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1275 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1278 /* Use the rsh program to run telnet program on a gateway host */
\r
1279 if (*appData.remoteUser == NULLCHAR) {
\r
1280 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1281 appData.gateway, appData.telnetProgram,
\r
1282 appData.icsHost, appData.icsPort);
\r
1284 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1285 appData.remoteShell, appData.gateway,
\r
1286 appData.remoteUser, appData.telnetProgram,
\r
1287 appData.icsHost, appData.icsPort);
\r
1289 return StartChildProcess(buf, "", &icsPR);
\r
1292 } else if (appData.useTelnet) {
\r
1293 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1296 /* TCP socket interface differs somewhat between
\r
1297 Unix and NT; handle details in the front end.
\r
1299 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1304 show_bytes(fp, buf, count)
\r
1310 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1311 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1320 /* Returns an errno value */
\r
1322 OutputMaybeTelnet(pr, message, count, outError)
\r
1328 char buf[8192], *p, *q, *buflim;
\r
1329 int left, newcount, outcount;
\r
1331 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1332 *appData.gateway != NULLCHAR) {
\r
1333 if (appData.debugMode) {
\r
1334 fprintf(debugFP, ">ICS: ");
\r
1335 show_bytes(debugFP, message, count);
\r
1336 fprintf(debugFP, "\n");
\r
1338 return OutputToProcess(pr, message, count, outError);
\r
1341 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1347 if (q >= buflim) {
\r
1348 if (appData.debugMode) {
\r
1349 fprintf(debugFP, ">ICS: ");
\r
1350 show_bytes(debugFP, buf, newcount);
\r
1351 fprintf(debugFP, "\n");
\r
1353 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1354 if (outcount < newcount) return -1; /* to be sure */
\r
1361 } else if (((unsigned char) *p) == TN_IAC) {
\r
1362 *q++ = (char) TN_IAC;
\r
1369 if (appData.debugMode) {
\r
1370 fprintf(debugFP, ">ICS: ");
\r
1371 show_bytes(debugFP, buf, newcount);
\r
1372 fprintf(debugFP, "\n");
\r
1374 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1375 if (outcount < newcount) return -1; /* to be sure */
\r
1380 read_from_player(isr, closure, message, count, error)
\r
1381 InputSourceRef isr;
\r
1387 int outError, outCount;
\r
1388 static int gotEof = 0;
\r
1390 /* Pass data read from player on to ICS */
\r
1393 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1394 if (outCount < count) {
\r
1395 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1397 } else if (count < 0) {
\r
1398 RemoveInputSource(isr);
\r
1399 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1400 } else if (gotEof++ > 0) {
\r
1401 RemoveInputSource(isr);
\r
1402 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1410 int count, outCount, outError;
\r
1412 if (icsPR == NULL) return;
\r
1414 count = strlen(s);
\r
1415 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1416 if (outCount < count) {
\r
1417 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1421 /* This is used for sending logon scripts to the ICS. Sending
\r
1422 without a delay causes problems when using timestamp on ICC
\r
1423 (at least on my machine). */
\r
1425 SendToICSDelayed(s,msdelay)
\r
1429 int count, outCount, outError;
\r
1431 if (icsPR == NULL) return;
\r
1433 count = strlen(s);
\r
1434 if (appData.debugMode) {
\r
1435 fprintf(debugFP, ">ICS: ");
\r
1436 show_bytes(debugFP, s, count);
\r
1437 fprintf(debugFP, "\n");
\r
1439 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1441 if (outCount < count) {
\r
1442 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1447 /* Remove all highlighting escape sequences in s
\r
1448 Also deletes any suffix starting with '('
\r
1451 StripHighlightAndTitle(s)
\r
1454 static char retbuf[MSG_SIZ];
\r
1457 while (*s != NULLCHAR) {
\r
1458 while (*s == '\033') {
\r
1459 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1460 if (*s != NULLCHAR) s++;
\r
1462 while (*s != NULLCHAR && *s != '\033') {
\r
1463 if (*s == '(' || *s == '[') {
\r
1474 /* Remove all highlighting escape sequences in s */
\r
1479 static char retbuf[MSG_SIZ];
\r
1482 while (*s != NULLCHAR) {
\r
1483 while (*s == '\033') {
\r
1484 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1485 if (*s != NULLCHAR) s++;
\r
1487 while (*s != NULLCHAR && *s != '\033') {
\r
1495 char *variantNames[] = VARIANT_NAMES;
\r
1500 return variantNames[v];
\r
1504 /* Identify a variant from the strings the chess servers use or the
\r
1505 PGN Variant tag names we use. */
\r
1507 StringToVariant(e)
\r
1512 VariantClass v = VariantNormal;
\r
1513 int i, found = FALSE;
\r
1514 char buf[MSG_SIZ];
\r
1518 /* [HGM] skip over optional board-size prefixes */
\r
1519 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1520 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1521 while( *e++ != '_');
\r
1524 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1525 if (StrCaseStr(e, variantNames[i])) {
\r
1526 v = (VariantClass) i;
\r
1533 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1534 || StrCaseStr(e, "wild/fr")) {
\r
1535 v = VariantFischeRandom;
\r
1536 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1537 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1539 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1540 if (isdigit(*p)) {
\r
1546 case 0: /* FICS only, actually */
\r
1548 /* Castling legal even if K starts on d-file */
\r
1549 v = VariantWildCastle;
\r
1554 /* Castling illegal even if K & R happen to start in
\r
1555 normal positions. */
\r
1556 v = VariantNoCastle;
\r
1569 /* Castling legal iff K & R start in normal positions */
\r
1570 v = VariantNormal;
\r
1575 /* Special wilds for position setup; unclear what to do here */
\r
1576 v = VariantLoadable;
\r
1579 /* Bizarre ICC game */
\r
1580 v = VariantTwoKings;
\r
1583 v = VariantKriegspiel;
\r
1586 v = VariantLosers;
\r
1589 v = VariantFischeRandom;
\r
1592 v = VariantCrazyhouse;
\r
1595 v = VariantBughouse;
\r
1598 v = Variant3Check;
\r
1601 /* Not quite the same as FICS suicide! */
\r
1602 v = VariantGiveaway;
\r
1605 v = VariantAtomic;
\r
1608 v = VariantShatranj;
\r
1611 /* Temporary names for future ICC types. The name *will* change in
\r
1612 the next xboard/WinBoard release after ICC defines it. */
\r
1641 v = VariantXiangqi;
\r
1644 v = VariantCourier;
\r
1647 v = VariantGothic;
\r
1650 v = VariantCapablanca;
\r
1653 v = VariantKnightmate;
\r
1659 v = VariantCylinder;
\r
1662 v = VariantFalcon;
\r
1665 v = VariantCapaRandom;
\r
1668 v = VariantBerolina;
\r
1677 /* Found "wild" or "w" in the string but no number;
\r
1678 must assume it's normal chess. */
\r
1679 v = VariantNormal;
\r
1682 sprintf(buf, "Unknown wild type %d", wnum);
\r
1683 DisplayError(buf, 0);
\r
1684 v = VariantUnknown;
\r
1689 if (appData.debugMode) {
\r
1690 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1691 e, wnum, VariantName(v));
\r
1696 static int leftover_start = 0, leftover_len = 0;
\r
1697 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1699 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1700 advance *index beyond it, and set leftover_start to the new value of
\r
1701 *index; else return FALSE. If pattern contains the character '*', it
\r
1702 matches any sequence of characters not containing '\r', '\n', or the
\r
1703 character following the '*' (if any), and the matched sequence(s) are
\r
1704 copied into star_match.
\r
1707 looking_at(buf, index, pattern)
\r
1712 char *bufp = &buf[*index], *patternp = pattern;
\r
1713 int star_count = 0;
\r
1714 char *matchp = star_match[0];
\r
1717 if (*patternp == NULLCHAR) {
\r
1718 *index = leftover_start = bufp - buf;
\r
1719 *matchp = NULLCHAR;
\r
1722 if (*bufp == NULLCHAR) return FALSE;
\r
1723 if (*patternp == '*') {
\r
1724 if (*bufp == *(patternp + 1)) {
\r
1725 *matchp = NULLCHAR;
\r
1726 matchp = star_match[++star_count];
\r
1730 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1732 if (*patternp == NULLCHAR)
\r
1737 *matchp++ = *bufp++;
\r
1741 if (*patternp != *bufp) return FALSE;
\r
1748 SendToPlayer(data, length)
\r
1752 int error, outCount;
\r
1753 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1754 if (outCount < length) {
\r
1755 DisplayFatalError("Error writing to display", error, 1);
\r
1760 PackHolding(packed, holding)
\r
1764 char *p = holding;
\r
1766 int runlength = 0;
\r
1772 switch (runlength) {
\r
1783 sprintf(q, "%d", runlength);
\r
1795 /* Telnet protocol requests from the front end */
\r
1797 TelnetRequest(ddww, option)
\r
1798 unsigned char ddww, option;
\r
1800 unsigned char msg[3];
\r
1801 int outCount, outError;
\r
1803 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1805 if (appData.debugMode) {
\r
1806 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1822 sprintf(buf1, "%d", ddww);
\r
1827 optionStr = "ECHO";
\r
1831 sprintf(buf2, "%d", option);
\r
1834 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1839 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1840 if (outCount < 3) {
\r
1841 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1848 if (!appData.icsActive) return;
\r
1849 TelnetRequest(TN_DO, TN_ECHO);
\r
1855 if (!appData.icsActive) return;
\r
1856 TelnetRequest(TN_DONT, TN_ECHO);
\r
1860 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1862 /* put the holdings sent to us by the server on the board holdings area */
\r
1863 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1865 ChessSquare piece;
\r
1867 if(gameInfo.holdingsWidth < 2) return;
\r
1869 if( (int)lowestPiece >= BlackPawn ) {
\r
1870 holdingsColumn = 0;
\r
1872 holdingsStartRow = BOARD_HEIGHT-1;
\r
1875 holdingsColumn = BOARD_WIDTH-1;
\r
1876 countsColumn = BOARD_WIDTH-2;
\r
1877 holdingsStartRow = 0;
\r
1881 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1882 board[i][holdingsColumn] = EmptySquare;
\r
1883 board[i][countsColumn] = (ChessSquare) 0;
\r
1885 while( (p=*holdings++) != NULLCHAR ) {
\r
1886 piece = CharToPiece( ToUpper(p) );
\r
1887 if(piece == EmptySquare) continue;
\r
1888 /*j = (int) piece - (int) WhitePawn;*/
\r
1889 j = PieceToNumber(piece);
\r
1890 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1891 if(j < 0) continue; /* should not happen */
\r
1892 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1893 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1894 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1901 VariantSwitch(Board board, VariantClass newVariant)
\r
1903 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1904 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1905 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1907 startedFromPositionFile = FALSE;
\r
1908 if(gameInfo.variant == newVariant) return;
\r
1910 /* [HGM] This routine is called each time an assignment is made to
\r
1911 * gameInfo.variant during a game, to make sure the board sizes
\r
1912 * are set to match the new variant. If that means adding or deleting
\r
1913 * holdings, we shift the playing board accordingly
\r
1914 * This kludge is needed because in ICS observe mode, we get boards
\r
1915 * of an ongoing game without knowing the variant, and learn about the
\r
1916 * latter only later. This can be because of the move list we requested,
\r
1917 * in which case the game history is refilled from the beginning anyway,
\r
1918 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1919 * case we want to add those holdings to the already received position.
\r
1923 if (appData.debugMode) {
\r
1924 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1925 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1926 setbuf(debugFP, NULL);
\r
1928 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1929 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1930 switch(newVariant) {
\r
1931 case VariantShogi:
\r
1932 newWidth = 9; newHeight = 9;
\r
1933 gameInfo.holdingsSize = 7;
\r
1934 case VariantBughouse:
\r
1935 case VariantCrazyhouse:
\r
1936 newHoldingsWidth = 2; break;
\r
1938 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1941 if(newWidth != gameInfo.boardWidth ||
\r
1942 newHeight != gameInfo.boardHeight ||
\r
1943 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1945 /* shift position to new playing area, if needed */
\r
1946 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1947 for(i=0; i<BOARD_HEIGHT; i++)
\r
1948 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1949 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1951 for(i=0; i<newHeight; i++) {
\r
1952 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1953 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1955 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1956 for(i=0; i<BOARD_HEIGHT; i++)
\r
1957 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1958 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1962 gameInfo.boardWidth = newWidth;
\r
1963 gameInfo.boardHeight = newHeight;
\r
1964 gameInfo.holdingsWidth = newHoldingsWidth;
\r
1965 gameInfo.variant = newVariant;
\r
1966 InitDrawingSizes(-2, 0);
\r
1968 /* [HGM] The following should definitely be solved in a better way */
\r
1970 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
1971 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
1972 saveEP = epStatus[0];
\r
1974 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
1976 epStatus[0] = saveEP;
\r
1977 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
1978 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
1980 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
1982 forwardMostMove = oldForwardMostMove;
\r
1983 backwardMostMove = oldBackwardMostMove;
\r
1984 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
1987 static int loggedOn = FALSE;
\r
1989 /*-- Game start info cache: --*/
\r
1991 char gs_kind[MSG_SIZ];
\r
1992 static char player1Name[128] = "";
\r
1993 static char player2Name[128] = "";
\r
1994 static int player1Rating = -1;
\r
1995 static int player2Rating = -1;
\r
1996 /*----------------------------*/
\r
1998 ColorClass curColor = ColorNormal;
\r
2001 read_from_ics(isr, closure, data, count, error)
\r
2002 InputSourceRef isr;
\r
2008 #define BUF_SIZE 8192
\r
2009 #define STARTED_NONE 0
\r
2010 #define STARTED_MOVES 1
\r
2011 #define STARTED_BOARD 2
\r
2012 #define STARTED_OBSERVE 3
\r
2013 #define STARTED_HOLDINGS 4
\r
2014 #define STARTED_CHATTER 5
\r
2015 #define STARTED_COMMENT 6
\r
2016 #define STARTED_MOVES_NOHIDE 7
\r
2018 static int started = STARTED_NONE;
\r
2019 static char parse[20000];
\r
2020 static int parse_pos = 0;
\r
2021 static char buf[BUF_SIZE + 1];
\r
2022 static int firstTime = TRUE, intfSet = FALSE;
\r
2023 static ColorClass prevColor = ColorNormal;
\r
2024 static int savingComment = FALSE;
\r
2033 if (appData.debugMode) {
\r
2035 fprintf(debugFP, "<ICS: ");
\r
2036 show_bytes(debugFP, data, count);
\r
2037 fprintf(debugFP, "\n");
\r
2042 if (appData.debugMode) { int f = forwardMostMove;
\r
2043 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2044 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2047 /* If last read ended with a partial line that we couldn't parse,
\r
2048 prepend it to the new read and try again. */
\r
2049 if (leftover_len > 0) {
\r
2050 for (i=0; i<leftover_len; i++)
\r
2051 buf[i] = buf[leftover_start + i];
\r
2054 /* Copy in new characters, removing nulls and \r's */
\r
2055 buf_len = leftover_len;
\r
2056 for (i = 0; i < count; i++) {
\r
2057 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2058 buf[buf_len++] = data[i];
\r
2059 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2060 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2061 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2064 buf[buf_len] = NULLCHAR;
\r
2065 next_out = leftover_len;
\r
2066 leftover_start = 0;
\r
2069 while (i < buf_len) {
\r
2070 /* Deal with part of the TELNET option negotiation
\r
2071 protocol. We refuse to do anything beyond the
\r
2072 defaults, except that we allow the WILL ECHO option,
\r
2073 which ICS uses to turn off password echoing when we are
\r
2074 directly connected to it. We reject this option
\r
2075 if localLineEditing mode is on (always on in xboard)
\r
2076 and we are talking to port 23, which might be a real
\r
2077 telnet server that will try to keep WILL ECHO on permanently.
\r
2079 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2080 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2081 unsigned char option;
\r
2083 switch ((unsigned char) buf[++i]) {
\r
2085 if (appData.debugMode)
\r
2086 fprintf(debugFP, "\n<WILL ");
\r
2087 switch (option = (unsigned char) buf[++i]) {
\r
2089 if (appData.debugMode)
\r
2090 fprintf(debugFP, "ECHO ");
\r
2091 /* Reply only if this is a change, according
\r
2092 to the protocol rules. */
\r
2093 if (remoteEchoOption) break;
\r
2094 if (appData.localLineEditing &&
\r
2095 atoi(appData.icsPort) == TN_PORT) {
\r
2096 TelnetRequest(TN_DONT, TN_ECHO);
\r
2099 TelnetRequest(TN_DO, TN_ECHO);
\r
2100 remoteEchoOption = TRUE;
\r
2104 if (appData.debugMode)
\r
2105 fprintf(debugFP, "%d ", option);
\r
2106 /* Whatever this is, we don't want it. */
\r
2107 TelnetRequest(TN_DONT, option);
\r
2112 if (appData.debugMode)
\r
2113 fprintf(debugFP, "\n<WONT ");
\r
2114 switch (option = (unsigned char) buf[++i]) {
\r
2116 if (appData.debugMode)
\r
2117 fprintf(debugFP, "ECHO ");
\r
2118 /* Reply only if this is a change, according
\r
2119 to the protocol rules. */
\r
2120 if (!remoteEchoOption) break;
\r
2122 TelnetRequest(TN_DONT, TN_ECHO);
\r
2123 remoteEchoOption = FALSE;
\r
2126 if (appData.debugMode)
\r
2127 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2128 /* Whatever this is, it must already be turned
\r
2129 off, because we never agree to turn on
\r
2130 anything non-default, so according to the
\r
2131 protocol rules, we don't reply. */
\r
2136 if (appData.debugMode)
\r
2137 fprintf(debugFP, "\n<DO ");
\r
2138 switch (option = (unsigned char) buf[++i]) {
\r
2140 /* Whatever this is, we refuse to do it. */
\r
2141 if (appData.debugMode)
\r
2142 fprintf(debugFP, "%d ", option);
\r
2143 TelnetRequest(TN_WONT, option);
\r
2148 if (appData.debugMode)
\r
2149 fprintf(debugFP, "\n<DONT ");
\r
2150 switch (option = (unsigned char) buf[++i]) {
\r
2152 if (appData.debugMode)
\r
2153 fprintf(debugFP, "%d ", option);
\r
2154 /* Whatever this is, we are already not doing
\r
2155 it, because we never agree to do anything
\r
2156 non-default, so according to the protocol
\r
2157 rules, we don't reply. */
\r
2162 if (appData.debugMode)
\r
2163 fprintf(debugFP, "\n<IAC ");
\r
2164 /* Doubled IAC; pass it through */
\r
2168 if (appData.debugMode)
\r
2169 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2170 /* Drop all other telnet commands on the floor */
\r
2173 if (oldi > next_out)
\r
2174 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2175 if (++i > next_out)
\r
2180 /* OK, this at least will *usually* work */
\r
2181 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2185 if (loggedOn && !intfSet) {
\r
2186 if (ics_type == ICS_ICC) {
\r
2188 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2191 } else if (ics_type == ICS_CHESSNET) {
\r
2192 sprintf(str, "/style 12\n");
\r
2194 strcpy(str, "alias $ @\n$set interface ");
\r
2195 strcat(str, programVersion);
\r
2196 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2198 strcat(str, "$iset nohighlight 1\n");
\r
2200 strcat(str, "$iset lock 1\n$style 12\n");
\r
2206 if (started == STARTED_COMMENT) {
\r
2207 /* Accumulate characters in comment */
\r
2208 parse[parse_pos++] = buf[i];
\r
2209 if (buf[i] == '\n') {
\r
2210 parse[parse_pos] = NULLCHAR;
\r
2211 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2212 started = STARTED_NONE;
\r
2214 /* Don't match patterns against characters in chatter */
\r
2219 if (started == STARTED_CHATTER) {
\r
2220 if (buf[i] != '\n') {
\r
2221 /* Don't match patterns against characters in chatter */
\r
2225 started = STARTED_NONE;
\r
2228 /* Kludge to deal with rcmd protocol */
\r
2229 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2230 DisplayFatalError(&buf[1], 0, 1);
\r
2233 firstTime = FALSE;
\r
2236 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2237 ics_type = ICS_ICC;
\r
2239 if (appData.debugMode)
\r
2240 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2243 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2244 ics_type = ICS_FICS;
\r
2246 if (appData.debugMode)
\r
2247 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2250 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2251 ics_type = ICS_CHESSNET;
\r
2253 if (appData.debugMode)
\r
2254 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2259 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2260 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2261 looking_at(buf, &i, "will be \"*\""))) {
\r
2262 strcpy(ics_handle, star_match[0]);
\r
2266 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2267 char buf[MSG_SIZ];
\r
2268 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2269 DisplayIcsInteractionTitle(buf);
\r
2270 have_set_title = TRUE;
\r
2273 /* skip finger notes */
\r
2274 if (started == STARTED_NONE &&
\r
2275 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2276 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2277 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2278 started = STARTED_CHATTER;
\r
2283 /* skip formula vars */
\r
2284 if (started == STARTED_NONE &&
\r
2285 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2286 started = STARTED_CHATTER;
\r
2292 if (appData.zippyTalk || appData.zippyPlay) {
\r
2294 if (ZippyControl(buf, &i) ||
\r
2295 ZippyConverse(buf, &i) ||
\r
2296 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2302 if (/* Don't color "message" or "messages" output */
\r
2303 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2304 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2305 looking_at(buf, &i, "--* (*:*): ") ||
\r
2306 /* Regular tells and says */
\r
2307 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2308 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2309 looking_at(buf, &i, "* says: ") ||
\r
2310 /* Message notifications (same color as tells) */
\r
2311 looking_at(buf, &i, "* has left a message ") ||
\r
2312 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2313 /* Whispers and kibitzes */
\r
2314 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2315 looking_at(buf, &i, "* kibitzes: ") ||
\r
2316 /* Channel tells */
\r
2317 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2319 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2320 /* Avoid "tells you:" spoofs in channels */
\r
2323 if (star_match[0][0] == NULLCHAR ||
\r
2324 strchr(star_match[0], ' ') ||
\r
2325 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2326 /* Reject bogus matches */
\r
2329 if (appData.colorize) {
\r
2330 if (oldi > next_out) {
\r
2331 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2336 Colorize(ColorTell, FALSE);
\r
2337 curColor = ColorTell;
\r
2340 Colorize(ColorKibitz, FALSE);
\r
2341 curColor = ColorKibitz;
\r
2344 p = strrchr(star_match[1], '(');
\r
2346 p = star_match[1];
\r
2350 if (atoi(p) == 1) {
\r
2351 Colorize(ColorChannel1, FALSE);
\r
2352 curColor = ColorChannel1;
\r
2354 Colorize(ColorChannel, FALSE);
\r
2355 curColor = ColorChannel;
\r
2359 curColor = ColorNormal;
\r
2363 if (started == STARTED_NONE && appData.autoComment &&
\r
2364 (gameMode == IcsObserving ||
\r
2365 gameMode == IcsPlayingWhite ||
\r
2366 gameMode == IcsPlayingBlack)) {
\r
2367 parse_pos = i - oldi;
\r
2368 memcpy(parse, &buf[oldi], parse_pos);
\r
2369 parse[parse_pos] = NULLCHAR;
\r
2370 started = STARTED_COMMENT;
\r
2371 savingComment = TRUE;
\r
2373 started = STARTED_CHATTER;
\r
2374 savingComment = FALSE;
\r
2381 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2382 looking_at(buf, &i, "* c-shouts: ")) {
\r
2383 if (appData.colorize) {
\r
2384 if (oldi > next_out) {
\r
2385 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2388 Colorize(ColorSShout, FALSE);
\r
2389 curColor = ColorSShout;
\r
2392 started = STARTED_CHATTER;
\r
2396 if (looking_at(buf, &i, "--->")) {
\r
2401 if (looking_at(buf, &i, "* shouts: ") ||
\r
2402 looking_at(buf, &i, "--> ")) {
\r
2403 if (appData.colorize) {
\r
2404 if (oldi > next_out) {
\r
2405 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2408 Colorize(ColorShout, FALSE);
\r
2409 curColor = ColorShout;
\r
2412 started = STARTED_CHATTER;
\r
2416 if (looking_at( buf, &i, "Challenge:")) {
\r
2417 if (appData.colorize) {
\r
2418 if (oldi > next_out) {
\r
2419 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2422 Colorize(ColorChallenge, FALSE);
\r
2423 curColor = ColorChallenge;
\r
2429 if (looking_at(buf, &i, "* offers you") ||
\r
2430 looking_at(buf, &i, "* offers to be") ||
\r
2431 looking_at(buf, &i, "* would like to") ||
\r
2432 looking_at(buf, &i, "* requests to") ||
\r
2433 looking_at(buf, &i, "Your opponent offers") ||
\r
2434 looking_at(buf, &i, "Your opponent requests")) {
\r
2436 if (appData.colorize) {
\r
2437 if (oldi > next_out) {
\r
2438 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2441 Colorize(ColorRequest, FALSE);
\r
2442 curColor = ColorRequest;
\r
2447 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2448 if (appData.colorize) {
\r
2449 if (oldi > next_out) {
\r
2450 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2453 Colorize(ColorSeek, FALSE);
\r
2454 curColor = ColorSeek;
\r
2460 if (looking_at(buf, &i, "\\ ")) {
\r
2461 if (prevColor != ColorNormal) {
\r
2462 if (oldi > next_out) {
\r
2463 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2466 Colorize(prevColor, TRUE);
\r
2467 curColor = prevColor;
\r
2469 if (savingComment) {
\r
2470 parse_pos = i - oldi;
\r
2471 memcpy(parse, &buf[oldi], parse_pos);
\r
2472 parse[parse_pos] = NULLCHAR;
\r
2473 started = STARTED_COMMENT;
\r
2475 started = STARTED_CHATTER;
\r
2480 if (looking_at(buf, &i, "Black Strength :") ||
\r
2481 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2482 looking_at(buf, &i, "<10>") ||
\r
2483 looking_at(buf, &i, "#@#")) {
\r
2484 /* Wrong board style */
\r
2486 SendToICS(ics_prefix);
\r
2487 SendToICS("set style 12\n");
\r
2488 SendToICS(ics_prefix);
\r
2489 SendToICS("refresh\n");
\r
2493 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2495 have_sent_ICS_logon = 1;
\r
2499 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2500 (looking_at(buf, &i, "\n<12> ") ||
\r
2501 looking_at(buf, &i, "<12> "))) {
\r
2503 if (oldi > next_out) {
\r
2504 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2507 started = STARTED_BOARD;
\r
2512 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2513 looking_at(buf, &i, "<b1> ")) {
\r
2514 if (oldi > next_out) {
\r
2515 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2518 started = STARTED_HOLDINGS;
\r
2523 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2525 /* Header for a move list -- first line */
\r
2527 switch (ics_getting_history) {
\r
2529 switch (gameMode) {
\r
2531 case BeginningOfGame:
\r
2532 /* User typed "moves" or "oldmoves" while we
\r
2533 were idle. Pretend we asked for these
\r
2534 moves and soak them up so user can step
\r
2535 through them and/or save them.
\r
2537 Reset(FALSE, TRUE);
\r
2538 gameMode = IcsObserving;
\r
2541 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2543 case EditGame: /*?*/
\r
2544 case EditPosition: /*?*/
\r
2545 /* Should above feature work in these modes too? */
\r
2546 /* For now it doesn't */
\r
2547 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2550 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2555 /* Is this the right one? */
\r
2556 if (gameInfo.white && gameInfo.black &&
\r
2557 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2558 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2560 ics_getting_history = H_GOT_REQ_HEADER;
\r
2563 case H_GOT_REQ_HEADER:
\r
2564 case H_GOT_UNREQ_HEADER:
\r
2565 case H_GOT_UNWANTED_HEADER:
\r
2566 case H_GETTING_MOVES:
\r
2567 /* Should not happen */
\r
2568 DisplayError("Error gathering move list: two headers", 0);
\r
2569 ics_getting_history = H_FALSE;
\r
2573 /* Save player ratings into gameInfo if needed */
\r
2574 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2575 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2576 (gameInfo.whiteRating == -1 ||
\r
2577 gameInfo.blackRating == -1)) {
\r
2579 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2580 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2581 if (appData.debugMode)
\r
2582 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2583 gameInfo.whiteRating, gameInfo.blackRating);
\r
2588 if (looking_at(buf, &i,
\r
2589 "* * match, initial time: * minute*, increment: * second")) {
\r
2590 /* Header for a move list -- second line */
\r
2591 /* Initial board will follow if this is a wild game */
\r
2592 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2593 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2594 gameInfo.event = StrSave(str);
\r
2595 /* [HGM] we switched variant. Translate boards if needed. */
\r
2596 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2600 if (looking_at(buf, &i, "Move ")) {
\r
2601 /* Beginning of a move list */
\r
2602 switch (ics_getting_history) {
\r
2604 /* Normally should not happen */
\r
2605 /* Maybe user hit reset while we were parsing */
\r
2608 /* Happens if we are ignoring a move list that is not
\r
2609 * the one we just requested. Common if the user
\r
2610 * tries to observe two games without turning off
\r
2613 case H_GETTING_MOVES:
\r
2614 /* Should not happen */
\r
2615 DisplayError("Error gathering move list: nested", 0);
\r
2616 ics_getting_history = H_FALSE;
\r
2618 case H_GOT_REQ_HEADER:
\r
2619 ics_getting_history = H_GETTING_MOVES;
\r
2620 started = STARTED_MOVES;
\r
2622 if (oldi > next_out) {
\r
2623 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2626 case H_GOT_UNREQ_HEADER:
\r
2627 ics_getting_history = H_GETTING_MOVES;
\r
2628 started = STARTED_MOVES_NOHIDE;
\r
2631 case H_GOT_UNWANTED_HEADER:
\r
2632 ics_getting_history = H_FALSE;
\r
2638 if (looking_at(buf, &i, "% ") ||
\r
2639 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2640 && looking_at(buf, &i, "}*"))) {
\r
2641 savingComment = FALSE;
\r
2642 switch (started) {
\r
2643 case STARTED_MOVES:
\r
2644 case STARTED_MOVES_NOHIDE:
\r
2645 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2646 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2647 ParseGameHistory(parse);
\r
2649 if (appData.zippyPlay && first.initDone) {
\r
2650 FeedMovesToProgram(&first, forwardMostMove);
\r
2651 if (gameMode == IcsPlayingWhite) {
\r
2652 if (WhiteOnMove(forwardMostMove)) {
\r
2653 if (first.sendTime) {
\r
2654 if (first.useColors) {
\r
2655 SendToProgram("black\n", &first);
\r
2657 SendTimeRemaining(&first, TRUE);
\r
2659 if (first.useColors) {
\r
2660 SendToProgram("white\ngo\n", &first);
\r
2662 SendToProgram("go\n", &first);
\r
2664 first.maybeThinking = TRUE;
\r
2666 if (first.usePlayother) {
\r
2667 if (first.sendTime) {
\r
2668 SendTimeRemaining(&first, TRUE);
\r
2670 SendToProgram("playother\n", &first);
\r
2671 firstMove = FALSE;
\r
2676 } else if (gameMode == IcsPlayingBlack) {
\r
2677 if (!WhiteOnMove(forwardMostMove)) {
\r
2678 if (first.sendTime) {
\r
2679 if (first.useColors) {
\r
2680 SendToProgram("white\n", &first);
\r
2682 SendTimeRemaining(&first, FALSE);
\r
2684 if (first.useColors) {
\r
2685 SendToProgram("black\ngo\n", &first);
\r
2687 SendToProgram("go\n", &first);
\r
2689 first.maybeThinking = TRUE;
\r
2691 if (first.usePlayother) {
\r
2692 if (first.sendTime) {
\r
2693 SendTimeRemaining(&first, FALSE);
\r
2695 SendToProgram("playother\n", &first);
\r
2696 firstMove = FALSE;
\r
2704 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2705 /* Moves came from oldmoves or moves command
\r
2706 while we weren't doing anything else.
\r
2708 currentMove = forwardMostMove;
\r
2709 ClearHighlights();/*!!could figure this out*/
\r
2710 flipView = appData.flipView;
\r
2711 DrawPosition(FALSE, boards[currentMove]);
\r
2712 DisplayBothClocks();
\r
2713 sprintf(str, "%s vs. %s",
\r
2714 gameInfo.white, gameInfo.black);
\r
2715 DisplayTitle(str);
\r
2716 gameMode = IcsIdle;
\r
2718 /* Moves were history of an active game */
\r
2719 if (gameInfo.resultDetails != NULL) {
\r
2720 free(gameInfo.resultDetails);
\r
2721 gameInfo.resultDetails = NULL;
\r
2724 HistorySet(parseList, backwardMostMove,
\r
2725 forwardMostMove, currentMove-1);
\r
2726 DisplayMove(currentMove - 1);
\r
2727 if (started == STARTED_MOVES) next_out = i;
\r
2728 started = STARTED_NONE;
\r
2729 ics_getting_history = H_FALSE;
\r
2732 case STARTED_OBSERVE:
\r
2733 started = STARTED_NONE;
\r
2734 SendToICS(ics_prefix);
\r
2735 SendToICS("refresh\n");
\r
2744 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2745 started == STARTED_HOLDINGS ||
\r
2746 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2747 /* Accumulate characters in move list or board */
\r
2748 parse[parse_pos++] = buf[i];
\r
2751 /* Start of game messages. Mostly we detect start of game
\r
2752 when the first board image arrives. On some versions
\r
2753 of the ICS, though, we need to do a "refresh" after starting
\r
2754 to observe in order to get the current board right away. */
\r
2755 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2756 started = STARTED_OBSERVE;
\r
2760 /* Handle auto-observe */
\r
2761 if (appData.autoObserve &&
\r
2762 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2763 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2765 /* Choose the player that was highlighted, if any. */
\r
2766 if (star_match[0][0] == '\033' ||
\r
2767 star_match[1][0] != '\033') {
\r
2768 player = star_match[0];
\r
2770 player = star_match[2];
\r
2772 sprintf(str, "%sobserve %s\n",
\r
2773 ics_prefix, StripHighlightAndTitle(player));
\r
2776 /* Save ratings from notify string */
\r
2777 strcpy(player1Name, star_match[0]);
\r
2778 player1Rating = string_to_rating(star_match[1]);
\r
2779 strcpy(player2Name, star_match[2]);
\r
2780 player2Rating = string_to_rating(star_match[3]);
\r
2782 if (appData.debugMode)
\r
2784 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2785 player1Name, player1Rating,
\r
2786 player2Name, player2Rating);
\r
2791 /* Deal with automatic examine mode after a game,
\r
2792 and with IcsObserving -> IcsExamining transition */
\r
2793 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2794 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2796 int gamenum = atoi(star_match[0]);
\r
2797 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2798 gamenum == ics_gamenum) {
\r
2799 /* We were already playing or observing this game;
\r
2800 no need to refetch history */
\r
2801 gameMode = IcsExamining;
\r
2803 pauseExamForwardMostMove = forwardMostMove;
\r
2804 } else if (currentMove < forwardMostMove) {
\r
2805 ForwardInner(forwardMostMove);
\r
2808 /* I don't think this case really can happen */
\r
2809 SendToICS(ics_prefix);
\r
2810 SendToICS("refresh\n");
\r
2815 /* Error messages */
\r
2816 if (ics_user_moved) {
\r
2817 if (looking_at(buf, &i, "Illegal move") ||
\r
2818 looking_at(buf, &i, "Not a legal move") ||
\r
2819 looking_at(buf, &i, "Your king is in check") ||
\r
2820 looking_at(buf, &i, "It isn't your turn") ||
\r
2821 looking_at(buf, &i, "It is not your move")) {
\r
2822 /* Illegal move */
\r
2823 ics_user_moved = 0;
\r
2824 if (forwardMostMove > backwardMostMove) {
\r
2825 currentMove = --forwardMostMove;
\r
2826 DisplayMove(currentMove - 1); /* before DMError */
\r
2827 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2828 DrawPosition(FALSE, boards[currentMove]);
\r
2830 DisplayBothClocks();
\r
2836 if (looking_at(buf, &i, "still have time") ||
\r
2837 looking_at(buf, &i, "not out of time") ||
\r
2838 looking_at(buf, &i, "either player is out of time") ||
\r
2839 looking_at(buf, &i, "has timeseal; checking")) {
\r
2840 /* We must have called his flag a little too soon */
\r
2841 whiteFlag = blackFlag = FALSE;
\r
2845 if (looking_at(buf, &i, "added * seconds to") ||
\r
2846 looking_at(buf, &i, "seconds were added to")) {
\r
2847 /* Update the clocks */
\r
2848 SendToICS(ics_prefix);
\r
2849 SendToICS("refresh\n");
\r
2853 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2854 ics_clock_paused = TRUE;
\r
2859 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2860 ics_clock_paused = FALSE;
\r
2865 /* Grab player ratings from the Creating: message.
\r
2866 Note we have to check for the special case when
\r
2867 the ICS inserts things like [white] or [black]. */
\r
2868 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2869 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2871 0 player 1 name (not necessarily white)
\r
2873 2 empty, white, or black (IGNORED)
\r
2874 3 player 2 name (not necessarily black)
\r
2877 The names/ratings are sorted out when the game
\r
2878 actually starts (below).
\r
2880 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2881 player1Rating = string_to_rating(star_match[1]);
\r
2882 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2883 player2Rating = string_to_rating(star_match[4]);
\r
2885 if (appData.debugMode)
\r
2887 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2888 player1Name, player1Rating,
\r
2889 player2Name, player2Rating);
\r
2894 /* Improved generic start/end-of-game messages */
\r
2895 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2896 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2897 /* If tkind == 0: */
\r
2898 /* star_match[0] is the game number */
\r
2899 /* [1] is the white player's name */
\r
2900 /* [2] is the black player's name */
\r
2901 /* For end-of-game: */
\r
2902 /* [3] is the reason for the game end */
\r
2903 /* [4] is a PGN end game-token, preceded by " " */
\r
2904 /* For start-of-game: */
\r
2905 /* [3] begins with "Creating" or "Continuing" */
\r
2906 /* [4] is " *" or empty (don't care). */
\r
2907 int gamenum = atoi(star_match[0]);
\r
2908 char *whitename, *blackname, *why, *endtoken;
\r
2909 ChessMove endtype = (ChessMove) 0;
\r
2912 whitename = star_match[1];
\r
2913 blackname = star_match[2];
\r
2914 why = star_match[3];
\r
2915 endtoken = star_match[4];
\r
2917 whitename = star_match[1];
\r
2918 blackname = star_match[3];
\r
2919 why = star_match[5];
\r
2920 endtoken = star_match[6];
\r
2923 /* Game start messages */
\r
2924 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2925 strncmp(why, "Continuing ", 11) == 0) {
\r
2926 gs_gamenum = gamenum;
\r
2927 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2929 if (appData.zippyPlay) {
\r
2930 ZippyGameStart(whitename, blackname);
\r
2936 /* Game end messages */
\r
2937 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2938 ics_gamenum != gamenum) {
\r
2941 while (endtoken[0] == ' ') endtoken++;
\r
2942 switch (endtoken[0]) {
\r
2945 endtype = GameUnfinished;
\r
2948 endtype = BlackWins;
\r
2951 if (endtoken[1] == '/')
\r
2952 endtype = GameIsDrawn;
\r
2954 endtype = WhiteWins;
\r
2957 GameEnds(endtype, why, GE_ICS);
\r
2959 if (appData.zippyPlay && first.initDone) {
\r
2960 ZippyGameEnd(endtype, why);
\r
2961 if (first.pr == NULL) {
\r
2962 /* Start the next process early so that we'll
\r
2963 be ready for the next challenge */
\r
2964 StartChessProgram(&first);
\r
2966 /* Send "new" early, in case this command takes
\r
2967 a long time to finish, so that we'll be ready
\r
2968 for the next challenge. */
\r
2969 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
2970 Reset(TRUE, TRUE);
\r
2976 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2977 looking_at(buf, &i, "no longer observing game *") ||
\r
2978 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2979 if (gameMode == IcsObserving &&
\r
2980 atoi(star_match[0]) == ics_gamenum)
\r
2983 gameMode = IcsIdle;
\r
2985 ics_user_moved = FALSE;
\r
2990 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2991 if (gameMode == IcsExamining &&
\r
2992 atoi(star_match[0]) == ics_gamenum)
\r
2994 gameMode = IcsIdle;
\r
2996 ics_user_moved = FALSE;
\r
3001 /* Advance leftover_start past any newlines we find,
\r
3002 so only partial lines can get reparsed */
\r
3003 if (looking_at(buf, &i, "\n")) {
\r
3004 prevColor = curColor;
\r
3005 if (curColor != ColorNormal) {
\r
3006 if (oldi > next_out) {
\r
3007 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3010 Colorize(ColorNormal, FALSE);
\r
3011 curColor = ColorNormal;
\r
3013 if (started == STARTED_BOARD) {
\r
3014 started = STARTED_NONE;
\r
3015 parse[parse_pos] = NULLCHAR;
\r
3016 ParseBoard12(parse);
\r
3017 ics_user_moved = 0;
\r
3019 /* Send premove here */
\r
3020 if (appData.premove) {
\r
3021 char str[MSG_SIZ];
\r
3022 if (currentMove == 0 &&
\r
3023 gameMode == IcsPlayingWhite &&
\r
3024 appData.premoveWhite) {
\r
3025 sprintf(str, "%s%s\n", ics_prefix,
\r
3026 appData.premoveWhiteText);
\r
3027 if (appData.debugMode)
\r
3028 fprintf(debugFP, "Sending premove:\n");
\r
3030 } else if (currentMove == 1 &&
\r
3031 gameMode == IcsPlayingBlack &&
\r
3032 appData.premoveBlack) {
\r
3033 sprintf(str, "%s%s\n", ics_prefix,
\r
3034 appData.premoveBlackText);
\r
3035 if (appData.debugMode)
\r
3036 fprintf(debugFP, "Sending premove:\n");
\r
3038 } else if (gotPremove) {
\r
3040 ClearPremoveHighlights();
\r
3041 if (appData.debugMode)
\r
3042 fprintf(debugFP, "Sending premove:\n");
\r
3043 UserMoveEvent(premoveFromX, premoveFromY,
\r
3044 premoveToX, premoveToY,
\r
3045 premovePromoChar);
\r
3049 /* Usually suppress following prompt */
\r
3050 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3051 if (looking_at(buf, &i, "*% ")) {
\r
3052 savingComment = FALSE;
\r
3056 } else if (started == STARTED_HOLDINGS) {
\r
3058 char new_piece[MSG_SIZ];
\r
3059 started = STARTED_NONE;
\r
3060 parse[parse_pos] = NULLCHAR;
\r
3061 if (appData.debugMode)
\r
3062 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3063 parse, currentMove);
\r
3064 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3065 gamenum == ics_gamenum) {
\r
3066 if (gameInfo.variant == VariantNormal) {
\r
3067 /* [HGM] We seem to switch variant during a game!
\r
3068 * Presumably no holdings were displayed, so we have
\r
3069 * to move the position two files to the right to
\r
3070 * create room for them!
\r
3072 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3073 /* Get a move list just to see the header, which
\r
3074 will tell us whether this is really bug or zh */
\r
3075 if (ics_getting_history == H_FALSE) {
\r
3076 ics_getting_history = H_REQUESTED;
\r
3077 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3081 new_piece[0] = NULLCHAR;
\r
3082 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3083 &gamenum, white_holding, black_holding,
\r
3085 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3086 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3087 /* [HGM] copy holdings to board holdings area */
\r
3088 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3089 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3091 if (appData.zippyPlay && first.initDone) {
\r
3092 ZippyHoldings(white_holding, black_holding,
\r
3096 if (tinyLayout || smallLayout) {
\r
3097 char wh[16], bh[16];
\r
3098 PackHolding(wh, white_holding);
\r
3099 PackHolding(bh, black_holding);
\r
3100 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3101 gameInfo.white, gameInfo.black);
\r
3103 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3104 gameInfo.white, white_holding,
\r
3105 gameInfo.black, black_holding);
\r
3108 DrawPosition(FALSE, boards[currentMove]);
\r
3109 DisplayTitle(str);
\r
3111 /* Suppress following prompt */
\r
3112 if (looking_at(buf, &i, "*% ")) {
\r
3113 savingComment = FALSE;
\r
3120 i++; /* skip unparsed character and loop back */
\r
3123 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
3124 started != STARTED_HOLDINGS && i > next_out) {
\r
3125 SendToPlayer(&buf[next_out], i - next_out);
\r
3129 leftover_len = buf_len - leftover_start;
\r
3130 /* if buffer ends with something we couldn't parse,
\r
3131 reparse it after appending the next read */
\r
3133 } else if (count == 0) {
\r
3134 RemoveInputSource(isr);
\r
3135 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
3137 DisplayFatalError("Error reading from ICS", error, 1);
\r
3142 /* Board style 12 looks like this:
\r
3144 <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
3146 * The "<12> " is stripped before it gets to this routine. The two
\r
3147 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3148 * some chess servers may not have them, or may have only the first.
\r
3149 * Additional trailing fields may be added in the future.
\r
3152 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
\r
3154 #define RELATION_OBSERVING_PLAYED 0
\r
3155 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3156 #define RELATION_PLAYING_MYMOVE 1
\r
3157 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3158 #define RELATION_EXAMINING 2
\r
3159 #define RELATION_ISOLATED_BOARD -3
\r
3160 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3163 ParseBoard12(string)
\r
3166 GameMode newGameMode;
\r
3167 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
3168 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
3169 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3170 char to_play, board_chars[200];
\r
3171 char move_str[500], str[500], elapsed_time[500];
\r
3172 char black[32], white[32];
\r
3174 int prevMove = currentMove;
\r
3176 ChessMove moveType;
\r
3177 int fromX, fromY, toX, toY;
\r
3179 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3180 char *bookHit = NULL; // [HGM] book
\r
3182 fromX = fromY = toX = toY = -1;
\r
3186 if (appData.debugMode)
\r
3187 fprintf(debugFP, "Parsing board: %s\n", string);
\r
3189 move_str[0] = NULLCHAR;
\r
3190 elapsed_time[0] = NULLCHAR;
\r
3191 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3193 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3194 if(string[i] == ' ') { ranks++; files = 0; }
\r
3198 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3199 board_chars[i] = '\0';
\r
3202 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3203 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3204 &gamenum, white, black, &relation, &basetime, &increment,
\r
3205 &white_stren, &black_stren, &white_time, &black_time,
\r
3206 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3210 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
3211 DisplayError(str, 0);
\r
3215 /* Convert the move number to internal form */
\r
3216 moveNum = (moveNum - 1) * 2;
\r
3217 if (to_play == 'B') moveNum++;
\r
3218 if (moveNum >= MAX_MOVES) {
\r
3219 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3224 switch (relation) {
\r
3225 case RELATION_OBSERVING_PLAYED:
\r
3226 case RELATION_OBSERVING_STATIC:
\r
3227 if (gamenum == -1) {
\r
3228 /* Old ICC buglet */
\r
3229 relation = RELATION_OBSERVING_STATIC;
\r
3231 newGameMode = IcsObserving;
\r
3233 case RELATION_PLAYING_MYMOVE:
\r
3234 case RELATION_PLAYING_NOTMYMOVE:
\r
3236 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3237 IcsPlayingWhite : IcsPlayingBlack;
\r
3239 case RELATION_EXAMINING:
\r
3240 newGameMode = IcsExamining;
\r
3242 case RELATION_ISOLATED_BOARD:
\r
3244 /* Just display this board. If user was doing something else,
\r
3245 we will forget about it until the next board comes. */
\r
3246 newGameMode = IcsIdle;
\r
3248 case RELATION_STARTING_POSITION:
\r
3249 newGameMode = gameMode;
\r
3253 /* Modify behavior for initial board display on move listing
\r
3256 switch (ics_getting_history) {
\r
3260 case H_GOT_REQ_HEADER:
\r
3261 case H_GOT_UNREQ_HEADER:
\r
3262 /* This is the initial position of the current game */
\r
3263 gamenum = ics_gamenum;
\r
3264 moveNum = 0; /* old ICS bug workaround */
\r
3265 if (to_play == 'B') {
\r
3266 startedFromSetupPosition = TRUE;
\r
3267 blackPlaysFirst = TRUE;
\r
3269 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3270 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3271 if (currentMove == 0) currentMove = 1;
\r
3273 newGameMode = gameMode;
\r
3274 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3276 case H_GOT_UNWANTED_HEADER:
\r
3277 /* This is an initial board that we don't want */
\r
3279 case H_GETTING_MOVES:
\r
3280 /* Should not happen */
\r
3281 DisplayError("Error gathering move list: extra board", 0);
\r
3282 ics_getting_history = H_FALSE;
\r
3286 /* Take action if this is the first board of a new game, or of a
\r
3287 different game than is currently being displayed. */
\r
3288 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3289 relation == RELATION_ISOLATED_BOARD) {
\r
3291 /* Forget the old game and get the history (if any) of the new one */
\r
3292 if (gameMode != BeginningOfGame) {
\r
3293 Reset(FALSE, TRUE);
\r
3296 if (appData.autoRaiseBoard) BoardToTop();
\r
3298 if (gamenum == -1) {
\r
3299 newGameMode = IcsIdle;
\r
3300 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3301 appData.getMoveList) {
\r
3302 /* Need to get game history */
\r
3303 ics_getting_history = H_REQUESTED;
\r
3304 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3308 /* Initially flip the board to have black on the bottom if playing
\r
3309 black or if the ICS flip flag is set, but let the user change
\r
3310 it with the Flip View button. */
\r
3311 flipView = appData.autoFlipView ?
\r
3312 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3315 /* Done with values from previous mode; copy in new ones */
\r
3316 gameMode = newGameMode;
\r
3318 ics_gamenum = gamenum;
\r
3319 if (gamenum == gs_gamenum) {
\r
3320 int klen = strlen(gs_kind);
\r
3321 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3322 sprintf(str, "ICS %s", gs_kind);
\r
3323 gameInfo.event = StrSave(str);
\r
3325 gameInfo.event = StrSave("ICS game");
\r
3327 gameInfo.site = StrSave(appData.icsHost);
\r
3328 gameInfo.date = PGNDate();
\r
3329 gameInfo.round = StrSave("-");
\r
3330 gameInfo.white = StrSave(white);
\r
3331 gameInfo.black = StrSave(black);
\r
3332 timeControl = basetime * 60 * 1000;
\r
3333 timeControl_2 = 0;
\r
3334 timeIncrement = increment * 1000;
\r
3335 movesPerSession = 0;
\r
3336 gameInfo.timeControl = TimeControlTagValue();
\r
3337 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3338 if (appData.debugMode) {
\r
3339 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3340 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3341 setbuf(debugFP, NULL);
\r
3344 gameInfo.outOfBook = NULL;
\r
3346 /* Do we have the ratings? */
\r
3347 if (strcmp(player1Name, white) == 0 &&
\r
3348 strcmp(player2Name, black) == 0) {
\r
3349 if (appData.debugMode)
\r
3350 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3351 player1Rating, player2Rating);
\r
3352 gameInfo.whiteRating = player1Rating;
\r
3353 gameInfo.blackRating = player2Rating;
\r
3354 } else if (strcmp(player2Name, white) == 0 &&
\r
3355 strcmp(player1Name, black) == 0) {
\r
3356 if (appData.debugMode)
\r
3357 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3358 player2Rating, player1Rating);
\r
3359 gameInfo.whiteRating = player2Rating;
\r
3360 gameInfo.blackRating = player1Rating;
\r
3362 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3364 /* Silence shouts if requested */
\r
3365 if (appData.quietPlay &&
\r
3366 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3367 SendToICS(ics_prefix);
\r
3368 SendToICS("set shout 0\n");
\r
3372 /* Deal with midgame name changes */
\r
3374 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3375 if (gameInfo.white) free(gameInfo.white);
\r
3376 gameInfo.white = StrSave(white);
\r
3378 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3379 if (gameInfo.black) free(gameInfo.black);
\r
3380 gameInfo.black = StrSave(black);
\r
3384 /* Throw away game result if anything actually changes in examine mode */
\r
3385 if (gameMode == IcsExamining && !newGame) {
\r
3386 gameInfo.result = GameUnfinished;
\r
3387 if (gameInfo.resultDetails != NULL) {
\r
3388 free(gameInfo.resultDetails);
\r
3389 gameInfo.resultDetails = NULL;
\r
3393 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3394 in if they are in a different variation than we are. */
\r
3395 if (pauseExamInvalid) return;
\r
3396 if (pausing && gameMode == IcsExamining) {
\r
3397 if (moveNum <= pauseExamForwardMostMove) {
\r
3398 pauseExamInvalid = TRUE;
\r
3399 forwardMostMove = pauseExamForwardMostMove;
\r
3404 if (appData.debugMode) {
\r
3405 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3407 /* Parse the board */
\r
3408 for (k = 0; k < ranks; k++) {
\r
3409 for (j = 0; j < files; j++)
\r
3410 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3411 if(gameInfo.holdingsWidth > 1) {
\r
3412 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3413 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3416 CopyBoard(boards[moveNum], board);
\r
3417 if (moveNum == 0) {
\r
3418 startedFromSetupPosition =
\r
3419 !CompareBoards(board, initialPosition);
\r
3420 if(startedFromSetupPosition)
\r
3421 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3424 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3425 to make it also work for FRC opening positions. Note that board12
\r
3426 is really defective for later FRC positions, as it has no way to
\r
3427 indicate which Rook can castle if they are on the same side of King.
\r
3428 For the initial position we grant rights to the outermost Rooks,
\r
3429 and remember thos rights, and we then copy them on positions
\r
3430 later in an FRC game. This means WB might not recognize castlings with
\r
3431 Rooks that have moved back to their original position as illegal,
\r
3432 but in ICS mode that is not its job anyway.
\r
3434 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3435 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3437 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3438 if(board[0][i] == WhiteRook) j = i;
\r
3439 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3440 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3441 if(board[0][i] == WhiteRook) j = i;
\r
3442 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3443 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3444 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3445 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3446 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3447 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3448 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3450 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3451 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3452 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3453 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3454 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3455 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3457 r = castlingRights[moveNum][0] = initialRights[0];
\r
3458 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3459 r = castlingRights[moveNum][1] = initialRights[1];
\r
3460 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3461 r = castlingRights[moveNum][3] = initialRights[3];
\r
3462 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3463 r = castlingRights[moveNum][4] = initialRights[4];
\r
3464 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3465 /* wildcastle kludge: always assume King has rights */
\r
3466 r = castlingRights[moveNum][2] = initialRights[2];
\r
3467 r = castlingRights[moveNum][5] = initialRights[5];
\r
3469 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3470 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3473 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3474 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3475 /* This was an initial position from a move list, not
\r
3476 the current position */
\r
3480 /* Update currentMove and known move number limits */
\r
3481 newMove = newGame || moveNum > forwardMostMove;
\r
3483 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3484 if (gameMode == IcsExamining && moveNum == 0) {
\r
3485 /* Workaround for ICS limitation: we are not told the wild
\r
3486 type when starting to examine a game. But if we ask for
\r
3487 the move list, the move list header will tell us */
\r
3488 ics_getting_history = H_REQUESTED;
\r
3489 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3492 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3493 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3494 forwardMostMove = moveNum;
\r
3495 if (!pausing || currentMove > forwardMostMove)
\r
3496 currentMove = forwardMostMove;
\r
3498 /* New part of history that is not contiguous with old part */
\r
3499 if (pausing && gameMode == IcsExamining) {
\r
3500 pauseExamInvalid = TRUE;
\r
3501 forwardMostMove = pauseExamForwardMostMove;
\r
3504 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3505 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3506 ics_getting_history = H_REQUESTED;
\r
3507 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3512 /* Update the clocks */
\r
3513 if (strchr(elapsed_time, '.')) {
\r
3514 /* Time is in ms */
\r
3515 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3516 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3518 /* Time is in seconds */
\r
3519 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3520 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3525 if (appData.zippyPlay && newGame &&
\r
3526 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3527 gameMode != IcsExamining)
\r
3528 ZippyFirstBoard(moveNum, basetime, increment);
\r
3531 /* Put the move on the move list, first converting
\r
3532 to canonical algebraic form. */
\r
3533 if (moveNum > 0) {
\r
3534 if (appData.debugMode) {
\r
3535 if (appData.debugMode) { int f = forwardMostMove;
\r
3536 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3537 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3539 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3540 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3541 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3542 setbuf(debugFP, NULL);
\r
3544 if (moveNum <= backwardMostMove) {
\r
3545 /* We don't know what the board looked like before
\r
3546 this move. Punt. */
\r
3547 strcpy(parseList[moveNum - 1], move_str);
\r
3548 strcat(parseList[moveNum - 1], " ");
\r
3549 strcat(parseList[moveNum - 1], elapsed_time);
\r
3550 moveList[moveNum - 1][0] = NULLCHAR;
\r
3551 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3552 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3553 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3554 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3555 fromY, fromX, toY, toX, promoChar,
\r
3556 parseList[moveNum-1]);
\r
3557 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3558 castlingRights[moveNum]) ) {
\r
3560 case MT_STALEMATE:
\r
3564 if(gameInfo.variant != VariantShogi)
\r
3565 strcat(parseList[moveNum - 1], "+");
\r
3567 case MT_CHECKMATE:
\r
3568 strcat(parseList[moveNum - 1], "#");
\r
3571 strcat(parseList[moveNum - 1], " ");
\r
3572 strcat(parseList[moveNum - 1], elapsed_time);
\r
3573 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3574 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3575 strcat(moveList[moveNum - 1], "\n");
\r
3576 } else if (strcmp(move_str, "none") == 0) {
\r
3577 /* Again, we don't know what the board looked like;
\r
3578 this is really the start of the game. */
\r
3579 parseList[moveNum - 1][0] = NULLCHAR;
\r
3580 moveList[moveNum - 1][0] = NULLCHAR;
\r
3581 backwardMostMove = moveNum;
\r
3582 startedFromSetupPosition = TRUE;
\r
3583 fromX = fromY = toX = toY = -1;
\r
3585 /* Move from ICS was illegal!? Punt. */
\r
3586 if (appData.debugMode) {
\r
3587 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3588 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3591 if (appData.testLegality && appData.debugMode) {
\r
3592 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3593 DisplayError(str, 0);
\r
3596 strcpy(parseList[moveNum - 1], move_str);
\r
3597 strcat(parseList[moveNum - 1], " ");
\r
3598 strcat(parseList[moveNum - 1], elapsed_time);
\r
3599 moveList[moveNum - 1][0] = NULLCHAR;
\r
3600 fromX = fromY = toX = toY = -1;
\r
3602 if (appData.debugMode) {
\r
3603 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3604 setbuf(debugFP, NULL);
\r
3608 /* Send move to chess program (BEFORE animating it). */
\r
3609 if (appData.zippyPlay && !newGame && newMove &&
\r
3610 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3612 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3613 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3614 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3615 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3617 DisplayError(str, 0);
\r
3619 if (first.sendTime) {
\r
3620 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3622 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3623 if (firstMove && !bookHit) {
\r
3624 firstMove = FALSE;
\r
3625 if (first.useColors) {
\r
3626 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3628 "black\ngo\n", &first);
\r
3630 SendToProgram("go\n", &first);
\r
3632 first.maybeThinking = TRUE;
\r
3635 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3636 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3637 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3638 DisplayError(str, 0);
\r
3640 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3641 SendMoveToProgram(moveNum - 1, &first);
\r
3648 if (moveNum > 0 && !gotPremove) {
\r
3649 /* If move comes from a remote source, animate it. If it
\r
3650 isn't remote, it will have already been animated. */
\r
3651 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3652 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3654 if (!pausing && appData.highlightLastMove) {
\r
3655 SetHighlights(fromX, fromY, toX, toY);
\r
3659 /* Start the clocks */
\r
3660 whiteFlag = blackFlag = FALSE;
\r
3661 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3662 if (ticking == 0) {
\r
3663 ics_clock_paused = TRUE;
\r
3665 } else if (ticking == 1) {
\r
3666 ics_clock_paused = FALSE;
\r
3668 if (gameMode == IcsIdle ||
\r
3669 relation == RELATION_OBSERVING_STATIC ||
\r
3670 relation == RELATION_EXAMINING ||
\r
3672 DisplayBothClocks();
\r
3676 /* Display opponents and material strengths */
\r
3677 if (gameInfo.variant != VariantBughouse &&
\r
3678 gameInfo.variant != VariantCrazyhouse) {
\r
3679 if (tinyLayout || smallLayout) {
\r
3680 if(gameInfo.variant == VariantNormal)
\r
3681 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3682 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3683 basetime, increment);
\r
3685 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3686 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3687 basetime, increment, (int) gameInfo.variant);
\r
3689 if(gameInfo.variant == VariantNormal)
\r
3690 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3691 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3692 basetime, increment);
\r
3694 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3695 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3696 basetime, increment, VariantName(gameInfo.variant));
\r
3698 DisplayTitle(str);
\r
3699 if (appData.debugMode) {
\r
3700 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3705 /* Display the board */
\r
3708 if (appData.premove)
\r
3709 if (!gotPremove ||
\r
3710 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3711 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3712 ClearPremoveHighlights();
\r
3714 DrawPosition(FALSE, boards[currentMove]);
\r
3715 DisplayMove(moveNum - 1);
\r
3716 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3720 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3722 if(bookHit) { // [HGM] book: simulate book reply
\r
3723 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3725 programStats.depth = programStats.nodes = programStats.time =
\r
3726 programStats.score = programStats.got_only_move = 0;
\r
3727 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
3729 strcpy(bookMove, "move ");
\r
3730 strcat(bookMove, bookHit);
\r
3731 HandleMachineMove(bookMove, &first);
\r
3737 GetMoveListEvent()
\r
3739 char buf[MSG_SIZ];
\r
3740 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3741 ics_getting_history = H_REQUESTED;
\r
3742 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3748 AnalysisPeriodicEvent(force)
\r
3751 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3752 && !force) || !appData.periodicUpdates)
\r
3755 /* Send . command to Crafty to collect stats */
\r
3756 SendToProgram(".\n", &first);
\r
3758 /* Don't send another until we get a response (this makes
\r
3759 us stop sending to old Crafty's which don't understand
\r
3760 the "." command (sending illegal cmds resets node count & time,
\r
3761 which looks bad)) */
\r
3762 programStats.ok_to_send = 0;
\r
3766 SendMoveToProgram(moveNum, cps)
\r
3768 ChessProgramState *cps;
\r
3770 char buf[MSG_SIZ];
\r
3772 if (cps->useUsermove) {
\r
3773 SendToProgram("usermove ", cps);
\r
3775 if (cps->useSAN) {
\r
3777 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3778 int len = space - parseList[moveNum];
\r
3779 memcpy(buf, parseList[moveNum], len);
\r
3780 buf[len++] = '\n';
\r
3781 buf[len] = NULLCHAR;
\r
3783 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3785 SendToProgram(buf, cps);
\r
3787 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3788 AlphaRank(moveList[moveNum], 4);
\r
3789 SendToProgram(moveList[moveNum], cps);
\r
3790 AlphaRank(moveList[moveNum], 4); // and back
\r
3792 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3793 * the engine. It would be nice to have a better way to identify castle
\r
3795 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3796 && cps->useOOCastle) {
\r
3797 int fromX = moveList[moveNum][0] - AAA;
\r
3798 int fromY = moveList[moveNum][1] - ONE;
\r
3799 int toX = moveList[moveNum][2] - AAA;
\r
3800 int toY = moveList[moveNum][3] - ONE;
\r
3801 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3802 && boards[moveNum][toY][toX] == WhiteRook)
\r
3803 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3804 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3805 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3806 else SendToProgram("O-O-O\n", cps);
\r
3808 else SendToProgram(moveList[moveNum], cps);
\r
3810 else SendToProgram(moveList[moveNum], cps);
\r
3811 /* End of additions by Tord */
\r
3814 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3815 /* Send 'go' if we are in a mode where machine should play. */
\r
3816 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3817 (gameMode == TwoMachinesPlay ||
\r
3819 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3821 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3822 SendToProgram("go\n", cps);
\r
3823 if (appData.debugMode) {
\r
3824 fprintf(debugFP, "(extra)\n");
\r
3827 setboardSpoiledMachineBlack = 0;
\r
3831 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3832 ChessMove moveType;
\r
3833 int fromX, fromY, toX, toY;
\r
3835 char user_move[MSG_SIZ];
\r
3837 switch (moveType) {
\r
3839 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3840 (int)moveType, fromX, fromY, toX, toY);
\r
3841 DisplayError(user_move + strlen("say "), 0);
\r
3843 case WhiteKingSideCastle:
\r
3844 case BlackKingSideCastle:
\r
3845 case WhiteQueenSideCastleWild:
\r
3846 case BlackQueenSideCastleWild:
\r
3848 case WhiteHSideCastleFR:
\r
3849 case BlackHSideCastleFR:
\r
3851 sprintf(user_move, "o-o\n");
\r
3853 case WhiteQueenSideCastle:
\r
3854 case BlackQueenSideCastle:
\r
3855 case WhiteKingSideCastleWild:
\r
3856 case BlackKingSideCastleWild:
\r
3858 case WhiteASideCastleFR:
\r
3859 case BlackASideCastleFR:
\r
3861 sprintf(user_move, "o-o-o\n");
\r
3863 case WhitePromotionQueen:
\r
3864 case BlackPromotionQueen:
\r
3865 case WhitePromotionRook:
\r
3866 case BlackPromotionRook:
\r
3867 case WhitePromotionBishop:
\r
3868 case BlackPromotionBishop:
\r
3869 case WhitePromotionKnight:
\r
3870 case BlackPromotionKnight:
\r
3871 case WhitePromotionKing:
\r
3872 case BlackPromotionKing:
\r
3873 case WhitePromotionChancellor:
\r
3874 case BlackPromotionChancellor:
\r
3875 case WhitePromotionArchbishop:
\r
3876 case BlackPromotionArchbishop:
\r
3877 if(gameInfo.variant == VariantShatranj)
\r
3878 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3879 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3880 PieceToChar(WhiteFerz));
\r
3882 sprintf(user_move, "%c%c%c%c=%c\n",
\r
3883 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
3884 PieceToChar(PromoPiece(moveType)));
\r
3888 sprintf(user_move, "%c@%c%c\n",
\r
3889 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
3890 AAA + toX, ONE + toY);
\r
3893 case WhiteCapturesEnPassant:
\r
3894 case BlackCapturesEnPassant:
\r
3895 case IllegalMove: /* could be a variant we don't quite understand */
\r
3896 sprintf(user_move, "%c%c%c%c\n",
\r
3897 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
3900 SendToICS(user_move);
\r
3904 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
3905 int rf, ff, rt, ft;
\r
3909 if (rf == DROP_RANK) {
\r
3910 sprintf(move, "%c@%c%c\n",
\r
3911 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
3913 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
3914 sprintf(move, "%c%c%c%c\n",
\r
3915 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
3917 sprintf(move, "%c%c%c%c%c\n",
\r
3918 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
3924 ProcessICSInitScript(f)
\r
3927 char buf[MSG_SIZ];
\r
3929 while (fgets(buf, MSG_SIZ, f)) {
\r
3930 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
3937 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
3939 AlphaRank(char *move, int n)
\r
3941 char *p = move, c; int x, y;
\r
3943 if (appData.debugMode) {
\r
3944 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
3947 if(move[1]=='*' &&
\r
3948 move[2]>='0' && move[2]<='9' &&
\r
3949 move[3]>='a' && move[3]<='x' ) {
\r
3951 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3952 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3954 if(move[0]>='0' && move[0]<='9' &&
\r
3955 move[1]>='a' && move[1]<='x' &&
\r
3956 move[2]>='0' && move[2]<='9' &&
\r
3957 move[3]>='a' && move[3]<='x' ) {
\r
3958 /* input move, Shogi -> normal */
\r
3959 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
3960 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
3961 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
3962 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
3964 if(move[1]=='@' &&
\r
3965 move[3]>='0' && move[3]<='9' &&
\r
3966 move[2]>='a' && move[2]<='x' ) {
\r
3968 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3969 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3972 move[0]>='a' && move[0]<='x' &&
\r
3973 move[3]>='0' && move[3]<='9' &&
\r
3974 move[2]>='a' && move[2]<='x' ) {
\r
3975 /* output move, normal -> Shogi */
\r
3976 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
3977 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
3978 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
3979 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
3980 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
3982 if (appData.debugMode) {
\r
3983 fprintf(debugFP, " out = '%s'\n", move);
\r
3987 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
3989 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
3992 ChessMove *moveType;
\r
3993 int *fromX, *fromY, *toX, *toY;
\r
3996 if (appData.debugMode) {
\r
3997 fprintf(debugFP, "move to parse: %s\n", move);
\r
3999 *moveType = yylexstr(moveNum, move);
\r
4001 switch (*moveType) {
\r
4002 case WhitePromotionChancellor:
\r
4003 case BlackPromotionChancellor:
\r
4004 case WhitePromotionArchbishop:
\r
4005 case BlackPromotionArchbishop:
\r
4006 case WhitePromotionQueen:
\r
4007 case BlackPromotionQueen:
\r
4008 case WhitePromotionRook:
\r
4009 case BlackPromotionRook:
\r
4010 case WhitePromotionBishop:
\r
4011 case BlackPromotionBishop:
\r
4012 case WhitePromotionKnight:
\r
4013 case BlackPromotionKnight:
\r
4014 case WhitePromotionKing:
\r
4015 case BlackPromotionKing:
\r
4017 case WhiteCapturesEnPassant:
\r
4018 case BlackCapturesEnPassant:
\r
4019 case WhiteKingSideCastle:
\r
4020 case WhiteQueenSideCastle:
\r
4021 case BlackKingSideCastle:
\r
4022 case BlackQueenSideCastle:
\r
4023 case WhiteKingSideCastleWild:
\r
4024 case WhiteQueenSideCastleWild:
\r
4025 case BlackKingSideCastleWild:
\r
4026 case BlackQueenSideCastleWild:
\r
4027 /* Code added by Tord: */
\r
4028 case WhiteHSideCastleFR:
\r
4029 case WhiteASideCastleFR:
\r
4030 case BlackHSideCastleFR:
\r
4031 case BlackASideCastleFR:
\r
4032 /* End of code added by Tord */
\r
4033 case IllegalMove: /* bug or odd chess variant */
\r
4034 *fromX = currentMoveString[0] - AAA;
\r
4035 *fromY = currentMoveString[1] - ONE;
\r
4036 *toX = currentMoveString[2] - AAA;
\r
4037 *toY = currentMoveString[3] - ONE;
\r
4038 *promoChar = currentMoveString[4];
\r
4039 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4040 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4041 if (appData.debugMode) {
\r
4042 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4044 *fromX = *fromY = *toX = *toY = 0;
\r
4047 if (appData.testLegality) {
\r
4048 return (*moveType != IllegalMove);
\r
4050 return !(fromX == fromY && toX == toY);
\r
4055 *fromX = *moveType == WhiteDrop ?
\r
4056 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4057 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4058 *fromY = DROP_RANK;
\r
4059 *toX = currentMoveString[2] - AAA;
\r
4060 *toY = currentMoveString[3] - ONE;
\r
4061 *promoChar = NULLCHAR;
\r
4064 case AmbiguousMove:
\r
4065 case ImpossibleMove:
\r
4066 case (ChessMove) 0: /* end of file */
\r
4075 if (appData.debugMode) {
\r
4076 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4079 *fromX = *fromY = *toX = *toY = 0;
\r
4080 *promoChar = NULLCHAR;
\r
4085 /* [AS] FRC game initialization */
\r
4086 static int FindEmptySquare( Board board, int n )
\r
4091 while( board[0][i] != EmptySquare ) i++;
\r
4102 static void ShuffleFRC( Board board )
\r
4108 for( i=0; i<8; i++ ) {
\r
4109 board[0][i] = EmptySquare;
\r
4112 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4113 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4114 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4115 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4116 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4117 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4118 initialRights[1] = initialRights[4] =
\r
4119 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4120 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4121 initialRights[2] = initialRights[5] =
\r
4122 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4123 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4124 initialRights[0] = initialRights[3] =
\r
4125 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4127 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4128 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4132 static unsigned char FRC_KnightTable[10] = {
\r
4133 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4136 static void SetupFRC( Board board, int pos_index )
\r
4139 unsigned char knights;
\r
4141 /* Bring the position index into a safe range (just in case...) */
\r
4142 if( pos_index < 0 ) pos_index = 0;
\r
4146 /* Clear the board */
\r
4147 for( i=0; i<8; i++ ) {
\r
4148 board[0][i] = EmptySquare;
\r
4151 /* Place bishops and queen */
\r
4152 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4155 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4158 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4161 /* Place knigths */
\r
4162 knights = FRC_KnightTable[ pos_index ];
\r
4164 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4165 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4167 /* Place rooks and king */
\r
4168 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4169 initialRights[1] = initialRights[4] =
\r
4170 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4171 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4172 initialRights[2] = initialRights[5] =
\r
4173 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4174 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4175 initialRights[0] = initialRights[3] =
\r
4176 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4178 /* Mirror piece placement for black */
\r
4179 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4180 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4184 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4185 // All positions will have equal probability, but the current method will not provide a unique
\r
4186 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4191 int squaresLeft[4];
\r
4192 int piecesLeft[(int)BlackPawn];
\r
4193 long long int seed, nrOfShuffles;
\r
4195 void GetPositionNumber()
\r
4196 { // sets global variable seed
\r
4199 seed = appData.defaultFrcPosition;
\r
4200 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4201 srandom(time(0));
\r
4202 for(i=0; i<50; i++) seed += random();
\r
4203 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4204 if(seed<0) seed = -seed;
\r
4208 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4209 // put the piece on the (n-1)-th empty squares of the given shade
\r
4213 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4214 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4215 board[rank][i] = (ChessSquare) pieceType;
\r
4216 squaresLeft[(i-BOARD_LEFT&1) + 1]--;
\r
4217 squaresLeft[ANY]--;
\r
4218 piecesLeft[pieceType]--;
\r
4226 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4227 // calculate where the next piece goes, (any empty square), and put it there
\r
4231 i = seed % squaresLeft[shade];
\r
4232 nrOfShuffles *= squaresLeft[shade];
\r
4233 seed /= squaresLeft[shade];
\r
4234 put(board, pieceType, rank, i, shade);
\r
4237 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4238 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4240 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4242 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4243 i = seed % k; // pick one
\r
4244 nrOfShuffles *= k;
\r
4246 while(i >= j) i -= j--;
\r
4247 j = n - 1 - j; i += j;
\r
4248 put(board, pieceType, rank, j, ANY);
\r
4249 put(board, pieceType, rank, i, ANY);
\r
4252 void SetUpShuffle(Board board, int number)
\r
4254 int i, p, first=1;
\r
4256 GetPositionNumber(); nrOfShuffles = 1;
\r
4258 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4259 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4260 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4262 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4264 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4265 p = (int) board[0][i];
\r
4266 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4267 board[0][i] = EmptySquare;
\r
4270 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4271 // shuffles restricted to allow normal castling put KRR first
\r
4272 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4273 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4274 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4275 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4276 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4277 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4278 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4279 put(board, WhiteRook, 0, 0, ANY);
\r
4280 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4283 if((BOARD_RGHT-BOARD_LEFT & 1) == 0)
\r
4284 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4285 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4286 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4287 while(piecesLeft[p] >= 2) {
\r
4288 AddOnePiece(board, p, 0, LITE);
\r
4289 AddOnePiece(board, p, 0, DARK);
\r
4291 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4294 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4295 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4296 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4297 if(p == (int)WhiteRook) continue;
\r
4298 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4299 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4302 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4304 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4305 // Last King gets castling rights
\r
4306 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4307 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4308 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4311 while(piecesLeft[(int)WhiteKing]) {
\r
4312 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4313 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4318 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4319 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4322 // Only Rooks can be left; simply place them all
\r
4323 while(piecesLeft[(int)WhiteRook]) {
\r
4324 i = put(board, WhiteRook, 0, 0, ANY);
\r
4325 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4328 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4330 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4333 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4334 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4337 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4342 int SetCharTable( char *table, const char * map )
\r
4343 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4344 /* Basically a safe strcpy that uses the last character as King */
\r
4346 int result = FALSE; int NrPieces;
\r
4348 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4349 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4350 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4352 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4353 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4354 table[i] = map[i];
\r
4355 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4357 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4358 table[(int) BlackKing] = map[NrPieces-1];
\r
4366 void Prelude(Board board)
\r
4367 { // [HGM] superchess: random selection of exo-pieces
\r
4368 int i, j, k; ChessSquare p;
\r
4369 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4371 GetPositionNumber(); // use FRC position number
\r
4373 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4374 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4375 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4376 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4379 j = seed%4; seed /= 4;
\r
4380 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4381 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4382 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4383 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4384 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4385 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4386 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4387 j = seed%3; seed /= 3;
\r
4388 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4389 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4390 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4391 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4392 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4393 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4394 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4395 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4396 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4397 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4398 put(board, exoPieces[0], 0, 0, ANY);
\r
4399 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4403 InitPosition(redraw)
\r
4406 ChessSquare (* pieces)[BOARD_SIZE];
\r
4407 int i, j, pawnRow, overrule,
\r
4408 oldx = gameInfo.boardWidth,
\r
4409 oldy = gameInfo.boardHeight,
\r
4410 oldh = gameInfo.holdingsWidth,
\r
4411 oldv = gameInfo.variant;
\r
4413 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4414 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4416 /* [AS] Initialize pv info list [HGM] and game status */
\r
4418 for( i=0; i<MAX_MOVES; i++ ) {
\r
4419 pvInfoList[i].depth = 0;
\r
4420 epStatus[i]=EP_NONE;
\r
4421 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4424 initialRulePlies = 0; /* 50-move counter start */
\r
4426 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4427 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4431 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4432 /* the initialized data only consist of the two backranks. The switch */
\r
4433 /* selects which one we will use, which is than copied to the Board */
\r
4434 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4435 /* empty squares. This initial position is then copied to boards[0], */
\r
4436 /* possibly after shuffling, so that it remains available. */
\r
4438 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4439 gameInfo.boardWidth = 8;
\r
4440 gameInfo.boardHeight = 8;
\r
4441 gameInfo.holdingsSize = 0;
\r
4442 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4443 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4444 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4446 switch (gameInfo.variant) {
\r
4447 case VariantFischeRandom:
\r
4448 shuffleOpenings = TRUE;
\r
4450 pieces = FIDEArray;
\r
4452 case VariantShatranj:
\r
4453 pieces = ShatranjArray;
\r
4454 nrCastlingRights = 0;
\r
4455 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4457 case VariantTwoKings:
\r
4458 pieces = twoKingsArray;
\r
4459 nrCastlingRights = 8; /* add rights for second King */
\r
4460 castlingRights[0][6] = initialRights[2] = 5;
\r
4461 castlingRights[0][7] = initialRights[5] = 5;
\r
4462 castlingRank[6] = 0;
\r
4463 castlingRank[7] = BOARD_HEIGHT-1;
\r
4465 case VariantCapaRandom:
\r
4466 shuffleOpenings = TRUE;
\r
4467 case VariantCapablanca:
\r
4468 pieces = CapablancaArray;
\r
4469 gameInfo.boardWidth = 10;
\r
4470 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4472 case VariantGothic:
\r
4473 pieces = GothicArray;
\r
4474 gameInfo.boardWidth = 10;
\r
4475 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4477 case VariantJanus:
\r
4478 pieces = JanusArray;
\r
4479 gameInfo.boardWidth = 10;
\r
4480 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4481 nrCastlingRights = 6;
\r
4482 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4483 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4484 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;
\r
4485 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4486 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4487 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;
\r
4489 case VariantFalcon:
\r
4490 pieces = FalconArray;
\r
4491 gameInfo.boardWidth = 10;
\r
4492 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4494 case VariantXiangqi:
\r
4495 pieces = XiangqiArray;
\r
4496 gameInfo.boardWidth = 9;
\r
4497 gameInfo.boardHeight = 10;
\r
4498 nrCastlingRights = 0;
\r
4499 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4501 case VariantShogi:
\r
4502 pieces = ShogiArray;
\r
4503 gameInfo.boardWidth = 9;
\r
4504 gameInfo.boardHeight = 9;
\r
4505 gameInfo.holdingsSize = 7;
\r
4506 nrCastlingRights = 0;
\r
4507 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4509 case VariantCourier:
\r
4510 pieces = CourierArray;
\r
4511 gameInfo.boardWidth = 12;
\r
4512 nrCastlingRights = 0;
\r
4513 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4514 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4516 case VariantKnightmate:
\r
4517 pieces = KnightmateArray;
\r
4518 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4520 case VariantFairy:
\r
4521 pieces = fairyArray;
\r
4522 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4524 case VariantSuper:
\r
4525 pieces = FIDEArray;
\r
4526 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4527 gameInfo.holdingsSize = 8;
\r
4528 startedFromSetupPosition = TRUE;
\r
4530 case VariantCrazyhouse:
\r
4531 case VariantBughouse:
\r
4532 pieces = FIDEArray;
\r
4533 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4534 gameInfo.holdingsSize = 5;
\r
4536 case VariantWildCastle:
\r
4537 pieces = FIDEArray;
\r
4538 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4539 shuffleOpenings = 1;
\r
4541 case VariantNoCastle:
\r
4542 pieces = FIDEArray;
\r
4543 nrCastlingRights = 0;
\r
4544 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4545 /* !!?unconstrained back-rank shuffle */
\r
4546 shuffleOpenings = 1;
\r
4551 if(appData.NrFiles >= 0) {
\r
4552 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4553 gameInfo.boardWidth = appData.NrFiles;
\r
4555 if(appData.NrRanks >= 0) {
\r
4556 gameInfo.boardHeight = appData.NrRanks;
\r
4558 if(appData.holdingsSize >= 0) {
\r
4559 i = appData.holdingsSize;
\r
4560 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4561 gameInfo.holdingsSize = i;
\r
4563 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4564 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4565 DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);
\r
4567 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4568 if(pawnRow < 1) pawnRow = 1;
\r
4570 /* User pieceToChar list overrules defaults */
\r
4571 if(appData.pieceToCharTable != NULL)
\r
4572 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4574 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4576 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4577 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4578 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4579 initialPosition[i][j] = s;
\r
4581 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4582 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4583 initialPosition[pawnRow][j] = WhitePawn;
\r
4584 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4585 if(gameInfo.variant == VariantXiangqi) {
\r
4587 initialPosition[pawnRow][j] =
\r
4588 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4589 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4590 initialPosition[2][j] = WhiteCannon;
\r
4591 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4595 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4597 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4600 initialPosition[1][j] = WhiteBishop;
\r
4601 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4603 initialPosition[1][j] = WhiteRook;
\r
4604 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4607 if( nrCastlingRights == -1) {
\r
4608 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4609 /* This sets default castling rights from none to normal corners */
\r
4610 /* Variants with other castling rights must set them themselves above */
\r
4611 nrCastlingRights = 6;
\r
4613 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4614 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4615 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4616 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4617 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4618 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4621 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4623 if(gameInfo.variant == VariantFischeRandom) {
\r
4624 if( appData.defaultFrcPosition < 0 ) {
\r
4625 ShuffleFRC( initialPosition );
\r
4628 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4630 startedFromSetupPosition = TRUE;
\r
4633 if (appData.debugMode) {
\r
4634 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4636 if(shuffleOpenings) {
\r
4637 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4638 startedFromSetupPosition = TRUE;
\r
4641 if(startedFromPositionFile) {
\r
4642 /* [HGM] loadPos: use PositionFile for every new game */
\r
4643 CopyBoard(initialPosition, filePosition);
\r
4644 for(i=0; i<nrCastlingRights; i++)
\r
4645 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4646 startedFromSetupPosition = TRUE;
\r
4649 CopyBoard(boards[0], initialPosition);
\r
4651 if(oldx != gameInfo.boardWidth ||
\r
4652 oldy != gameInfo.boardHeight ||
\r
4653 oldh != gameInfo.holdingsWidth
\r
4655 || oldv == VariantGothic || // For licensing popups
\r
4656 gameInfo.variant == VariantGothic
\r
4659 || oldv == VariantFalcon ||
\r
4660 gameInfo.variant == VariantFalcon
\r
4663 InitDrawingSizes(-2 ,0);
\r
4666 DrawPosition(TRUE, boards[currentMove]);
\r
4670 SendBoard(cps, moveNum)
\r
4671 ChessProgramState *cps;
\r
4674 char message[MSG_SIZ];
\r
4676 if (cps->useSetboard) {
\r
4677 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4678 sprintf(message, "setboard %s\n", fen);
\r
4679 SendToProgram(message, cps);
\r
4685 /* Kludge to set black to move, avoiding the troublesome and now
\r
4686 * deprecated "black" command.
\r
4688 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4690 SendToProgram("edit\n", cps);
\r
4691 SendToProgram("#\n", cps);
\r
4692 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4693 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4694 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4695 if ((int) *bp < (int) BlackPawn) {
\r
4696 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4697 AAA + j, ONE + i);
\r
4698 if(message[0] == '+' || message[0] == '~') {
\r
4699 sprintf(message, "%c%c%c+\n",
\r
4700 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4701 AAA + j, ONE + i);
\r
4703 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4704 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4705 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4707 SendToProgram(message, cps);
\r
4712 SendToProgram("c\n", cps);
\r
4713 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4714 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4715 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4716 if (((int) *bp != (int) EmptySquare)
\r
4717 && ((int) *bp >= (int) BlackPawn)) {
\r
4718 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4719 AAA + j, ONE + i);
\r
4720 if(message[0] == '+' || message[0] == '~') {
\r
4721 sprintf(message, "%c%c%c+\n",
\r
4722 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4723 AAA + j, ONE + i);
\r
4725 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4726 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4727 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4729 SendToProgram(message, cps);
\r
4734 SendToProgram(".\n", cps);
\r
4736 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4740 IsPromotion(fromX, fromY, toX, toY)
\r
4741 int fromX, fromY, toX, toY;
\r
4743 /* [HGM] add Shogi promotions */
\r
4744 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4745 ChessSquare piece;
\r
4747 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4748 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4749 /* [HGM] Note to self: line above also weeds out drops */
\r
4750 piece = boards[currentMove][fromY][fromX];
\r
4751 if(gameInfo.variant == VariantShogi) {
\r
4752 promotionZoneSize = 3;
\r
4753 highestPromotingPiece = (int)WhiteKing;
\r
4754 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4755 and if in normal chess we then allow promotion to King, why not
\r
4756 allow promotion of other piece in Shogi? */
\r
4758 if((int)piece >= BlackPawn) {
\r
4759 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4761 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4763 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4764 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4766 return ( (int)piece <= highestPromotingPiece );
\r
4770 InPalace(row, column)
\r
4772 { /* [HGM] for Xiangqi */
\r
4773 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4774 column < (BOARD_WIDTH + 4)/2 &&
\r
4775 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4780 PieceForSquare (x, y)
\r
4784 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4787 return boards[currentMove][y][x];
\r
4791 OKToStartUserMove(x, y)
\r
4794 ChessSquare from_piece;
\r
4797 if (matchMode) return FALSE;
\r
4798 if (gameMode == EditPosition) return TRUE;
\r
4800 if (x >= 0 && y >= 0)
\r
4801 from_piece = boards[currentMove][y][x];
\r
4803 from_piece = EmptySquare;
\r
4805 if (from_piece == EmptySquare) return FALSE;
\r
4807 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4808 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4810 switch (gameMode) {
\r
4811 case PlayFromGameFile:
\r
4813 case TwoMachinesPlay:
\r
4817 case IcsObserving:
\r
4821 case MachinePlaysWhite:
\r
4822 case IcsPlayingBlack:
\r
4823 if (appData.zippyPlay) return FALSE;
\r
4824 if (white_piece) {
\r
4825 DisplayMoveError("You are playing Black");
\r
4830 case MachinePlaysBlack:
\r
4831 case IcsPlayingWhite:
\r
4832 if (appData.zippyPlay) return FALSE;
\r
4833 if (!white_piece) {
\r
4834 DisplayMoveError("You are playing White");
\r
4840 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4841 DisplayMoveError("It is White's turn");
\r
4844 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4845 DisplayMoveError("It is Black's turn");
\r
4848 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
4849 /* Editing correspondence game history */
\r
4850 /* Could disallow this or prompt for confirmation */
\r
4851 cmailOldMove = -1;
\r
4853 if (currentMove < forwardMostMove) {
\r
4854 /* Discarding moves */
\r
4855 /* Could prompt for confirmation here,
\r
4856 but I don't think that's such a good idea */
\r
4857 forwardMostMove = currentMove;
\r
4861 case BeginningOfGame:
\r
4862 if (appData.icsActive) return FALSE;
\r
4863 if (!appData.noChessProgram) {
\r
4864 if (!white_piece) {
\r
4865 DisplayMoveError("You are playing White");
\r
4872 if (!white_piece && WhiteOnMove(currentMove)) {
\r
4873 DisplayMoveError("It is White's turn");
\r
4876 if (white_piece && !WhiteOnMove(currentMove)) {
\r
4877 DisplayMoveError("It is Black's turn");
\r
4883 case IcsExamining:
\r
4886 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
4887 && gameMode != AnalyzeFile && gameMode != Training) {
\r
4888 DisplayMoveError("Displayed position is not current");
\r
4894 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
4895 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
4896 int lastLoadGameUseList = FALSE;
\r
4897 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
4898 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
4902 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
4903 int fromX, fromY, toX, toY;
\r
4906 ChessMove moveType;
\r
4907 ChessSquare pdown, pup;
\r
4909 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
4910 if ((fromX == toX) && (fromY == toY)) {
\r
4911 return ImpossibleMove;
\r
4914 /* [HGM] suppress all moves into holdings area and guard band */
\r
4915 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
4916 return ImpossibleMove;
\r
4918 /* [HGM] <sameColor> moved to here from winboard.c */
\r
4919 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
4920 pdown = boards[currentMove][fromY][fromX];
\r
4921 pup = boards[currentMove][toY][toX];
\r
4922 if ( gameMode != EditPosition &&
\r
4923 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
4924 WhitePawn <= pup && pup < BlackPawn ||
\r
4925 BlackPawn <= pdown && pdown < EmptySquare &&
\r
4926 BlackPawn <= pup && pup < EmptySquare
\r
4927 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
4928 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
4929 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
4931 return ImpossibleMove;
\r
4933 /* Check if the user is playing in turn. This is complicated because we
\r
4934 let the user "pick up" a piece before it is his turn. So the piece he
\r
4935 tried to pick up may have been captured by the time he puts it down!
\r
4936 Therefore we use the color the user is supposed to be playing in this
\r
4937 test, not the color of the piece that is currently on the starting
\r
4938 square---except in EditGame mode, where the user is playing both
\r
4939 sides; fortunately there the capture race can't happen. (It can
\r
4940 now happen in IcsExamining mode, but that's just too bad. The user
\r
4941 will get a somewhat confusing message in that case.)
\r
4944 switch (gameMode) {
\r
4945 case PlayFromGameFile:
\r
4947 case TwoMachinesPlay:
\r
4949 case IcsObserving:
\r
4951 /* We switched into a game mode where moves are not accepted,
\r
4952 perhaps while the mouse button was down. */
\r
4953 return ImpossibleMove;
\r
4955 case MachinePlaysWhite:
\r
4956 /* User is moving for Black */
\r
4957 if (WhiteOnMove(currentMove)) {
\r
4958 DisplayMoveError("It is White's turn");
\r
4959 return ImpossibleMove;
\r
4963 case MachinePlaysBlack:
\r
4964 /* User is moving for White */
\r
4965 if (!WhiteOnMove(currentMove)) {
\r
4966 DisplayMoveError("It is Black's turn");
\r
4967 return ImpossibleMove;
\r
4972 case IcsExamining:
\r
4973 case BeginningOfGame:
\r
4976 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
4977 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
4978 /* User is moving for Black */
\r
4979 if (WhiteOnMove(currentMove)) {
\r
4980 DisplayMoveError("It is White's turn");
\r
4981 return ImpossibleMove;
\r
4984 /* User is moving for White */
\r
4985 if (!WhiteOnMove(currentMove)) {
\r
4986 DisplayMoveError("It is Black's turn");
\r
4987 return ImpossibleMove;
\r
4992 case IcsPlayingBlack:
\r
4993 /* User is moving for Black */
\r
4994 if (WhiteOnMove(currentMove)) {
\r
4995 if (!appData.premove) {
\r
4996 DisplayMoveError("It is White's turn");
\r
4997 } else if (toX >= 0 && toY >= 0) {
\r
5000 premoveFromX = fromX;
\r
5001 premoveFromY = fromY;
\r
5002 premovePromoChar = promoChar;
\r
5004 if (appData.debugMode)
\r
5005 fprintf(debugFP, "Got premove: fromX %d,"
\r
5006 "fromY %d, toX %d, toY %d\n",
\r
5007 fromX, fromY, toX, toY);
\r
5009 return ImpossibleMove;
\r
5013 case IcsPlayingWhite:
\r
5014 /* User is moving for White */
\r
5015 if (!WhiteOnMove(currentMove)) {
\r
5016 if (!appData.premove) {
\r
5017 DisplayMoveError("It is Black's turn");
\r
5018 } else if (toX >= 0 && toY >= 0) {
\r
5021 premoveFromX = fromX;
\r
5022 premoveFromY = fromY;
\r
5023 premovePromoChar = promoChar;
\r
5025 if (appData.debugMode)
\r
5026 fprintf(debugFP, "Got premove: fromX %d,"
\r
5027 "fromY %d, toX %d, toY %d\n",
\r
5028 fromX, fromY, toX, toY);
\r
5030 return ImpossibleMove;
\r
5037 case EditPosition:
\r
5038 /* EditPosition, empty square, or different color piece;
\r
5039 click-click move is possible */
\r
5040 if (toX == -2 || toY == -2) {
\r
5041 boards[0][fromY][fromX] = EmptySquare;
\r
5042 return AmbiguousMove;
\r
5043 } else if (toX >= 0 && toY >= 0) {
\r
5044 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5045 boards[0][fromY][fromX] = EmptySquare;
\r
5046 return AmbiguousMove;
\r
5048 return ImpossibleMove;
\r
5051 /* [HGM] If move started in holdings, it means a drop */
\r
5052 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5053 if( pup != EmptySquare ) return ImpossibleMove;
\r
5054 if(appData.testLegality) {
\r
5055 /* it would be more logical if LegalityTest() also figured out
\r
5056 * which drops are legal. For now we forbid pawns on back rank.
\r
5057 * Shogi is on its own here...
\r
5059 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5060 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5061 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5063 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5066 userOfferedDraw = FALSE;
\r
5068 /* [HGM] always test for legality, to get promotion info */
\r
5069 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5070 epStatus[currentMove], castlingRights[currentMove],
\r
5071 fromY, fromX, toY, toX, promoChar);
\r
5073 /* [HGM] but possibly ignore an IllegalMove result */
\r
5074 if (appData.testLegality) {
\r
5075 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5076 DisplayMoveError("Illegal move");
\r
5077 return ImpossibleMove;
\r
5082 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5083 function is made into one that returns an OK move type if FinishMove
\r
5084 should be called. This to give the calling driver routine the
\r
5085 opportunity to finish the userMove input with a promotion popup,
\r
5086 without bothering the user with this for invalid or illegal moves */
\r
5088 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5091 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5093 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5094 ChessMove moveType;
\r
5095 int fromX, fromY, toX, toY;
\r
5096 /*char*/int promoChar;
\r
5098 char *bookHit = 0;
\r
5100 if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR) {
\r
5101 // [HGM] superchess: suppress promotions to non-available piece
\r
5102 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5103 if(WhiteOnMove(currentMove)) {
\r
5104 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5106 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5110 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5111 move type in caller when we know the move is a legal promotion */
\r
5112 if(moveType == NormalMove && promoChar)
\r
5113 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5115 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5116 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5117 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5118 fromX = boards[currentMove][fromY][fromX];
\r
5119 fromY = DROP_RANK;
\r
5122 /* [HGM] <popupFix> The following if has been moved here from
\r
5123 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5124 piece drops in training games?), and because it can only be
\r
5125 performed after it is known to what we promote. */
\r
5126 if (gameMode == Training) {
\r
5127 /* compare the move played on the board to the next move in the
\r
5128 * game. If they match, display the move and the opponent's response.
\r
5129 * If they don't match, display an error message.
\r
5133 CopyBoard(testBoard, boards[currentMove]);
\r
5134 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5136 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5137 ForwardInner(currentMove+1);
\r
5139 /* Autoplay the opponent's response.
\r
5140 * if appData.animate was TRUE when Training mode was entered,
\r
5141 * the response will be animated.
\r
5143 saveAnimate = appData.animate;
\r
5144 appData.animate = animateTraining;
\r
5145 ForwardInner(currentMove+1);
\r
5146 appData.animate = saveAnimate;
\r
5148 /* check for the end of the game */
\r
5149 if (currentMove >= forwardMostMove) {
\r
5150 gameMode = PlayFromGameFile;
\r
5152 SetTrainingModeOff();
\r
5153 DisplayInformation("End of game");
\r
5156 DisplayError("Incorrect move", 0);
\r
5161 /* Ok, now we know that the move is good, so we can kill
\r
5162 the previous line in Analysis Mode */
\r
5163 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5164 forwardMostMove = currentMove;
\r
5167 /* If we need the chess program but it's dead, restart it */
\r
5168 ResurrectChessProgram();
\r
5170 /* A user move restarts a paused game*/
\r
5174 thinkOutput[0] = NULLCHAR;
\r
5176 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5178 if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5179 // [HGM] superchess: take promotion piece out of holdings
\r
5180 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5181 if(WhiteOnMove(forwardMostMove-1)) {
\r
5182 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5183 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5185 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5186 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5190 if (gameMode == BeginningOfGame) {
\r
5191 if (appData.noChessProgram) {
\r
5192 gameMode = EditGame;
\r
5195 char buf[MSG_SIZ];
\r
5196 gameMode = MachinePlaysBlack;
\r
5199 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5200 DisplayTitle(buf);
\r
5201 if (first.sendName) {
\r
5202 sprintf(buf, "name %s\n", gameInfo.white);
\r
5203 SendToProgram(buf, &first);
\r
5210 /* Relay move to ICS or chess engine */
\r
5211 if (appData.icsActive) {
\r
5212 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5213 gameMode == IcsExamining) {
\r
5214 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5215 ics_user_moved = 1;
\r
5218 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5219 gameMode == MachinePlaysWhite ||
\r
5220 gameMode == MachinePlaysBlack)) {
\r
5221 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5223 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5224 // [HGM] book: if program might be playing, let it use book
\r
5225 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5226 first.maybeThinking = TRUE;
\r
5227 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5228 if (currentMove == cmailOldMove + 1) {
\r
5229 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5233 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5235 switch (gameMode) {
\r
5237 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5238 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5242 case MT_CHECKMATE:
\r
5243 if (WhiteOnMove(currentMove)) {
\r
5244 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5246 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5249 case MT_STALEMATE:
\r
5250 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5255 case MachinePlaysBlack:
\r
5256 case MachinePlaysWhite:
\r
5257 /* disable certain menu options while machine is thinking */
\r
5258 SetMachineThinkingEnables();
\r
5265 if(bookHit) { // [HGM] book: simulate book reply
\r
5266 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5268 programStats.depth = programStats.nodes = programStats.time =
\r
5269 programStats.score = programStats.got_only_move = 0;
\r
5270 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
5272 strcpy(bookMove, "move ");
\r
5273 strcat(bookMove, bookHit);
\r
5274 HandleMachineMove(bookMove, &first);
\r
5280 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5281 int fromX, fromY, toX, toY;
\r
5284 /* [HGM] This routine was added to allow calling of its two logical
\r
5285 parts from other modules in the old way. Before, UserMoveEvent()
\r
5286 automatically called FinishMove() if the move was OK, and returned
\r
5287 otherwise. I separated the two, in order to make it possible to
\r
5288 slip a promotion popup in between. But that it always needs two
\r
5289 calls, to the first part, (now called UserMoveTest() ), and to
\r
5290 FinishMove if the first part succeeded. Calls that do not need
\r
5291 to do anything in between, can call this routine the old way.
\r
5293 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5295 if(moveType != ImpossibleMove)
\r
5296 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5299 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5301 char * hint = lastHint;
\r
5302 FrontEndProgramStats stats;
\r
5304 stats.which = cps == &first ? 0 : 1;
\r
5305 stats.depth = cpstats->depth;
\r
5306 stats.nodes = cpstats->nodes;
\r
5307 stats.score = cpstats->score;
\r
5308 stats.time = cpstats->time;
\r
5309 stats.pv = cpstats->movelist;
\r
5310 stats.hint = lastHint;
\r
5311 stats.an_move_index = 0;
\r
5312 stats.an_move_count = 0;
\r
5314 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5315 stats.hint = cpstats->move_name;
\r
5316 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5317 stats.an_move_count = cpstats->nr_moves;
\r
5320 SetProgramStats( &stats );
\r
5323 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5324 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5325 char *bookHit = NULL;
\r
5327 //first determine if the incoming move brings opponent into his book
\r
5328 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5329 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5330 if(appData.debugMode && bookHit) fprintf(debugFP, "book hit = %s\n", bookHit);
\r
5331 if(bookHit != NULL && !cps->bookSuspend) {
\r
5332 // make sure opponent is not going to reply after receiving move to book position
\r
5333 SendToProgram("force\n", cps);
\r
5334 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5336 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5337 // now arrange restart after book miss
\r
5339 // after a book hit we never send 'go', and the code after the call to this routine
\r
5340 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5341 char buf[MSG_SIZ];
\r
5342 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5343 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5344 SendToProgram(buf, cps);
\r
5345 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5346 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5347 SendToProgram("go\n", cps);
\r
5348 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5349 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5350 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5351 SendToProgram("go\n", cps);
\r
5352 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5354 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5357 char *savedMessage;
\r
5358 ChessProgramState *savedState;
\r
5359 void DeferredBookMove(void)
\r
5361 if(savedState->lastPing != savedState->lastPong)
\r
5362 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5364 HandleMachineMove(savedMessage, savedState);
\r
5368 HandleMachineMove(message, cps)
\r
5370 ChessProgramState *cps;
\r
5372 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5373 char realname[MSG_SIZ];
\r
5374 int fromX, fromY, toX, toY;
\r
5375 ChessMove moveType;
\r
5381 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5383 * Kludge to ignore BEL characters
\r
5385 while (*message == '\007') message++;
\r
5388 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5390 if(cps->debug && *message == '#') return;
\r
5393 * Look for book output
\r
5395 if (cps == &first && bookRequested) {
\r
5396 if (message[0] == '\t' || message[0] == ' ') {
\r
5397 /* Part of the book output is here; append it */
\r
5398 strcat(bookOutput, message);
\r
5399 strcat(bookOutput, " \n");
\r
5401 } else if (bookOutput[0] != NULLCHAR) {
\r
5402 /* All of book output has arrived; display it */
\r
5403 char *p = bookOutput;
\r
5404 while (*p != NULLCHAR) {
\r
5405 if (*p == '\t') *p = ' ';
\r
5408 DisplayInformation(bookOutput);
\r
5409 bookRequested = FALSE;
\r
5410 /* Fall through to parse the current output */
\r
5415 * Look for machine move.
\r
5417 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5418 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5420 /* This method is only useful on engines that support ping */
\r
5421 if (cps->lastPing != cps->lastPong) {
\r
5422 if (gameMode == BeginningOfGame) {
\r
5423 /* Extra move from before last new; ignore */
\r
5424 if (appData.debugMode) {
\r
5425 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5428 if (appData.debugMode) {
\r
5429 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5430 cps->which, gameMode);
\r
5433 SendToProgram("undo\n", cps);
\r
5438 switch (gameMode) {
\r
5439 case BeginningOfGame:
\r
5440 /* Extra move from before last reset; ignore */
\r
5441 if (appData.debugMode) {
\r
5442 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5449 /* Extra move after we tried to stop. The mode test is
\r
5450 not a reliable way of detecting this problem, but it's
\r
5451 the best we can do on engines that don't support ping.
\r
5453 if (appData.debugMode) {
\r
5454 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5455 cps->which, gameMode);
\r
5457 SendToProgram("undo\n", cps);
\r
5460 case MachinePlaysWhite:
\r
5461 case IcsPlayingWhite:
\r
5462 machineWhite = TRUE;
\r
5465 case MachinePlaysBlack:
\r
5466 case IcsPlayingBlack:
\r
5467 machineWhite = FALSE;
\r
5470 case TwoMachinesPlay:
\r
5471 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5474 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5475 if (appData.debugMode) {
\r
5477 "Ignoring move out of turn by %s, gameMode %d"
\r
5478 ", forwardMost %d\n",
\r
5479 cps->which, gameMode, forwardMostMove);
\r
5484 if (appData.debugMode) { int f = forwardMostMove;
\r
5485 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5486 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5488 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5489 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5490 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5491 /* Machine move could not be parsed; ignore it. */
\r
5492 sprintf(buf1, "Illegal move \"%s\" from %s machine",
\r
5493 machineMove, cps->which);
\r
5494 DisplayError(buf1, 0);
\r
5495 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5496 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5497 if (gameMode == TwoMachinesPlay) {
\r
5498 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5504 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5505 /* So we have to redo legality test with true e.p. status here, */
\r
5506 /* to make sure an illegal e.p. capture does not slip through, */
\r
5507 /* to cause a forfeit on a justified illegal-move complaint */
\r
5508 /* of the opponent. */
\r
5509 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5510 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5512 ChessMove moveType;
\r
5513 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5514 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5515 fromY, fromX, toY, toX, promoChar);
\r
5516 if (appData.debugMode) {
\r
5518 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5519 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5520 fprintf(debugFP, "castling rights\n");
\r
5522 if(moveType == IllegalMove) {
\r
5523 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5524 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5525 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5527 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5528 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5529 when they shouldn't (like TSCP-Gothic) */
\r
5530 switch(moveType) {
\r
5531 case WhiteASideCastleFR:
\r
5532 case BlackASideCastleFR:
\r
5534 currentMoveString[2]++;
\r
5536 case WhiteHSideCastleFR:
\r
5537 case BlackHSideCastleFR:
\r
5539 currentMoveString[2]--;
\r
5543 hintRequested = FALSE;
\r
5544 lastHint[0] = NULLCHAR;
\r
5545 bookRequested = FALSE;
\r
5546 /* Program may be pondering now */
\r
5547 cps->maybeThinking = TRUE;
\r
5548 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5549 if (cps->offeredDraw) cps->offeredDraw--;
\r
5552 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5554 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5555 ics_user_moved = 1;
\r
5556 if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5557 char buf[3*MSG_SIZ];
\r
5559 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5560 programStats.depth,
\r
5561 programStats.score / 100.,
\r
5562 programStats.time / 100.,
\r
5563 (double) programStats.nodes,
\r
5564 programStats.nodes / (10*abs(programStats.time) + 1.),
\r
5565 programStats.movelist);
\r
5570 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5571 strcpy(machineMove, currentMoveString);
\r
5572 strcat(machineMove, "\n");
\r
5573 strcpy(moveList[forwardMostMove], machineMove);
\r
5575 /* [AS] Save move info and clear stats for next move */
\r
5576 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5577 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5578 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5579 ClearProgramStats();
\r
5580 thinkOutput[0] = NULLCHAR;
\r
5581 hiddenThinkOutputState = 0;
\r
5583 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5585 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5586 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5589 while( count < adjudicateLossPlies ) {
\r
5590 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5593 score = -score; /* Flip score for winning side */
\r
5596 if( score > adjudicateLossThreshold ) {
\r
5603 if( count >= adjudicateLossPlies ) {
\r
5604 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5606 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5607 "Xboard adjudication",
\r
5614 if( gameMode == TwoMachinesPlay ) {
\r
5615 // [HGM] some adjudications useful with buggy engines
\r
5616 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5617 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper) {
\r
5619 if(appData.testLegality)
\r
5620 // don't wait for engine to announce game end if we can judge ourselves
\r
5621 switch (MateTest(boards[forwardMostMove],
\r
5622 PosFlags(forwardMostMove), epFile,
\r
5623 castlingRights[forwardMostMove]) ) {
\r
5628 case MT_STALEMATE:
\r
5629 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5630 if(appData.checkMates) {
\r
5631 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5632 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5636 case MT_CHECKMATE:
\r
5637 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5638 if(appData.checkMates) {
\r
5639 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5640 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5641 "Xboard adjudication: Checkmate",
\r
5647 if( appData.testLegality )
\r
5648 { /* [HGM] Some more adjudications for obstinate engines */
\r
5649 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5650 NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,
\r
5651 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5652 static int moveCount = 6;
\r
5654 /* First absolutely insufficient mating material. Count what is on board. */
\r
5655 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5656 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5660 { /* count B,N,R and other of each side */
\r
5664 bishopsColor |= 1 << ((i^j)&1);
\r
5669 bishopsColor |= 1 << ((i^j)&1);
\r
5679 case EmptySquare:
\r
5684 PawnAdvance += m; NrPawns++;
\r
5686 NrPieces += (p != EmptySquare);
\r
5687 NrW += ((int)p < (int)BlackPawn);
\r
5690 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2
\r
5691 || NrPieces == 4 && NrBB+NrWB == NrPieces-2 && bishopsColor != 3)
\r
5692 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5694 /* always flag draws, for judging claims */
\r
5695 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5697 if(appData.materialDraws) {
\r
5698 /* but only adjudicate them if adjudication enabled */
\r
5699 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5700 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5705 /* Shatranj baring rule */
\r
5706 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5709 if(--bare < 0 && appData.checkMates) {
\r
5710 /* but only adjudicate them if adjudication enabled */
\r
5711 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5712 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5713 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5718 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5719 if(NrPieces == 4 &&
\r
5720 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5721 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5722 || NrWN==2 || NrBN==2 /* KNNK */
\r
5723 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5725 if(--moveCount < 0 && appData.trivialDraws)
\r
5726 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5727 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5728 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5731 } else moveCount = 6;
\r
5735 if (appData.debugMode) { int i;
\r
5736 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5737 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5738 appData.drawRepeats);
\r
5739 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5740 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5744 /* Check for rep-draws */
\r
5746 for(k = forwardMostMove-2;
\r
5747 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5748 epStatus[k] < EP_UNKNOWN &&
\r
5749 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5753 if (appData.debugMode) {
\r
5754 fprintf(debugFP, " loop\n");
\r
5757 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5759 if (appData.debugMode) {
\r
5760 fprintf(debugFP, "match\n");
\r
5763 /* compare castling rights */
\r
5764 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5765 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5766 rights++; /* King lost rights, while rook still had them */
\r
5767 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5768 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5769 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5770 rights++; /* but at least one rook lost them */
\r
5772 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5773 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5775 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5776 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5777 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5781 if (appData.debugMode) {
\r
5782 for(i=0; i<nrCastlingRights; i++)
\r
5783 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5786 if (appData.debugMode) {
\r
5787 fprintf(debugFP, " %d %d\n", rights, k);
\r
5790 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5791 && appData.drawRepeats > 1) {
\r
5792 /* adjudicate after user-specified nr of repeats */
\r
5793 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5794 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
5797 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
5798 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
5802 /* Now we test for 50-move draws. Determine ply count */
\r
5803 count = forwardMostMove;
\r
5804 /* look for last irreversble move */
\r
5805 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
5807 /* if we hit starting position, add initial plies */
\r
5808 if( count == backwardMostMove )
\r
5809 count -= initialRulePlies;
\r
5810 count = forwardMostMove - count;
\r
5812 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
5813 /* this is used to judge if draw claims are legal */
\r
5814 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
5815 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5816 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
5820 /* if draw offer is pending, treat it as a draw claim
\r
5821 * when draw condition present, to allow engines a way to
\r
5822 * claim draws before making their move to avoid a race
\r
5823 * condition occurring after their move
\r
5825 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
5827 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
5828 p = "Draw claim: 50-move rule";
\r
5829 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
5830 p = "Draw claim: 3-fold repetition";
\r
5831 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
5832 p = "Draw claim: insufficient mating material";
\r
5834 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
5835 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5843 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
5844 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5846 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
5852 if (gameMode == TwoMachinesPlay) {
\r
5853 /* [HGM] relaying draw offers moved to after reception of move */
\r
5854 /* and interpreting offer as claim if it brings draw condition */
\r
5855 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
5856 SendToProgram("draw\n", cps->other);
\r
5858 if (cps->other->sendTime) {
\r
5859 SendTimeRemaining(cps->other,
\r
5860 cps->other->twoMachinesColor[0] == 'w');
\r
5862 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
5863 if (firstMove && !bookHit) {
\r
5864 firstMove = FALSE;
\r
5865 if (cps->other->useColors) {
\r
5866 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
5868 SendToProgram("go\n", cps->other);
\r
5870 cps->other->maybeThinking = TRUE;
\r
5873 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5875 if (!pausing && appData.ringBellAfterMoves) {
\r
5880 * Reenable menu items that were disabled while
\r
5881 * machine was thinking
\r
5883 if (gameMode != TwoMachinesPlay)
\r
5884 SetUserThinkingEnables();
\r
5886 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
5887 // force the book reply into it, and then fake that it outputted this move by jumping
\r
5888 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
5890 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5892 strcpy(bookMove, "move ");
\r
5893 strcat(bookMove, bookHit);
\r
5894 message = bookMove;
\r
5896 programStats.depth = programStats.nodes = programStats.time =
\r
5897 programStats.score = programStats.got_only_move = 0;
\r
5898 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
5900 if(cps->lastPing != cps->lastPong) {
\r
5901 savedMessage = message; // args for deferred call
\r
5903 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5906 goto FakeBookMove;
\r
5912 /* Set special modes for chess engines. Later something general
\r
5913 * could be added here; for now there is just one kludge feature,
\r
5914 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
5915 * when "xboard" is given as an interactive command.
\r
5917 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
5918 cps->useSigint = FALSE;
\r
5919 cps->useSigterm = FALSE;
\r
5922 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
5923 * want this, I was asked to put it in, and obliged.
\r
5925 if (!strncmp(message, "setboard ", 9)) {
\r
5926 Board initial_position; int i;
\r
5928 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
5930 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
5931 DisplayError("Bad FEN received from engine", 0);
\r
5934 Reset(FALSE, FALSE);
\r
5935 CopyBoard(boards[0], initial_position);
\r
5936 initialRulePlies = FENrulePlies;
\r
5937 epStatus[0] = FENepStatus;
\r
5938 for( i=0; i<nrCastlingRights; i++ )
\r
5939 castlingRights[0][i] = FENcastlingRights[i];
\r
5940 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
5941 else gameMode = MachinePlaysBlack;
\r
5942 DrawPosition(FALSE, boards[currentMove]);
\r
5948 * Look for communication commands
\r
5950 if (!strncmp(message, "telluser ", 9)) {
\r
5951 DisplayNote(message + 9);
\r
5954 if (!strncmp(message, "tellusererror ", 14)) {
\r
5955 DisplayError(message + 14, 0);
\r
5958 if (!strncmp(message, "tellopponent ", 13)) {
\r
5959 if (appData.icsActive) {
\r
5961 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
5965 DisplayNote(message + 13);
\r
5969 if (!strncmp(message, "tellothers ", 11)) {
\r
5970 if (appData.icsActive) {
\r
5972 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
5978 if (!strncmp(message, "tellall ", 8)) {
\r
5979 if (appData.icsActive) {
\r
5981 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
5985 DisplayNote(message + 8);
\r
5989 if (strncmp(message, "warning", 7) == 0) {
\r
5990 /* Undocumented feature, use tellusererror in new code */
\r
5991 DisplayError(message, 0);
\r
5994 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
5995 strcpy(realname, cps->tidy);
\r
5996 strcat(realname, " query");
\r
5997 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6000 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6001 * sent until we are logged on. Crafty kibitzes have been known to
\r
6002 * interfere with the login process.
\r
6005 if (!strncmp(message, "tellics ", 8)) {
\r
6006 SendToICS(message + 8);
\r
6010 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6011 SendToICS(ics_prefix);
\r
6012 SendToICS(message + 15);
\r
6016 /* The following are for backward compatibility only */
\r
6017 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6018 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6019 SendToICS(ics_prefix);
\r
6020 SendToICS(message);
\r
6025 if (strncmp(message, "feature ", 8) == 0) {
\r
6026 ParseFeatures(message+8, cps);
\r
6028 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6032 * If the move is illegal, cancel it and redraw the board.
\r
6033 * Also deal with other error cases. Matching is rather loose
\r
6034 * here to accommodate engines written before the spec.
\r
6036 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6037 strncmp(message, "Error", 5) == 0) {
\r
6038 if (StrStr(message, "name") ||
\r
6039 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6040 StrStr(message, "result") || StrStr(message, "board") ||
\r
6041 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6042 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6043 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6044 StrStr(message, "accepted")) {
\r
6047 if (StrStr(message, "protover")) {
\r
6048 /* Program is responding to input, so it's apparently done
\r
6049 initializing, and this error message indicates it is
\r
6050 protocol version 1. So we don't need to wait any longer
\r
6051 for it to initialize and send feature commands. */
\r
6052 FeatureDone(cps, 1);
\r
6053 cps->protocolVersion = 1;
\r
6056 cps->maybeThinking = FALSE;
\r
6058 if (StrStr(message, "draw")) {
\r
6059 /* Program doesn't have "draw" command */
\r
6060 cps->sendDrawOffers = 0;
\r
6063 if (cps->sendTime != 1 &&
\r
6064 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6065 /* Program apparently doesn't have "time" or "otim" command */
\r
6066 cps->sendTime = 0;
\r
6069 if (StrStr(message, "analyze")) {
\r
6070 cps->analysisSupport = FALSE;
\r
6071 cps->analyzing = FALSE;
\r
6072 Reset(FALSE, TRUE);
\r
6073 sprintf(buf2, "%s does not support analysis", cps->tidy);
\r
6074 DisplayError(buf2, 0);
\r
6077 if (StrStr(message, "(no matching move)st")) {
\r
6078 /* Special kludge for GNU Chess 4 only */
\r
6079 cps->stKludge = TRUE;
\r
6080 SendTimeControl(cps, movesPerSession, timeControl,
\r
6081 timeIncrement, appData.searchDepth,
\r
6085 if (StrStr(message, "(no matching move)sd")) {
\r
6086 /* Special kludge for GNU Chess 4 only */
\r
6087 cps->sdKludge = TRUE;
\r
6088 SendTimeControl(cps, movesPerSession, timeControl,
\r
6089 timeIncrement, appData.searchDepth,
\r
6093 if (!StrStr(message, "llegal")) {
\r
6096 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6097 gameMode == IcsIdle) return;
\r
6098 if (forwardMostMove <= backwardMostMove) return;
\r
6100 /* Following removed: it caused a bug where a real illegal move
\r
6101 message in analyze mored would be ignored. */
\r
6102 if (cps == &first && programStats.ok_to_send == 0) {
\r
6103 /* Bogus message from Crafty responding to "." This filtering
\r
6104 can miss some of the bad messages, but fortunately the bug
\r
6105 is fixed in current Crafty versions, so it doesn't matter. */
\r
6109 if (pausing) PauseEvent();
\r
6110 if (gameMode == PlayFromGameFile) {
\r
6111 /* Stop reading this game file */
\r
6112 gameMode = EditGame;
\r
6115 currentMove = --forwardMostMove;
\r
6116 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6118 DisplayBothClocks();
\r
6119 sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",
\r
6120 parseList[currentMove], cps->which);
\r
6121 DisplayMoveError(buf1);
\r
6122 DrawPosition(FALSE, boards[currentMove]);
\r
6124 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6125 /* only passes fully legal moves */
\r
6126 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6127 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6128 "False illegal-move claim", GE_XBOARD );
\r
6132 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6133 /* Program has a broken "time" command that
\r
6134 outputs a string not ending in newline.
\r
6136 cps->sendTime = 0;
\r
6140 * If chess program startup fails, exit with an error message.
\r
6141 * Attempts to recover here are futile.
\r
6143 if ((StrStr(message, "unknown host") != NULL)
\r
6144 || (StrStr(message, "No remote directory") != NULL)
\r
6145 || (StrStr(message, "not found") != NULL)
\r
6146 || (StrStr(message, "No such file") != NULL)
\r
6147 || (StrStr(message, "can't alloc") != NULL)
\r
6148 || (StrStr(message, "Permission denied") != NULL)) {
\r
6150 cps->maybeThinking = FALSE;
\r
6151 sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",
\r
6152 cps->which, cps->program, cps->host, message);
\r
6153 RemoveInputSource(cps->isr);
\r
6154 DisplayFatalError(buf1, 0, 1);
\r
6159 * Look for hint output
\r
6161 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6162 if (cps == &first && hintRequested) {
\r
6163 hintRequested = FALSE;
\r
6164 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6165 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6166 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6167 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6168 fromY, fromX, toY, toX, promoChar, buf1);
\r
6169 sprintf(buf2, "Hint: %s", buf1);
\r
6170 DisplayInformation(buf2);
\r
6172 /* Hint move could not be parsed!? */
\r
6174 "Illegal hint move \"%s\"\nfrom %s chess program",
\r
6175 buf1, cps->which);
\r
6176 DisplayError(buf2, 0);
\r
6179 strcpy(lastHint, buf1);
\r
6185 * Ignore other messages if game is not in progress
\r
6187 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6188 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6191 * look for win, lose, draw, or draw offer
\r
6193 if (strncmp(message, "1-0", 3) == 0) {
\r
6194 char *p, *q, *r = "";
\r
6195 p = strchr(message, '{');
\r
6197 q = strchr(p, '}');
\r
6203 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6205 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6206 char *p, *q, *r = "";
\r
6207 p = strchr(message, '{');
\r
6209 q = strchr(p, '}');
\r
6215 /* Kludge for Arasan 4.1 bug */
\r
6216 if (strcmp(r, "Black resigns") == 0) {
\r
6217 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6220 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6222 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6223 char *p, *q, *r = "";
\r
6224 p = strchr(message, '{');
\r
6226 q = strchr(p, '}');
\r
6233 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6236 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6237 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6239 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6240 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6242 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6243 strncmp(message, "Black matches", 13) == 0 ) {
\r
6244 /* [HGM] ignore GNUShogi noises */
\r
6246 } else if (strncmp(message, "White", 5) == 0 &&
\r
6247 message[5] != '(' &&
\r
6248 StrStr(message, "Black") == NULL) {
\r
6249 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6251 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6252 message[5] != '(') {
\r
6253 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6255 } else if (strcmp(message, "resign") == 0 ||
\r
6256 strcmp(message, "computer resigns") == 0) {
\r
6257 switch (gameMode) {
\r
6258 case MachinePlaysBlack:
\r
6259 case IcsPlayingBlack:
\r
6260 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6262 case MachinePlaysWhite:
\r
6263 case IcsPlayingWhite:
\r
6264 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6266 case TwoMachinesPlay:
\r
6267 if (cps->twoMachinesColor[0] == 'w')
\r
6268 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6270 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6273 /* can't happen */
\r
6277 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6278 switch (gameMode) {
\r
6279 case MachinePlaysBlack:
\r
6280 case IcsPlayingBlack:
\r
6281 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6283 case MachinePlaysWhite:
\r
6284 case IcsPlayingWhite:
\r
6285 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6287 case TwoMachinesPlay:
\r
6288 if (cps->twoMachinesColor[0] == 'w')
\r
6289 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6291 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6294 /* can't happen */
\r
6298 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6299 switch (gameMode) {
\r
6300 case MachinePlaysBlack:
\r
6301 case IcsPlayingBlack:
\r
6302 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6304 case MachinePlaysWhite:
\r
6305 case IcsPlayingWhite:
\r
6306 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6308 case TwoMachinesPlay:
\r
6309 if (cps->twoMachinesColor[0] == 'w')
\r
6310 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6312 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6315 /* can't happen */
\r
6319 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6320 if (WhiteOnMove(forwardMostMove)) {
\r
6321 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6323 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6326 } else if (strstr(message, "Draw") != NULL ||
\r
6327 strstr(message, "game is a draw") != NULL) {
\r
6328 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6330 } else if (strstr(message, "offer") != NULL &&
\r
6331 strstr(message, "draw") != NULL) {
\r
6333 if (appData.zippyPlay && first.initDone) {
\r
6334 /* Relay offer to ICS */
\r
6335 SendToICS(ics_prefix);
\r
6336 SendToICS("draw\n");
\r
6339 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6340 if (gameMode == TwoMachinesPlay) {
\r
6341 if (cps->other->offeredDraw) {
\r
6342 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6343 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6344 /* until after we also have move, to see if it is really claim */
\r
6348 if (cps->other->sendDrawOffers) {
\r
6349 SendToProgram("draw\n", cps->other);
\r
6353 } else if (gameMode == MachinePlaysWhite ||
\r
6354 gameMode == MachinePlaysBlack) {
\r
6355 if (userOfferedDraw) {
\r
6356 DisplayInformation("Machine accepts your draw offer");
\r
6357 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6359 DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");
\r
6366 * Look for thinking output
\r
6368 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6369 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6371 int plylev, mvleft, mvtot, curscore, time;
\r
6372 char mvname[MOVE_LEN];
\r
6373 unsigned long nodes;
\r
6375 int ignore = FALSE;
\r
6376 int prefixHint = FALSE;
\r
6377 mvname[0] = NULLCHAR;
\r
6379 switch (gameMode) {
\r
6380 case MachinePlaysBlack:
\r
6381 case IcsPlayingBlack:
\r
6382 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6384 case MachinePlaysWhite:
\r
6385 case IcsPlayingWhite:
\r
6386 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6391 case TwoMachinesPlay:
\r
6392 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6402 buf1[0] = NULLCHAR;
\r
6403 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6404 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6406 if (plyext != ' ' && plyext != '\t') {
\r
6410 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6411 if( cps->scoreIsAbsolute &&
\r
6412 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6414 curscore = -curscore;
\r
6418 programStats.depth = plylev;
\r
6419 programStats.nodes = nodes;
\r
6420 programStats.time = time;
\r
6421 programStats.score = curscore;
\r
6422 programStats.got_only_move = 0;
\r
6424 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6427 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6428 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6429 if(WhiteOnMove(forwardMostMove))
\r
6430 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6431 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6434 /* Buffer overflow protection */
\r
6435 if (buf1[0] != NULLCHAR) {
\r
6436 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6437 && appData.debugMode) {
\r
6439 "PV is too long; using the first %d bytes.\n",
\r
6440 sizeof(programStats.movelist) - 1);
\r
6443 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6445 sprintf(programStats.movelist, " no PV\n");
\r
6448 if (programStats.seen_stat) {
\r
6449 programStats.ok_to_send = 1;
\r
6452 if (strchr(programStats.movelist, '(') != NULL) {
\r
6453 programStats.line_is_book = 1;
\r
6454 programStats.nr_moves = 0;
\r
6455 programStats.moves_left = 0;
\r
6457 programStats.line_is_book = 0;
\r
6460 SendProgramStatsToFrontend( cps, &programStats );
\r
6463 [AS] Protect the thinkOutput buffer from overflow... this
\r
6464 is only useful if buf1 hasn't overflowed first!
\r
6466 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6468 (gameMode == TwoMachinesPlay ?
\r
6469 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6470 ((double) curscore) / 100.0,
\r
6471 prefixHint ? lastHint : "",
\r
6472 prefixHint ? " " : "" );
\r
6474 if( buf1[0] != NULLCHAR ) {
\r
6475 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6477 if( strlen(buf1) > max_len ) {
\r
6478 if( appData.debugMode) {
\r
6479 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6481 buf1[max_len+1] = '\0';
\r
6484 strcat( thinkOutput, buf1 );
\r
6487 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
6488 DisplayMove(currentMove - 1);
\r
6489 DisplayAnalysis();
\r
6493 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6494 /* crafty (9.25+) says "(only move) <move>"
\r
6495 * if there is only 1 legal move
\r
6497 sscanf(p, "(only move) %s", buf1);
\r
6498 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6499 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6500 programStats.depth = 1;
\r
6501 programStats.nr_moves = 1;
\r
6502 programStats.moves_left = 1;
\r
6503 programStats.nodes = 1;
\r
6504 programStats.time = 1;
\r
6505 programStats.got_only_move = 1;
\r
6507 /* Not really, but we also use this member to
\r
6508 mean "line isn't going to change" (Crafty
\r
6509 isn't searching, so stats won't change) */
\r
6510 programStats.line_is_book = 1;
\r
6512 SendProgramStatsToFrontend( cps, &programStats );
\r
6514 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6515 DisplayMove(currentMove - 1);
\r
6516 DisplayAnalysis();
\r
6519 } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",
\r
6520 &time, &nodes, &plylev, &mvleft,
\r
6521 &mvtot, mvname) >= 5) {
\r
6522 /* The stat01: line is from Crafty (9.29+) in response
\r
6523 to the "." command */
\r
6524 programStats.seen_stat = 1;
\r
6525 cps->maybeThinking = TRUE;
\r
6527 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6530 programStats.depth = plylev;
\r
6531 programStats.time = time;
\r
6532 programStats.nodes = nodes;
\r
6533 programStats.moves_left = mvleft;
\r
6534 programStats.nr_moves = mvtot;
\r
6535 strcpy(programStats.move_name, mvname);
\r
6536 programStats.ok_to_send = 1;
\r
6537 programStats.movelist[0] = '\0';
\r
6539 SendProgramStatsToFrontend( cps, &programStats );
\r
6541 DisplayAnalysis();
\r
6544 } else if (strncmp(message,"++",2) == 0) {
\r
6545 /* Crafty 9.29+ outputs this */
\r
6546 programStats.got_fail = 2;
\r
6549 } else if (strncmp(message,"--",2) == 0) {
\r
6550 /* Crafty 9.29+ outputs this */
\r
6551 programStats.got_fail = 1;
\r
6554 } else if (thinkOutput[0] != NULLCHAR &&
\r
6555 strncmp(message, " ", 4) == 0) {
\r
6556 unsigned message_len;
\r
6559 while (*p && *p == ' ') p++;
\r
6561 message_len = strlen( p );
\r
6563 /* [AS] Avoid buffer overflow */
\r
6564 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6565 strcat(thinkOutput, " ");
\r
6566 strcat(thinkOutput, p);
\r
6569 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6570 strcat(programStats.movelist, " ");
\r
6571 strcat(programStats.movelist, p);
\r
6574 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {
\r
6575 DisplayMove(currentMove - 1);
\r
6576 DisplayAnalysis();
\r
6582 buf1[0] = NULLCHAR;
\r
6584 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6585 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6587 ChessProgramStats cpstats;
\r
6589 if (plyext != ' ' && plyext != '\t') {
\r
6593 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6594 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6595 curscore = -curscore;
\r
6598 cpstats.depth = plylev;
\r
6599 cpstats.nodes = nodes;
\r
6600 cpstats.time = time;
\r
6601 cpstats.score = curscore;
\r
6602 cpstats.got_only_move = 0;
\r
6603 cpstats.movelist[0] = '\0';
\r
6605 if (buf1[0] != NULLCHAR) {
\r
6606 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6609 cpstats.ok_to_send = 0;
\r
6610 cpstats.line_is_book = 0;
\r
6611 cpstats.nr_moves = 0;
\r
6612 cpstats.moves_left = 0;
\r
6614 SendProgramStatsToFrontend( cps, &cpstats );
\r
6621 /* Parse a game score from the character string "game", and
\r
6622 record it as the history of the current game. The game
\r
6623 score is NOT assumed to start from the standard position.
\r
6624 The display is not updated in any way.
\r
6627 ParseGameHistory(game)
\r
6630 ChessMove moveType;
\r
6631 int fromX, fromY, toX, toY, boardIndex;
\r
6634 char buf[MSG_SIZ];
\r
6636 if (appData.debugMode)
\r
6637 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6639 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6640 gameInfo.site = StrSave(appData.icsHost);
\r
6641 gameInfo.date = PGNDate();
\r
6642 gameInfo.round = StrSave("-");
\r
6644 /* Parse out names of players */
\r
6645 while (*game == ' ') game++;
\r
6647 while (*game != ' ') *p++ = *game++;
\r
6649 gameInfo.white = StrSave(buf);
\r
6650 while (*game == ' ') game++;
\r
6652 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6654 gameInfo.black = StrSave(buf);
\r
6657 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6660 yyboardindex = boardIndex;
\r
6661 moveType = (ChessMove) yylex();
\r
6662 switch (moveType) {
\r
6663 case IllegalMove: /* maybe suicide chess, etc. */
\r
6664 if (appData.debugMode) {
\r
6665 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6666 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6667 setbuf(debugFP, NULL);
\r
6669 case WhitePromotionChancellor:
\r
6670 case BlackPromotionChancellor:
\r
6671 case WhitePromotionArchbishop:
\r
6672 case BlackPromotionArchbishop:
\r
6673 case WhitePromotionQueen:
\r
6674 case BlackPromotionQueen:
\r
6675 case WhitePromotionRook:
\r
6676 case BlackPromotionRook:
\r
6677 case WhitePromotionBishop:
\r
6678 case BlackPromotionBishop:
\r
6679 case WhitePromotionKnight:
\r
6680 case BlackPromotionKnight:
\r
6681 case WhitePromotionKing:
\r
6682 case BlackPromotionKing:
\r
6684 case WhiteCapturesEnPassant:
\r
6685 case BlackCapturesEnPassant:
\r
6686 case WhiteKingSideCastle:
\r
6687 case WhiteQueenSideCastle:
\r
6688 case BlackKingSideCastle:
\r
6689 case BlackQueenSideCastle:
\r
6690 case WhiteKingSideCastleWild:
\r
6691 case WhiteQueenSideCastleWild:
\r
6692 case BlackKingSideCastleWild:
\r
6693 case BlackQueenSideCastleWild:
\r
6695 case WhiteHSideCastleFR:
\r
6696 case WhiteASideCastleFR:
\r
6697 case BlackHSideCastleFR:
\r
6698 case BlackASideCastleFR:
\r
6700 fromX = currentMoveString[0] - AAA;
\r
6701 fromY = currentMoveString[1] - ONE;
\r
6702 toX = currentMoveString[2] - AAA;
\r
6703 toY = currentMoveString[3] - ONE;
\r
6704 promoChar = currentMoveString[4];
\r
6708 fromX = moveType == WhiteDrop ?
\r
6709 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6710 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6711 fromY = DROP_RANK;
\r
6712 toX = currentMoveString[2] - AAA;
\r
6713 toY = currentMoveString[3] - ONE;
\r
6714 promoChar = NULLCHAR;
\r
6716 case AmbiguousMove:
\r
6718 sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);
\r
6719 if (appData.debugMode) {
\r
6720 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6721 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6722 setbuf(debugFP, NULL);
\r
6724 DisplayError(buf, 0);
\r
6726 case ImpossibleMove:
\r
6728 sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);
\r
6729 if (appData.debugMode) {
\r
6730 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6731 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6732 setbuf(debugFP, NULL);
\r
6734 DisplayError(buf, 0);
\r
6736 case (ChessMove) 0: /* end of file */
\r
6737 if (boardIndex < backwardMostMove) {
\r
6738 /* Oops, gap. How did that happen? */
\r
6739 DisplayError("Gap in move list", 0);
\r
6742 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6743 if (boardIndex > forwardMostMove) {
\r
6744 forwardMostMove = boardIndex;
\r
6748 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6749 strcat(parseList[boardIndex-1], " ");
\r
6750 strcat(parseList[boardIndex-1], yy_text);
\r
6762 case GameUnfinished:
\r
6763 if (gameMode == IcsExamining) {
\r
6764 if (boardIndex < backwardMostMove) {
\r
6765 /* Oops, gap. How did that happen? */
\r
6768 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6771 gameInfo.result = moveType;
\r
6772 p = strchr(yy_text, '{');
\r
6773 if (p == NULL) p = strchr(yy_text, '(');
\r
6776 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6778 q = strchr(p, *p == '{' ? '}' : ')');
\r
6779 if (q != NULL) *q = NULLCHAR;
\r
6782 gameInfo.resultDetails = StrSave(p);
\r
6785 if (boardIndex >= forwardMostMove &&
\r
6786 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
6787 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6790 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
6791 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
6792 parseList[boardIndex]);
\r
6793 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
6794 /* currentMoveString is set as a side-effect of yylex */
\r
6795 strcpy(moveList[boardIndex], currentMoveString);
\r
6796 strcat(moveList[boardIndex], "\n");
\r
6798 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
6799 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
6800 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
6802 case MT_STALEMATE:
\r
6806 if(gameInfo.variant != VariantShogi)
\r
6807 strcat(parseList[boardIndex - 1], "+");
\r
6809 case MT_CHECKMATE:
\r
6810 strcat(parseList[boardIndex - 1], "#");
\r
6817 /* Apply a move to the given board */
\r
6819 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
6820 int fromX, fromY, toX, toY;
\r
6824 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
6826 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
6827 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
6828 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
6831 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
6832 oldEP = epStatus[p-1];
\r
6833 epStatus[p] = EP_NONE;
\r
6835 if( board[toY][toX] != EmptySquare )
\r
6836 epStatus[p] = EP_CAPTURE;
\r
6838 if( board[fromY][fromX] == WhitePawn ) {
\r
6839 epStatus[p] = EP_PAWN_MOVE;
\r
6841 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
6842 gameInfo.variant != VariantBerolina || toX < fromX)
\r
6843 epStatus[p] = toX | berolina;
\r
6844 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
6845 gameInfo.variant != VariantBerolina || toX > fromX)
\r
6846 epStatus[p] = toX;
\r
6848 if( board[fromY][fromX] == BlackPawn ) {
\r
6849 epStatus[p] = EP_PAWN_MOVE;
\r
6850 if( toY-fromY== -2)
\r
6851 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
6852 gameInfo.variant != VariantBerolina || toX < fromX)
\r
6853 epStatus[p] = toX | berolina;
\r
6854 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
6855 gameInfo.variant != VariantBerolina || toX > fromX)
\r
6856 epStatus[p] = toX;
\r
6859 for(i=0; i<nrCastlingRights; i++) {
\r
6860 castlingRights[p][i] = castlingRights[p-1][i];
\r
6861 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
6862 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
6863 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
6868 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
6869 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
6870 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
6872 if (fromX == toX && fromY == toY) return;
\r
6874 if (fromY == DROP_RANK) {
\r
6875 /* must be first */
\r
6876 piece = board[toY][toX] = (ChessSquare) fromX;
\r
6878 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
6879 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
6880 if(gameInfo.variant == VariantKnightmate)
\r
6881 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
6883 /* Code added by Tord: */
\r
6884 /* FRC castling assumed when king captures friendly rook. */
\r
6885 if (board[fromY][fromX] == WhiteKing &&
\r
6886 board[toY][toX] == WhiteRook) {
\r
6887 board[fromY][fromX] = EmptySquare;
\r
6888 board[toY][toX] = EmptySquare;
\r
6890 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
6892 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
6894 } else if (board[fromY][fromX] == BlackKing &&
\r
6895 board[toY][toX] == BlackRook) {
\r
6896 board[fromY][fromX] = EmptySquare;
\r
6897 board[toY][toX] = EmptySquare;
\r
6899 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
6901 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
6903 /* End of code added by Tord */
\r
6905 } else if (board[fromY][fromX] == king
\r
6906 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6907 && toY == fromY && toX > fromX+1) {
\r
6908 board[fromY][fromX] = EmptySquare;
\r
6909 board[toY][toX] = king;
\r
6910 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6911 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6912 } else if (board[fromY][fromX] == king
\r
6913 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6914 && toY == fromY && toX < fromX-1) {
\r
6915 board[fromY][fromX] = EmptySquare;
\r
6916 board[toY][toX] = king;
\r
6917 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6918 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6919 } else if (board[fromY][fromX] == WhitePawn
\r
6920 && toY == BOARD_HEIGHT-1
\r
6921 && gameInfo.variant != VariantXiangqi
\r
6923 /* white pawn promotion */
\r
6924 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
6925 if (board[toY][toX] == EmptySquare) {
\r
6926 board[toY][toX] = WhiteQueen;
\r
6928 if(gameInfo.variant==VariantBughouse ||
\r
6929 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6930 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6931 board[fromY][fromX] = EmptySquare;
\r
6932 } else if ((fromY == BOARD_HEIGHT-4)
\r
6934 && gameInfo.variant != VariantXiangqi
\r
6935 && gameInfo.variant != VariantBerolina
\r
6936 && (board[fromY][fromX] == WhitePawn)
\r
6937 && (board[toY][toX] == EmptySquare)) {
\r
6938 board[fromY][fromX] = EmptySquare;
\r
6939 board[toY][toX] = WhitePawn;
\r
6940 captured = board[toY - 1][toX];
\r
6941 board[toY - 1][toX] = EmptySquare;
\r
6942 } else if ((fromY == BOARD_HEIGHT-4)
\r
6944 && gameInfo.variant == VariantBerolina
\r
6945 && (board[fromY][fromX] == WhitePawn)
\r
6946 && (board[toY][toX] == EmptySquare)) {
\r
6947 board[fromY][fromX] = EmptySquare;
\r
6948 board[toY][toX] = WhitePawn;
\r
6949 if(oldEP & EP_BEROLIN_A) {
\r
6950 captured = board[fromY][fromX-1];
\r
6951 board[fromY][fromX-1] = EmptySquare;
\r
6952 }else{ captured = board[fromY][fromX+1];
\r
6953 board[fromY][fromX+1] = EmptySquare;
\r
6955 } else if (board[fromY][fromX] == king
\r
6956 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6957 && toY == fromY && toX > fromX+1) {
\r
6958 board[fromY][fromX] = EmptySquare;
\r
6959 board[toY][toX] = king;
\r
6960 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
6961 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
6962 } else if (board[fromY][fromX] == king
\r
6963 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
6964 && toY == fromY && toX < fromX-1) {
\r
6965 board[fromY][fromX] = EmptySquare;
\r
6966 board[toY][toX] = king;
\r
6967 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
6968 board[fromY][BOARD_LEFT] = EmptySquare;
\r
6969 } else if (fromY == 7 && fromX == 3
\r
6970 && board[fromY][fromX] == BlackKing
\r
6971 && toY == 7 && toX == 5) {
\r
6972 board[fromY][fromX] = EmptySquare;
\r
6973 board[toY][toX] = BlackKing;
\r
6974 board[fromY][7] = EmptySquare;
\r
6975 board[toY][4] = BlackRook;
\r
6976 } else if (fromY == 7 && fromX == 3
\r
6977 && board[fromY][fromX] == BlackKing
\r
6978 && toY == 7 && toX == 1) {
\r
6979 board[fromY][fromX] = EmptySquare;
\r
6980 board[toY][toX] = BlackKing;
\r
6981 board[fromY][0] = EmptySquare;
\r
6982 board[toY][2] = BlackRook;
\r
6983 } else if (board[fromY][fromX] == BlackPawn
\r
6985 && gameInfo.variant != VariantXiangqi
\r
6987 /* black pawn promotion */
\r
6988 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
6989 if (board[0][toX] == EmptySquare) {
\r
6990 board[0][toX] = BlackQueen;
\r
6992 if(gameInfo.variant==VariantBughouse ||
\r
6993 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
6994 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
6995 board[fromY][fromX] = EmptySquare;
\r
6996 } else if ((fromY == 3)
\r
6998 && gameInfo.variant != VariantXiangqi
\r
6999 && gameInfo.variant != VariantBerolina
\r
7000 && (board[fromY][fromX] == BlackPawn)
\r
7001 && (board[toY][toX] == EmptySquare)) {
\r
7002 board[fromY][fromX] = EmptySquare;
\r
7003 board[toY][toX] = BlackPawn;
\r
7004 captured = board[toY + 1][toX];
\r
7005 board[toY + 1][toX] = EmptySquare;
\r
7006 } else if ((fromY == 3)
\r
7008 && gameInfo.variant == VariantBerolina
\r
7009 && (board[fromY][fromX] == BlackPawn)
\r
7010 && (board[toY][toX] == EmptySquare)) {
\r
7011 board[fromY][fromX] = EmptySquare;
\r
7012 board[toY][toX] = BlackPawn;
\r
7013 if(oldEP & EP_BEROLIN_A) {
\r
7014 captured = board[fromY][fromX-1];
\r
7015 board[fromY][fromX-1] = EmptySquare;
\r
7016 }else{ captured = board[fromY][fromX+1];
\r
7017 board[fromY][fromX+1] = EmptySquare;
\r
7020 board[toY][toX] = board[fromY][fromX];
\r
7021 board[fromY][fromX] = EmptySquare;
\r
7024 /* [HGM] now we promote for Shogi, if needed */
\r
7025 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7026 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7029 if (gameInfo.holdingsWidth != 0) {
\r
7031 /* !!A lot more code needs to be written to support holdings */
\r
7032 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7033 /* penultimate board files, so they are automaticlly stored */
\r
7034 /* in the game history. */
\r
7035 if (fromY == DROP_RANK) {
\r
7036 /* Delete from holdings, by decreasing count */
\r
7037 /* and erasing image if necessary */
\r
7039 if(p < (int) BlackPawn) { /* white drop */
\r
7040 p -= (int)WhitePawn;
\r
7041 if(p >= gameInfo.holdingsSize) p = 0;
\r
7042 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7043 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7044 } else { /* black drop */
\r
7045 p -= (int)BlackPawn;
\r
7046 if(p >= gameInfo.holdingsSize) p = 0;
\r
7047 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7048 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7051 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7052 && gameInfo.variant != VariantBughouse ) {
\r
7053 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7054 if(gameInfo.variant == VariantSuper) {
\r
7055 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7056 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7058 p = (int) captured;
\r
7059 if (p >= (int) BlackPawn) {
\r
7060 p -= (int)BlackPawn;
\r
7061 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7062 /* in Shogi restore piece to its original first */
\r
7063 captured = (ChessSquare) (DEMOTED captured);
\r
7066 p = PieceToNumber((ChessSquare)p);
\r
7067 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7068 board[p][BOARD_WIDTH-2]++;
\r
7069 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7071 p -= (int)WhitePawn;
\r
7072 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7073 captured = (ChessSquare) (DEMOTED captured);
\r
7076 p = PieceToNumber((ChessSquare)p);
\r
7077 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7078 board[BOARD_HEIGHT-1-p][1]++;
\r
7079 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7083 } else if (gameInfo.variant == VariantAtomic) {
\r
7084 if (captured != EmptySquare) {
\r
7086 for (y = toY-1; y <= toY+1; y++) {
\r
7087 for (x = toX-1; x <= toX+1; x++) {
\r
7088 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7089 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7090 board[y][x] = EmptySquare;
\r
7094 board[toY][toX] = EmptySquare;
\r
7097 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7098 /* [HGM] Shogi promotions */
\r
7099 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7104 /* Updates forwardMostMove */
\r
7106 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7107 int fromX, fromY, toX, toY;
\r
7110 forwardMostMove++;
\r
7112 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */
\r
7113 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7114 piece = boards[forwardMostMove-1][fromY][fromX];
\r
7115 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7116 if(gameInfo.variant == VariantKnightmate)
\r
7117 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7118 if(forwardMostMove == 1) {
\r
7119 if(blackPlaysFirst)
\r
7120 fprintf(serverMoves, "%s;", second.tidy);
\r
7121 fprintf(serverMoves, "%s;", first.tidy);
\r
7122 if(!blackPlaysFirst)
\r
7123 fprintf(serverMoves, "%s;", second.tidy);
\r
7124 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7125 lastLoadFlag = loadFlag;
\r
7126 // print base move
\r
7127 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7128 // print castling suffix
\r
7129 if( toY == fromY && piece == king ) {
\r
7131 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7133 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7136 if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||
\r
7137 boards[forwardMostMove-1][fromY][fromX] == BlackPawn ) &&
\r
7138 boards[forwardMostMove-1][toY][toX] == EmptySquare
\r
7140 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7141 // promotion suffix
\r
7142 if(promoChar != NULLCHAR)
\r
7143 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7145 fprintf(serverMoves, "/%d/%d",
\r
7146 pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);
\r
7147 if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;
\r
7148 else timeLeft = blackTimeRemaining/1000;
\r
7149 fprintf(serverMoves, "/%d", timeLeft);
\r
7151 fflush(serverMoves);
\r
7154 if (forwardMostMove >= MAX_MOVES) {
\r
7155 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
7160 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7161 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7162 if (commentList[forwardMostMove] != NULL) {
\r
7163 free(commentList[forwardMostMove]);
\r
7164 commentList[forwardMostMove] = NULL;
\r
7166 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
7167 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
7168 gameInfo.result = GameUnfinished;
\r
7169 if (gameInfo.resultDetails != NULL) {
\r
7170 free(gameInfo.resultDetails);
\r
7171 gameInfo.resultDetails = NULL;
\r
7173 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7174 moveList[forwardMostMove - 1]);
\r
7175 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7176 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7177 fromY, fromX, toY, toX, promoChar,
\r
7178 parseList[forwardMostMove - 1]);
\r
7179 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7180 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7181 castlingRights[forwardMostMove]) ) {
\r
7183 case MT_STALEMATE:
\r
7187 if(gameInfo.variant != VariantShogi)
\r
7188 strcat(parseList[forwardMostMove - 1], "+");
\r
7190 case MT_CHECKMATE:
\r
7191 strcat(parseList[forwardMostMove - 1], "#");
\r
7194 if (appData.debugMode) {
\r
7195 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7200 /* Updates currentMove if not pausing */
\r
7202 ShowMove(fromX, fromY, toX, toY)
\r
7204 int instant = (gameMode == PlayFromGameFile) ?
\r
7205 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7206 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7208 if (forwardMostMove == currentMove + 1) {
\r
7209 AnimateMove(boards[forwardMostMove - 1],
\r
7210 fromX, fromY, toX, toY);
\r
7212 if (appData.highlightLastMove) {
\r
7213 SetHighlights(fromX, fromY, toX, toY);
\r
7216 currentMove = forwardMostMove;
\r
7219 if (instant) return;
\r
7221 DisplayMove(currentMove - 1);
\r
7222 DrawPosition(FALSE, boards[currentMove]);
\r
7223 DisplayBothClocks();
\r
7224 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7227 void SendEgtPath(ChessProgramState *cps)
\r
7228 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7229 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7231 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7234 char c, *q = name+1, *r, *s;
\r
7236 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7237 while(*p && *p != ',') *q++ = *p++;
\r
7238 *q++ = ':'; *q = 0;
\r
7239 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7240 strcmp(name, ",nalimov:") == 0 ) {
\r
7241 // take nalimov path from the menu-changeable option first, if it is defined
\r
7242 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7243 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7245 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7246 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7247 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7248 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7249 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7250 c = *r; *r = 0; // temporarily null-terminate path info
\r
7251 *--q = 0; // strip of trailig ':' from name
\r
7252 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7254 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7256 if(*p == ',') p++; // read away comma to position for next format name
\r
7261 InitChessProgram(cps, setup)
\r
7262 ChessProgramState *cps;
\r
7263 int setup; /* [HGM] needed to setup FRC opening position */
\r
7265 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7266 if (appData.noChessProgram) return;
\r
7267 hintRequested = FALSE;
\r
7268 bookRequested = FALSE;
\r
7270 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7271 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7272 if(cps->memSize) { /* [HGM] memory */
\r
7273 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7274 SendToProgram(buf, cps);
\r
7276 SendEgtPath(cps); /* [HGM] EGT */
\r
7277 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7278 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7279 SendToProgram(buf, cps);
\r
7282 SendToProgram(cps->initString, cps);
\r
7283 if (gameInfo.variant != VariantNormal &&
\r
7284 gameInfo.variant != VariantLoadable
\r
7285 /* [HGM] also send variant if board size non-standard */
\r
7286 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7288 char *v = VariantName(gameInfo.variant);
\r
7289 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7290 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7291 sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);
\r
7292 DisplayFatalError(buf, 0, 1);
\r
7296 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7297 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7298 if( gameInfo.variant == VariantXiangqi )
\r
7299 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7300 if( gameInfo.variant == VariantShogi )
\r
7301 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7302 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7303 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7304 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7305 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7306 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7307 if( gameInfo.variant == VariantCourier )
\r
7308 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7309 if( gameInfo.variant == VariantSuper )
\r
7310 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7313 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7314 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7315 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7316 if(StrStr(cps->variants, b) == NULL) {
\r
7317 // specific sized variant not known, check if general sizing allowed
\r
7318 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7319 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7320 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7321 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7322 DisplayFatalError(buf, 0, 1);
\r
7325 /* [HGM] here we really should compare with the maximum supported board size */
\r
7328 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7329 sprintf(buf, "variant %s\n", b);
\r
7330 SendToProgram(buf, cps);
\r
7332 currentlyInitializedVariant = gameInfo.variant;
\r
7334 /* [HGM] send opening position in FRC to first engine */
\r
7336 SendToProgram("force\n", cps);
\r
7337 SendBoard(cps, 0);
\r
7338 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7339 setboardSpoiledMachineBlack = 1;
\r
7342 if (cps->sendICS) {
\r
7343 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7344 SendToProgram(buf, cps);
\r
7346 cps->maybeThinking = FALSE;
\r
7347 cps->offeredDraw = 0;
\r
7348 if (!appData.icsActive) {
\r
7349 SendTimeControl(cps, movesPerSession, timeControl,
\r
7350 timeIncrement, appData.searchDepth,
\r
7353 if (appData.showThinking
\r
7354 // [HGM] thinking: four options require thinking output to be sent
\r
7355 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7357 SendToProgram("post\n", cps);
\r
7359 SendToProgram("hard\n", cps);
\r
7360 if (!appData.ponderNextMove) {
\r
7361 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7362 it without being sure what state we are in first. "hard"
\r
7363 is not a toggle, so that one is OK.
\r
7365 SendToProgram("easy\n", cps);
\r
7367 if (cps->usePing) {
\r
7368 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7369 SendToProgram(buf, cps);
\r
7371 cps->initDone = TRUE;
\r
7376 StartChessProgram(cps)
\r
7377 ChessProgramState *cps;
\r
7379 char buf[MSG_SIZ];
\r
7382 if (appData.noChessProgram) return;
\r
7383 cps->initDone = FALSE;
\r
7385 if (strcmp(cps->host, "localhost") == 0) {
\r
7386 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7387 } else if (*appData.remoteShell == NULLCHAR) {
\r
7388 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7390 if (*appData.remoteUser == NULLCHAR) {
\r
7391 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7394 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7395 cps->host, appData.remoteUser, cps->program);
\r
7397 err = StartChildProcess(buf, "", &cps->pr);
\r
7401 sprintf(buf, "Startup failure on '%s'", cps->program);
\r
7402 DisplayFatalError(buf, err, 1);
\r
7408 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7409 if (cps->protocolVersion > 1) {
\r
7410 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7411 SendToProgram(buf, cps);
\r
7413 SendToProgram("xboard\n", cps);
\r
7419 TwoMachinesEventIfReady P((void))
\r
7421 if (first.lastPing != first.lastPong) {
\r
7422 DisplayMessage("", "Waiting for first chess program");
\r
7423 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
7426 if (second.lastPing != second.lastPong) {
\r
7427 DisplayMessage("", "Waiting for second chess program");
\r
7428 ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);
\r
7432 TwoMachinesEvent();
\r
7436 NextMatchGame P((void))
\r
7438 int index; /* [HGM] autoinc: step lod index during match */
\r
7439 Reset(FALSE, TRUE);
\r
7440 if (*appData.loadGameFile != NULLCHAR) {
\r
7441 index = appData.loadGameIndex;
\r
7442 if(index < 0) { // [HGM] autoinc
\r
7443 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7444 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7446 LoadGameFromFile(appData.loadGameFile,
\r
7448 appData.loadGameFile, FALSE);
\r
7449 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7450 index = appData.loadPositionIndex;
\r
7451 if(index < 0) { // [HGM] autoinc
\r
7452 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7453 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7455 LoadPositionFromFile(appData.loadPositionFile,
\r
7457 appData.loadPositionFile);
\r
7459 TwoMachinesEventIfReady();
\r
7462 void UserAdjudicationEvent( int result )
\r
7464 ChessMove gameResult = GameIsDrawn;
\r
7466 if( result > 0 ) {
\r
7467 gameResult = WhiteWins;
\r
7469 else if( result < 0 ) {
\r
7470 gameResult = BlackWins;
\r
7473 if( gameMode == TwoMachinesPlay ) {
\r
7474 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7480 GameEnds(result, resultDetails, whosays)
\r
7482 char *resultDetails;
\r
7485 GameMode nextGameMode;
\r
7487 char buf[MSG_SIZ];
\r
7489 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7492 if (appData.debugMode) {
\r
7493 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7494 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7497 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7498 /* If we are playing on ICS, the server decides when the
\r
7499 game is over, but the engine can offer to draw, claim
\r
7500 a draw, or resign.
\r
7503 if (appData.zippyPlay && first.initDone) {
\r
7504 if (result == GameIsDrawn) {
\r
7505 /* In case draw still needs to be claimed */
\r
7506 SendToICS(ics_prefix);
\r
7507 SendToICS("draw\n");
\r
7508 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7509 SendToICS(ics_prefix);
\r
7510 SendToICS("resign\n");
\r
7514 endingGame = 0; /* [HGM] crash */
\r
7518 /* If we're loading the game from a file, stop */
\r
7519 if (whosays == GE_FILE) {
\r
7520 (void) StopLoadGameTimer();
\r
7521 gameFileFP = NULL;
\r
7524 /* Cancel draw offers */
\r
7525 first.offeredDraw = second.offeredDraw = 0;
\r
7527 /* If this is an ICS game, only ICS can really say it's done;
\r
7528 if not, anyone can. */
\r
7529 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7530 gameMode == IcsPlayingBlack ||
\r
7531 gameMode == IcsObserving ||
\r
7532 gameMode == IcsExamining);
\r
7534 if (!isIcsGame || whosays == GE_ICS) {
\r
7535 /* OK -- not an ICS game, or ICS said it was done */
\r
7537 if (!isIcsGame && !appData.noChessProgram)
\r
7538 SetUserThinkingEnables();
\r
7540 /* [HGM] if a machine claims the game end we verify this claim */
\r
7541 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7542 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7545 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7546 first.twoMachinesColor[0] :
\r
7547 second.twoMachinesColor[0] ;
\r
7548 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) &&
\r
7549 (result == WhiteWins && claimer == 'w' ||
\r
7550 result == BlackWins && claimer == 'b' ) ) {
\r
7551 if (appData.debugMode) {
\r
7552 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7553 result, epStatus[forwardMostMove], forwardMostMove);
\r
7555 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7556 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7557 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7558 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7559 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7560 resultDetails = buf;
\r
7563 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7564 && (forwardMostMove <= backwardMostMove ||
\r
7565 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7566 (claimer=='b')==(forwardMostMove&1))
\r
7568 /* [HGM] verify: draws that were not flagged are false claims */
\r
7569 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7570 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7571 resultDetails = buf;
\r
7573 /* (Claiming a loss is accepted no questions asked!) */
\r
7575 /* [HGM] bare: don't allow bare King to win */
\r
7576 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) && result != GameIsDrawn)
\r
7577 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7578 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7579 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7580 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7582 if (appData.debugMode) {
\r
7583 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7584 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7587 result = GameIsDrawn;
\r
7588 sprintf(buf, "%s but bare king", resultDetails);
\r
7589 resultDetails = buf;
\r
7595 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7596 if(result==WhiteWins) c = '+';
\r
7597 if(result==BlackWins) c = '-';
\r
7598 if(resultDetails != NULL)
\r
7599 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7601 if (resultDetails != NULL) {
\r
7602 gameInfo.result = result;
\r
7603 gameInfo.resultDetails = StrSave(resultDetails);
\r
7605 /* display last move only if game was not loaded from file */
\r
7606 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7607 DisplayMove(currentMove - 1);
\r
7609 if (forwardMostMove != 0) {
\r
7610 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7611 if (*appData.saveGameFile != NULLCHAR) {
\r
7612 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7613 } else if (appData.autoSaveGames) {
\r
7616 if (*appData.savePositionFile != NULLCHAR) {
\r
7617 SavePositionToFile(appData.savePositionFile);
\r
7622 /* Tell program how game ended in case it is learning */
\r
7623 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7624 /* engine died and we got here through time loss. In that */
\r
7625 /* case we will get a fatal error writing the pipe, which */
\r
7626 /* would otherwise lose us the PGN. */
\r
7627 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7628 /* output during GameEnds should never be fatal anymore */
\r
7629 if (gameMode == MachinePlaysWhite ||
\r
7630 gameMode == MachinePlaysBlack ||
\r
7631 gameMode == TwoMachinesPlay ||
\r
7632 gameMode == IcsPlayingWhite ||
\r
7633 gameMode == IcsPlayingBlack ||
\r
7634 gameMode == BeginningOfGame) {
\r
7635 char buf[MSG_SIZ];
\r
7636 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7638 if (first.pr != NoProc) {
\r
7639 SendToProgram(buf, &first);
\r
7641 if (second.pr != NoProc &&
\r
7642 gameMode == TwoMachinesPlay) {
\r
7643 SendToProgram(buf, &second);
\r
7648 if (appData.icsActive) {
\r
7649 if (appData.quietPlay &&
\r
7650 (gameMode == IcsPlayingWhite ||
\r
7651 gameMode == IcsPlayingBlack)) {
\r
7652 SendToICS(ics_prefix);
\r
7653 SendToICS("set shout 1\n");
\r
7655 nextGameMode = IcsIdle;
\r
7656 ics_user_moved = FALSE;
\r
7657 /* clean up premove. It's ugly when the game has ended and the
\r
7658 * premove highlights are still on the board.
\r
7661 gotPremove = FALSE;
\r
7662 ClearPremoveHighlights();
\r
7663 DrawPosition(FALSE, boards[currentMove]);
\r
7665 if (whosays == GE_ICS) {
\r
7668 if (gameMode == IcsPlayingWhite)
\r
7669 PlayIcsWinSound();
\r
7670 else if(gameMode == IcsPlayingBlack)
\r
7671 PlayIcsLossSound();
\r
7674 if (gameMode == IcsPlayingBlack)
\r
7675 PlayIcsWinSound();
\r
7676 else if(gameMode == IcsPlayingWhite)
\r
7677 PlayIcsLossSound();
\r
7680 PlayIcsDrawSound();
\r
7683 PlayIcsUnfinishedSound();
\r
7686 } else if (gameMode == EditGame ||
\r
7687 gameMode == PlayFromGameFile ||
\r
7688 gameMode == AnalyzeMode ||
\r
7689 gameMode == AnalyzeFile) {
\r
7690 nextGameMode = gameMode;
\r
7692 nextGameMode = EndOfGame;
\r
7697 nextGameMode = gameMode;
\r
7700 if (appData.noChessProgram) {
\r
7701 gameMode = nextGameMode;
\r
7703 endingGame = 0; /* [HGM] crash */
\r
7707 if (first.reuse) {
\r
7708 /* Put first chess program into idle state */
\r
7709 if (first.pr != NoProc &&
\r
7710 (gameMode == MachinePlaysWhite ||
\r
7711 gameMode == MachinePlaysBlack ||
\r
7712 gameMode == TwoMachinesPlay ||
\r
7713 gameMode == IcsPlayingWhite ||
\r
7714 gameMode == IcsPlayingBlack ||
\r
7715 gameMode == BeginningOfGame)) {
\r
7716 SendToProgram("force\n", &first);
\r
7717 if (first.usePing) {
\r
7718 char buf[MSG_SIZ];
\r
7719 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7720 SendToProgram(buf, &first);
\r
7723 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7724 /* Kill off first chess program */
\r
7725 if (first.isr != NULL)
\r
7726 RemoveInputSource(first.isr);
\r
7729 if (first.pr != NoProc) {
\r
7730 ExitAnalyzeMode();
\r
7731 DoSleep( appData.delayBeforeQuit );
\r
7732 SendToProgram("quit\n", &first);
\r
7733 DoSleep( appData.delayAfterQuit );
\r
7734 DestroyChildProcess(first.pr, first.useSigterm);
\r
7736 first.pr = NoProc;
\r
7738 if (second.reuse) {
\r
7739 /* Put second chess program into idle state */
\r
7740 if (second.pr != NoProc &&
\r
7741 gameMode == TwoMachinesPlay) {
\r
7742 SendToProgram("force\n", &second);
\r
7743 if (second.usePing) {
\r
7744 char buf[MSG_SIZ];
\r
7745 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7746 SendToProgram(buf, &second);
\r
7749 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7750 /* Kill off second chess program */
\r
7751 if (second.isr != NULL)
\r
7752 RemoveInputSource(second.isr);
\r
7753 second.isr = NULL;
\r
7755 if (second.pr != NoProc) {
\r
7756 DoSleep( appData.delayBeforeQuit );
\r
7757 SendToProgram("quit\n", &second);
\r
7758 DoSleep( appData.delayAfterQuit );
\r
7759 DestroyChildProcess(second.pr, second.useSigterm);
\r
7761 second.pr = NoProc;
\r
7764 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7767 if (first.twoMachinesColor[0] == 'w') {
\r
7768 first.matchWins++;
\r
7770 second.matchWins++;
\r
7774 if (first.twoMachinesColor[0] == 'b') {
\r
7775 first.matchWins++;
\r
7777 second.matchWins++;
\r
7783 if (matchGame < appData.matchGames) {
\r
7785 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
7786 tmp = first.twoMachinesColor;
\r
7787 first.twoMachinesColor = second.twoMachinesColor;
\r
7788 second.twoMachinesColor = tmp;
\r
7790 gameMode = nextGameMode;
\r
7792 if(appData.matchPause>10000 || appData.matchPause<10)
\r
7793 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
7794 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
7795 endingGame = 0; /* [HGM] crash */
\r
7798 char buf[MSG_SIZ];
\r
7799 gameMode = nextGameMode;
\r
7800 sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",
\r
7801 first.tidy, second.tidy,
\r
7802 first.matchWins, second.matchWins,
\r
7803 appData.matchGames - (first.matchWins + second.matchWins));
\r
7804 DisplayFatalError(buf, 0, 0);
\r
7807 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7808 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
7809 ExitAnalyzeMode();
\r
7810 gameMode = nextGameMode;
\r
7812 endingGame = 0; /* [HGM] crash */
\r
7815 /* Assumes program was just initialized (initString sent).
\r
7816 Leaves program in force mode. */
\r
7818 FeedMovesToProgram(cps, upto)
\r
7819 ChessProgramState *cps;
\r
7824 if (appData.debugMode)
\r
7825 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
7826 startedFromSetupPosition ? "position and " : "",
\r
7827 backwardMostMove, upto, cps->which);
\r
7828 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
7829 // [HGM] variantswitch: make engine aware of new variant
\r
7830 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
7831 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
7832 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
7833 SendToProgram(buf, cps);
\r
7834 currentlyInitializedVariant = gameInfo.variant;
\r
7836 SendToProgram("force\n", cps);
\r
7837 if (startedFromSetupPosition) {
\r
7838 SendBoard(cps, backwardMostMove);
\r
7839 if (appData.debugMode) {
\r
7840 fprintf(debugFP, "feedMoves\n");
\r
7843 for (i = backwardMostMove; i < upto; i++) {
\r
7844 SendMoveToProgram(i, cps);
\r
7850 ResurrectChessProgram()
\r
7852 /* The chess program may have exited.
\r
7853 If so, restart it and feed it all the moves made so far. */
\r
7855 if (appData.noChessProgram || first.pr != NoProc) return;
\r
7857 StartChessProgram(&first);
\r
7858 InitChessProgram(&first, FALSE);
\r
7859 FeedMovesToProgram(&first, currentMove);
\r
7861 if (!first.sendTime) {
\r
7862 /* can't tell gnuchess what its clock should read,
\r
7863 so we bow to its notion. */
\r
7865 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
7866 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
7869 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
7870 first.analysisSupport) {
\r
7871 SendToProgram("analyze\n", &first);
\r
7872 first.analyzing = TRUE;
\r
7877 * Button procedures
\r
7880 Reset(redraw, init)
\r
7885 if (appData.debugMode) {
\r
7886 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
7887 redraw, init, gameMode);
\r
7889 pausing = pauseExamInvalid = FALSE;
\r
7890 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
7892 whiteFlag = blackFlag = FALSE;
\r
7893 userOfferedDraw = FALSE;
\r
7894 hintRequested = bookRequested = FALSE;
\r
7895 first.maybeThinking = FALSE;
\r
7896 second.maybeThinking = FALSE;
\r
7897 first.bookSuspend = FALSE; // [HGM] book
\r
7898 second.bookSuspend = FALSE;
\r
7899 thinkOutput[0] = NULLCHAR;
\r
7900 lastHint[0] = NULLCHAR;
\r
7901 ClearGameInfo(&gameInfo);
\r
7902 gameInfo.variant = StringToVariant(appData.variant);
\r
7903 ics_user_moved = ics_clock_paused = FALSE;
\r
7904 ics_getting_history = H_FALSE;
\r
7906 white_holding[0] = black_holding[0] = NULLCHAR;
\r
7907 ClearProgramStats();
\r
7910 ClearHighlights();
\r
7911 flipView = appData.flipView;
\r
7912 ClearPremoveHighlights();
\r
7913 gotPremove = FALSE;
\r
7914 alarmSounded = FALSE;
\r
7916 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
7917 if(appData.serverMovesName != NULL) {
\r
7918 /* [HGM] prepare to make moves file for broadcasting */
\r
7919 clock_t t = clock();
\r
7920 if(serverMoves != NULL) fclose(serverMoves);
\r
7921 serverMoves = fopen(appData.serverMovesName, "r");
\r
7922 if(serverMoves != NULL) {
\r
7923 fclose(serverMoves);
\r
7924 /* delay 15 sec before overwriting, so all clients can see end */
\r
7925 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
7927 serverMoves = fopen(appData.serverMovesName, "w");
\r
7930 ExitAnalyzeMode();
\r
7931 gameMode = BeginningOfGame;
\r
7933 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
7934 InitPosition(redraw);
\r
7935 for (i = 0; i < MAX_MOVES; i++) {
\r
7936 if (commentList[i] != NULL) {
\r
7937 free(commentList[i]);
\r
7938 commentList[i] = NULL;
\r
7942 timeRemaining[0][0] = whiteTimeRemaining;
\r
7943 timeRemaining[1][0] = blackTimeRemaining;
\r
7944 if (first.pr == NULL) {
\r
7945 StartChessProgram(&first);
\r
7948 InitChessProgram(&first, startedFromSetupPosition);
\r
7951 DisplayMessage("", "");
\r
7952 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
7956 AutoPlayGameLoop()
\r
7959 if (!AutoPlayOneMove())
\r
7961 if (matchMode || appData.timeDelay == 0)
\r
7963 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
7965 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
7974 int fromX, fromY, toX, toY;
\r
7976 if (appData.debugMode) {
\r
7977 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
7980 if (gameMode != PlayFromGameFile)
\r
7983 if (currentMove >= forwardMostMove) {
\r
7984 gameMode = EditGame;
\r
7987 /* [AS] Clear current move marker at the end of a game */
\r
7988 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
7993 toX = moveList[currentMove][2] - AAA;
\r
7994 toY = moveList[currentMove][3] - ONE;
\r
7996 if (moveList[currentMove][1] == '@') {
\r
7997 if (appData.highlightLastMove) {
\r
7998 SetHighlights(-1, -1, toX, toY);
\r
8001 fromX = moveList[currentMove][0] - AAA;
\r
8002 fromY = moveList[currentMove][1] - ONE;
\r
8004 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8006 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8008 if (appData.highlightLastMove) {
\r
8009 SetHighlights(fromX, fromY, toX, toY);
\r
8012 DisplayMove(currentMove);
\r
8013 SendMoveToProgram(currentMove++, &first);
\r
8014 DisplayBothClocks();
\r
8015 DrawPosition(FALSE, boards[currentMove]);
\r
8016 // [HGM] PV info: always display, routine tests if empty
\r
8017 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8023 LoadGameOneMove(readAhead)
\r
8024 ChessMove readAhead;
\r
8026 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8027 char promoChar = NULLCHAR;
\r
8028 ChessMove moveType;
\r
8029 char move[MSG_SIZ];
\r
8032 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8033 gameMode != AnalyzeMode && gameMode != Training) {
\r
8034 gameFileFP = NULL;
\r
8038 yyboardindex = forwardMostMove;
\r
8039 if (readAhead != (ChessMove)0) {
\r
8040 moveType = readAhead;
\r
8042 if (gameFileFP == NULL)
\r
8044 moveType = (ChessMove) yylex();
\r
8048 switch (moveType) {
\r
8050 if (appData.debugMode)
\r
8051 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8053 if (*p == '{' || *p == '[' || *p == '(') {
\r
8054 p[strlen(p) - 1] = NULLCHAR;
\r
8058 /* append the comment but don't display it */
\r
8059 while (*p == '\n') p++;
\r
8060 AppendComment(currentMove, p);
\r
8063 case WhiteCapturesEnPassant:
\r
8064 case BlackCapturesEnPassant:
\r
8065 case WhitePromotionChancellor:
\r
8066 case BlackPromotionChancellor:
\r
8067 case WhitePromotionArchbishop:
\r
8068 case BlackPromotionArchbishop:
\r
8069 case WhitePromotionCentaur:
\r
8070 case BlackPromotionCentaur:
\r
8071 case WhitePromotionQueen:
\r
8072 case BlackPromotionQueen:
\r
8073 case WhitePromotionRook:
\r
8074 case BlackPromotionRook:
\r
8075 case WhitePromotionBishop:
\r
8076 case BlackPromotionBishop:
\r
8077 case WhitePromotionKnight:
\r
8078 case BlackPromotionKnight:
\r
8079 case WhitePromotionKing:
\r
8080 case BlackPromotionKing:
\r
8082 case WhiteKingSideCastle:
\r
8083 case WhiteQueenSideCastle:
\r
8084 case BlackKingSideCastle:
\r
8085 case BlackQueenSideCastle:
\r
8086 case WhiteKingSideCastleWild:
\r
8087 case WhiteQueenSideCastleWild:
\r
8088 case BlackKingSideCastleWild:
\r
8089 case BlackQueenSideCastleWild:
\r
8091 case WhiteHSideCastleFR:
\r
8092 case WhiteASideCastleFR:
\r
8093 case BlackHSideCastleFR:
\r
8094 case BlackASideCastleFR:
\r
8096 if (appData.debugMode)
\r
8097 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8098 fromX = currentMoveString[0] - AAA;
\r
8099 fromY = currentMoveString[1] - ONE;
\r
8100 toX = currentMoveString[2] - AAA;
\r
8101 toY = currentMoveString[3] - ONE;
\r
8102 promoChar = currentMoveString[4];
\r
8107 if (appData.debugMode)
\r
8108 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8109 fromX = moveType == WhiteDrop ?
\r
8110 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8111 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8112 fromY = DROP_RANK;
\r
8113 toX = currentMoveString[2] - AAA;
\r
8114 toY = currentMoveString[3] - ONE;
\r
8120 case GameUnfinished:
\r
8121 if (appData.debugMode)
\r
8122 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8123 p = strchr(yy_text, '{');
\r
8124 if (p == NULL) p = strchr(yy_text, '(');
\r
8127 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8129 q = strchr(p, *p == '{' ? '}' : ')');
\r
8130 if (q != NULL) *q = NULLCHAR;
\r
8133 GameEnds(moveType, p, GE_FILE);
\r
8135 if (cmailMsgLoaded) {
\r
8136 ClearHighlights();
\r
8137 flipView = WhiteOnMove(currentMove);
\r
8138 if (moveType == GameUnfinished) flipView = !flipView;
\r
8139 if (appData.debugMode)
\r
8140 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8144 case (ChessMove) 0: /* end of file */
\r
8145 if (appData.debugMode)
\r
8146 fprintf(debugFP, "Parser hit end of file\n");
\r
8147 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8148 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8152 case MT_CHECKMATE:
\r
8153 if (WhiteOnMove(currentMove)) {
\r
8154 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8156 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8159 case MT_STALEMATE:
\r
8160 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8166 case MoveNumberOne:
\r
8167 if (lastLoadGameStart == GNUChessGame) {
\r
8168 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8169 if (appData.debugMode)
\r
8170 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8171 yy_text, (int) moveType);
\r
8172 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8174 /* else fall thru */
\r
8177 case GNUChessGame:
\r
8179 /* Reached start of next game in file */
\r
8180 if (appData.debugMode)
\r
8181 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8182 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8183 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8187 case MT_CHECKMATE:
\r
8188 if (WhiteOnMove(currentMove)) {
\r
8189 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8191 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8194 case MT_STALEMATE:
\r
8195 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8201 case PositionDiagram: /* should not happen; ignore */
\r
8202 case ElapsedTime: /* ignore */
\r
8203 case NAG: /* ignore */
\r
8204 if (appData.debugMode)
\r
8205 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8206 yy_text, (int) moveType);
\r
8207 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8210 if (appData.testLegality) {
\r
8211 if (appData.debugMode)
\r
8212 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8213 sprintf(move, "Illegal move: %d.%s%s",
\r
8214 (forwardMostMove / 2) + 1,
\r
8215 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8216 DisplayError(move, 0);
\r
8219 if (appData.debugMode)
\r
8220 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8221 yy_text, currentMoveString);
\r
8222 fromX = currentMoveString[0] - AAA;
\r
8223 fromY = currentMoveString[1] - ONE;
\r
8224 toX = currentMoveString[2] - AAA;
\r
8225 toY = currentMoveString[3] - ONE;
\r
8226 promoChar = currentMoveString[4];
\r
8230 case AmbiguousMove:
\r
8231 if (appData.debugMode)
\r
8232 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8233 sprintf(move, "Ambiguous move: %d.%s%s",
\r
8234 (forwardMostMove / 2) + 1,
\r
8235 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8236 DisplayError(move, 0);
\r
8241 case ImpossibleMove:
\r
8242 if (appData.debugMode)
\r
8243 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8244 sprintf(move, "Illegal move: %d.%s%s",
\r
8245 (forwardMostMove / 2) + 1,
\r
8246 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8247 DisplayError(move, 0);
\r
8253 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8254 DrawPosition(FALSE, boards[currentMove]);
\r
8255 DisplayBothClocks();
\r
8256 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8257 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8259 (void) StopLoadGameTimer();
\r
8260 gameFileFP = NULL;
\r
8261 cmailOldMove = forwardMostMove;
\r
8264 /* currentMoveString is set as a side-effect of yylex */
\r
8265 strcat(currentMoveString, "\n");
\r
8266 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8268 thinkOutput[0] = NULLCHAR;
\r
8269 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8270 currentMove = forwardMostMove;
\r
8275 /* Load the nth game from the given file */
\r
8277 LoadGameFromFile(filename, n, title, useList)
\r
8281 /*Boolean*/ int useList;
\r
8284 char buf[MSG_SIZ];
\r
8286 if (strcmp(filename, "-") == 0) {
\r
8290 f = fopen(filename, "rb");
\r
8292 sprintf(buf, "Can't open \"%s\"", filename);
\r
8293 DisplayError(buf, errno);
\r
8297 if (fseek(f, 0, 0) == -1) {
\r
8298 /* f is not seekable; probably a pipe */
\r
8301 if (useList && n == 0) {
\r
8302 int error = GameListBuild(f);
\r
8304 DisplayError("Cannot build game list", error);
\r
8305 } else if (!ListEmpty(&gameList) &&
\r
8306 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8307 GameListPopUp(f, title);
\r
8310 GameListDestroy();
\r
8313 if (n == 0) n = 1;
\r
8314 return LoadGame(f, n, title, FALSE);
\r
8319 MakeRegisteredMove()
\r
8321 int fromX, fromY, toX, toY;
\r
8323 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8324 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8327 if (appData.debugMode)
\r
8328 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8329 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8331 thinkOutput[0] = NULLCHAR;
\r
8332 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8333 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8334 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8335 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8336 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8337 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8338 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8339 ShowMove(fromX, fromY, toX, toY);
\r
8341 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8342 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8347 case MT_CHECKMATE:
\r
8348 if (WhiteOnMove(currentMove)) {
\r
8349 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8351 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8355 case MT_STALEMATE:
\r
8356 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8362 case CMAIL_RESIGN:
\r
8363 if (WhiteOnMove(currentMove)) {
\r
8364 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8366 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8370 case CMAIL_ACCEPT:
\r
8371 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8382 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8384 CmailLoadGame(f, gameNumber, title, useList)
\r
8392 if (gameNumber > nCmailGames) {
\r
8393 DisplayError("No more games in this message", 0);
\r
8396 if (f == lastLoadGameFP) {
\r
8397 int offset = gameNumber - lastLoadGameNumber;
\r
8398 if (offset == 0) {
\r
8399 cmailMsg[0] = NULLCHAR;
\r
8400 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8401 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8402 nCmailMovesRegistered--;
\r
8404 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8405 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8406 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8409 if (! RegisterMove()) return FALSE;
\r
8413 retVal = LoadGame(f, gameNumber, title, useList);
\r
8415 /* Make move registered during previous look at this game, if any */
\r
8416 MakeRegisteredMove();
\r
8418 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8419 commentList[currentMove]
\r
8420 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8421 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8427 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8429 ReloadGame(offset)
\r
8432 int gameNumber = lastLoadGameNumber + offset;
\r
8433 if (lastLoadGameFP == NULL) {
\r
8434 DisplayError("No game has been loaded yet", 0);
\r
8437 if (gameNumber <= 0) {
\r
8438 DisplayError("Can't back up any further", 0);
\r
8441 if (cmailMsgLoaded) {
\r
8442 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8443 lastLoadGameTitle, lastLoadGameUseList);
\r
8445 return LoadGame(lastLoadGameFP, gameNumber,
\r
8446 lastLoadGameTitle, lastLoadGameUseList);
\r
8452 /* Load the nth game from open file f */
\r
8454 LoadGame(f, gameNumber, title, useList)
\r
8461 char buf[MSG_SIZ];
\r
8462 int gn = gameNumber;
\r
8463 ListGame *lg = NULL;
\r
8464 int numPGNTags = 0;
\r
8466 GameMode oldGameMode;
\r
8467 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8469 if (appData.debugMode)
\r
8470 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8472 if (gameMode == Training )
\r
8473 SetTrainingModeOff();
\r
8475 oldGameMode = gameMode;
\r
8476 if (gameMode != BeginningOfGame) {
\r
8477 Reset(FALSE, TRUE);
\r
8481 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8482 fclose(lastLoadGameFP);
\r
8486 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8489 fseek(f, lg->offset, 0);
\r
8490 GameListHighlight(gameNumber);
\r
8494 DisplayError("Game number out of range", 0);
\r
8498 GameListDestroy();
\r
8499 if (fseek(f, 0, 0) == -1) {
\r
8500 if (f == lastLoadGameFP ?
\r
8501 gameNumber == lastLoadGameNumber + 1 :
\r
8502 gameNumber == 1) {
\r
8505 DisplayError("Can't seek on game file", 0);
\r
8510 lastLoadGameFP = f;
\r
8511 lastLoadGameNumber = gameNumber;
\r
8512 strcpy(lastLoadGameTitle, title);
\r
8513 lastLoadGameUseList = useList;
\r
8517 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8518 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8519 lg->gameInfo.black);
\r
8520 DisplayTitle(buf);
\r
8521 } else if (*title != NULLCHAR) {
\r
8522 if (gameNumber > 1) {
\r
8523 sprintf(buf, "%s %d", title, gameNumber);
\r
8524 DisplayTitle(buf);
\r
8526 DisplayTitle(title);
\r
8530 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8531 gameMode = PlayFromGameFile;
\r
8535 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8536 CopyBoard(boards[0], initialPosition);
\r
8540 * Skip the first gn-1 games in the file.
\r
8541 * Also skip over anything that precedes an identifiable
\r
8542 * start of game marker, to avoid being confused by
\r
8543 * garbage at the start of the file. Currently
\r
8544 * recognized start of game markers are the move number "1",
\r
8545 * the pattern "gnuchess .* game", the pattern
\r
8546 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8547 * A game that starts with one of the latter two patterns
\r
8548 * will also have a move number 1, possibly
\r
8549 * following a position diagram.
\r
8550 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8551 * start with an unnumbered move. Does that break anything?
\r
8553 cm = lastLoadGameStart = (ChessMove) 0;
\r
8555 yyboardindex = forwardMostMove;
\r
8556 cm = (ChessMove) yylex();
\r
8558 case (ChessMove) 0:
\r
8559 if (cmailMsgLoaded) {
\r
8560 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8562 Reset(TRUE, TRUE);
\r
8563 DisplayError("Game not found in file", 0);
\r
8567 case GNUChessGame:
\r
8570 lastLoadGameStart = cm;
\r
8573 case MoveNumberOne:
\r
8574 switch (lastLoadGameStart) {
\r
8575 case GNUChessGame:
\r
8579 case MoveNumberOne:
\r
8580 case (ChessMove) 0:
\r
8581 gn--; /* count this game */
\r
8582 lastLoadGameStart = cm;
\r
8591 switch (lastLoadGameStart) {
\r
8592 case GNUChessGame:
\r
8594 case MoveNumberOne:
\r
8595 case (ChessMove) 0:
\r
8596 gn--; /* count this game */
\r
8597 lastLoadGameStart = cm;
\r
8600 lastLoadGameStart = cm; /* game counted already */
\r
8608 yyboardindex = forwardMostMove;
\r
8609 cm = (ChessMove) yylex();
\r
8610 } while (cm == PGNTag || cm == Comment);
\r
8617 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8618 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8619 != CMAIL_OLD_RESULT) {
\r
8620 nCmailResults ++ ;
\r
8621 cmailResult[ CMAIL_MAX_GAMES
\r
8622 - gn - 1] = CMAIL_OLD_RESULT;
\r
8628 /* Only a NormalMove can be at the start of a game
\r
8629 * without a position diagram. */
\r
8630 if (lastLoadGameStart == (ChessMove) 0) {
\r
8632 lastLoadGameStart = MoveNumberOne;
\r
8641 if (appData.debugMode)
\r
8642 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8644 if (cm == XBoardGame) {
\r
8645 /* Skip any header junk before position diagram and/or move 1 */
\r
8647 yyboardindex = forwardMostMove;
\r
8648 cm = (ChessMove) yylex();
\r
8650 if (cm == (ChessMove) 0 ||
\r
8651 cm == GNUChessGame || cm == XBoardGame) {
\r
8652 /* Empty game; pretend end-of-file and handle later */
\r
8653 cm = (ChessMove) 0;
\r
8657 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8658 cm == PGNTag || cm == Comment)
\r
8661 } else if (cm == GNUChessGame) {
\r
8662 if (gameInfo.event != NULL) {
\r
8663 free(gameInfo.event);
\r
8665 gameInfo.event = StrSave(yy_text);
\r
8668 startedFromSetupPosition = FALSE;
\r
8669 while (cm == PGNTag) {
\r
8670 if (appData.debugMode)
\r
8671 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8672 err = ParsePGNTag(yy_text, &gameInfo);
\r
8673 if (!err) numPGNTags++;
\r
8675 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8676 if(gameInfo.variant != oldVariant) {
\r
8677 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8678 InitPosition(TRUE);
\r
8679 oldVariant = gameInfo.variant;
\r
8680 if (appData.debugMode)
\r
8681 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8685 if (gameInfo.fen != NULL) {
\r
8686 Board initial_position;
\r
8687 startedFromSetupPosition = TRUE;
\r
8688 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8689 Reset(TRUE, TRUE);
\r
8690 DisplayError("Bad FEN position in file", 0);
\r
8693 CopyBoard(boards[0], initial_position);
\r
8694 if (blackPlaysFirst) {
\r
8695 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8696 CopyBoard(boards[1], initial_position);
\r
8697 strcpy(moveList[0], "");
\r
8698 strcpy(parseList[0], "");
\r
8699 timeRemaining[0][1] = whiteTimeRemaining;
\r
8700 timeRemaining[1][1] = blackTimeRemaining;
\r
8701 if (commentList[0] != NULL) {
\r
8702 commentList[1] = commentList[0];
\r
8703 commentList[0] = NULL;
\r
8706 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8708 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8710 initialRulePlies = FENrulePlies;
\r
8711 epStatus[forwardMostMove] = FENepStatus;
\r
8712 for( i=0; i< nrCastlingRights; i++ )
\r
8713 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8715 yyboardindex = forwardMostMove;
\r
8716 free(gameInfo.fen);
\r
8717 gameInfo.fen = NULL;
\r
8720 yyboardindex = forwardMostMove;
\r
8721 cm = (ChessMove) yylex();
\r
8723 /* Handle comments interspersed among the tags */
\r
8724 while (cm == Comment) {
\r
8726 if (appData.debugMode)
\r
8727 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8729 if (*p == '{' || *p == '[' || *p == '(') {
\r
8730 p[strlen(p) - 1] = NULLCHAR;
\r
8733 while (*p == '\n') p++;
\r
8734 AppendComment(currentMove, p);
\r
8735 yyboardindex = forwardMostMove;
\r
8736 cm = (ChessMove) yylex();
\r
8740 /* don't rely on existence of Event tag since if game was
\r
8741 * pasted from clipboard the Event tag may not exist
\r
8743 if (numPGNTags > 0){
\r
8745 if (gameInfo.variant == VariantNormal) {
\r
8746 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8749 if( appData.autoDisplayTags ) {
\r
8750 tags = PGNTags(&gameInfo);
\r
8751 TagsPopUp(tags, CmailMsg());
\r
8756 /* Make something up, but don't display it now */
\r
8761 if (cm == PositionDiagram) {
\r
8764 Board initial_position;
\r
8766 if (appData.debugMode)
\r
8767 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
8769 if (!startedFromSetupPosition) {
\r
8771 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
8772 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
8782 initial_position[i][j++] = CharToPiece(*p);
\r
8785 while (*p == ' ' || *p == '\t' ||
\r
8786 *p == '\n' || *p == '\r') p++;
\r
8788 if (strncmp(p, "black", strlen("black"))==0)
\r
8789 blackPlaysFirst = TRUE;
\r
8791 blackPlaysFirst = FALSE;
\r
8792 startedFromSetupPosition = TRUE;
\r
8794 CopyBoard(boards[0], initial_position);
\r
8795 if (blackPlaysFirst) {
\r
8796 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8797 CopyBoard(boards[1], initial_position);
\r
8798 strcpy(moveList[0], "");
\r
8799 strcpy(parseList[0], "");
\r
8800 timeRemaining[0][1] = whiteTimeRemaining;
\r
8801 timeRemaining[1][1] = blackTimeRemaining;
\r
8802 if (commentList[0] != NULL) {
\r
8803 commentList[1] = commentList[0];
\r
8804 commentList[0] = NULL;
\r
8807 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8810 yyboardindex = forwardMostMove;
\r
8811 cm = (ChessMove) yylex();
\r
8814 if (first.pr == NoProc) {
\r
8815 StartChessProgram(&first);
\r
8817 InitChessProgram(&first, FALSE);
\r
8818 SendToProgram("force\n", &first);
\r
8819 if (startedFromSetupPosition) {
\r
8820 SendBoard(&first, forwardMostMove);
\r
8821 if (appData.debugMode) {
\r
8822 fprintf(debugFP, "Load Game\n");
\r
8824 DisplayBothClocks();
\r
8827 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
8828 loadFlag = appData.suppressLoadMoves;
\r
8830 while (cm == Comment) {
\r
8832 if (appData.debugMode)
\r
8833 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8835 if (*p == '{' || *p == '[' || *p == '(') {
\r
8836 p[strlen(p) - 1] = NULLCHAR;
\r
8839 while (*p == '\n') p++;
\r
8840 AppendComment(currentMove, p);
\r
8841 yyboardindex = forwardMostMove;
\r
8842 cm = (ChessMove) yylex();
\r
8845 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
8846 cm == WhiteWins || cm == BlackWins ||
\r
8847 cm == GameIsDrawn || cm == GameUnfinished) {
\r
8848 DisplayMessage("", "No moves in game");
\r
8849 if (cmailMsgLoaded) {
\r
8850 if (appData.debugMode)
\r
8851 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
8852 ClearHighlights();
\r
8855 DrawPosition(FALSE, boards[currentMove]);
\r
8856 DisplayBothClocks();
\r
8857 gameMode = EditGame;
\r
8859 gameFileFP = NULL;
\r
8864 // [HGM] PV info: routine tests if comment empty
\r
8865 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
8866 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8868 if (!matchMode && appData.timeDelay != 0)
\r
8869 DrawPosition(FALSE, boards[currentMove]);
\r
8871 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
8872 programStats.ok_to_send = 1;
\r
8875 /* if the first token after the PGN tags is a move
\r
8876 * and not move number 1, retrieve it from the parser
\r
8878 if (cm != MoveNumberOne)
\r
8879 LoadGameOneMove(cm);
\r
8881 /* load the remaining moves from the file */
\r
8882 while (LoadGameOneMove((ChessMove)0)) {
\r
8883 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
8884 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
8887 /* rewind to the start of the game */
\r
8888 currentMove = backwardMostMove;
\r
8890 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8892 if (oldGameMode == AnalyzeFile ||
\r
8893 oldGameMode == AnalyzeMode) {
\r
8894 AnalyzeFileEvent();
\r
8897 if (matchMode || appData.timeDelay == 0) {
\r
8899 gameMode = EditGame;
\r
8901 } else if (appData.timeDelay > 0) {
\r
8902 AutoPlayGameLoop();
\r
8905 if (appData.debugMode)
\r
8906 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
8908 loadFlag = 0; /* [HGM] true game starts */
\r
8912 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
8914 ReloadPosition(offset)
\r
8917 int positionNumber = lastLoadPositionNumber + offset;
\r
8918 if (lastLoadPositionFP == NULL) {
\r
8919 DisplayError("No position has been loaded yet", 0);
\r
8922 if (positionNumber <= 0) {
\r
8923 DisplayError("Can't back up any further", 0);
\r
8926 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
8927 lastLoadPositionTitle);
\r
8930 /* Load the nth position from the given file */
\r
8932 LoadPositionFromFile(filename, n, title)
\r
8938 char buf[MSG_SIZ];
\r
8940 if (strcmp(filename, "-") == 0) {
\r
8941 return LoadPosition(stdin, n, "stdin");
\r
8943 f = fopen(filename, "rb");
\r
8945 sprintf(buf, "Can't open \"%s\"", filename);
\r
8946 DisplayError(buf, errno);
\r
8949 return LoadPosition(f, n, title);
\r
8954 /* Load the nth position from the given open file, and close it */
\r
8956 LoadPosition(f, positionNumber, title)
\r
8958 int positionNumber;
\r
8961 char *p, line[MSG_SIZ];
\r
8962 Board initial_position;
\r
8963 int i, j, fenMode, pn;
\r
8965 if (gameMode == Training )
\r
8966 SetTrainingModeOff();
\r
8968 if (gameMode != BeginningOfGame) {
\r
8969 Reset(FALSE, TRUE);
\r
8971 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
8972 fclose(lastLoadPositionFP);
\r
8974 if (positionNumber == 0) positionNumber = 1;
\r
8975 lastLoadPositionFP = f;
\r
8976 lastLoadPositionNumber = positionNumber;
\r
8977 strcpy(lastLoadPositionTitle, title);
\r
8978 if (first.pr == NoProc) {
\r
8979 StartChessProgram(&first);
\r
8980 InitChessProgram(&first, FALSE);
\r
8982 pn = positionNumber;
\r
8983 if (positionNumber < 0) {
\r
8984 /* Negative position number means to seek to that byte offset */
\r
8985 if (fseek(f, -positionNumber, 0) == -1) {
\r
8986 DisplayError("Can't seek on position file", 0);
\r
8991 if (fseek(f, 0, 0) == -1) {
\r
8992 if (f == lastLoadPositionFP ?
\r
8993 positionNumber == lastLoadPositionNumber + 1 :
\r
8994 positionNumber == 1) {
\r
8997 DisplayError("Can't seek on position file", 0);
\r
9002 /* See if this file is FEN or old-style xboard */
\r
9003 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9004 DisplayError("Position not found in file", 0);
\r
9008 switch (line[0]) {
\r
9009 case '#': case 'x':
\r
9013 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9014 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9015 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9016 case '7': case '8': case '9':
\r
9017 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9018 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9019 case 'C': case 'W': case 'c': case 'w':
\r
9024 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9025 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9029 if (fenMode || line[0] == '#') pn--;
\r
9031 /* skip positions before number pn */
\r
9032 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9033 Reset(TRUE, TRUE);
\r
9034 DisplayError("Position not found in file", 0);
\r
9037 if (fenMode || line[0] == '#') pn--;
\r
9042 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9043 DisplayError("Bad FEN position in file", 0);
\r
9047 (void) fgets(line, MSG_SIZ, f);
\r
9048 (void) fgets(line, MSG_SIZ, f);
\r
9050 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9051 (void) fgets(line, MSG_SIZ, f);
\r
9052 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9055 initial_position[i][j++] = CharToPiece(*p);
\r
9059 blackPlaysFirst = FALSE;
\r
9061 (void) fgets(line, MSG_SIZ, f);
\r
9062 if (strncmp(line, "black", strlen("black"))==0)
\r
9063 blackPlaysFirst = TRUE;
\r
9066 startedFromSetupPosition = TRUE;
\r
9068 SendToProgram("force\n", &first);
\r
9069 CopyBoard(boards[0], initial_position);
\r
9070 if (blackPlaysFirst) {
\r
9071 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9072 strcpy(moveList[0], "");
\r
9073 strcpy(parseList[0], "");
\r
9074 CopyBoard(boards[1], initial_position);
\r
9075 DisplayMessage("", "Black to play");
\r
9077 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9078 DisplayMessage("", "White to play");
\r
9080 /* [HGM] copy FEN attributes as well */
\r
9082 initialRulePlies = FENrulePlies;
\r
9083 epStatus[forwardMostMove] = FENepStatus;
\r
9084 for( i=0; i< nrCastlingRights; i++ )
\r
9085 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9087 SendBoard(&first, forwardMostMove);
\r
9088 if (appData.debugMode) {
\r
9090 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9091 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9092 fprintf(debugFP, "Load Position\n");
\r
9095 if (positionNumber > 1) {
\r
9096 sprintf(line, "%s %d", title, positionNumber);
\r
9097 DisplayTitle(line);
\r
9099 DisplayTitle(title);
\r
9101 gameMode = EditGame;
\r
9104 timeRemaining[0][1] = whiteTimeRemaining;
\r
9105 timeRemaining[1][1] = blackTimeRemaining;
\r
9106 DrawPosition(FALSE, boards[currentMove]);
\r
9113 CopyPlayerNameIntoFileName(dest, src)
\r
9114 char **dest, *src;
\r
9116 while (*src != NULLCHAR && *src != ',') {
\r
9117 if (*src == ' ') {
\r
9121 *(*dest)++ = *src++;
\r
9126 char *DefaultFileName(ext)
\r
9129 static char def[MSG_SIZ];
\r
9132 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9134 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9136 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9140 def[0] = NULLCHAR;
\r
9145 /* Save the current game to the given file */
\r
9147 SaveGameToFile(filename, append)
\r
9152 char buf[MSG_SIZ];
\r
9154 if (strcmp(filename, "-") == 0) {
\r
9155 return SaveGame(stdout, 0, NULL);
\r
9157 f = fopen(filename, append ? "a" : "w");
\r
9159 sprintf(buf, "Can't open \"%s\"", filename);
\r
9160 DisplayError(buf, errno);
\r
9163 return SaveGame(f, 0, NULL);
\r
9172 static char buf[MSG_SIZ];
\r
9175 p = strchr(str, ' ');
\r
9176 if (p == NULL) return str;
\r
9177 strncpy(buf, str, p - str);
\r
9178 buf[p - str] = NULLCHAR;
\r
9182 #define PGN_MAX_LINE 75
\r
9184 #define PGN_SIDE_WHITE 0
\r
9185 #define PGN_SIDE_BLACK 1
\r
9188 static int FindFirstMoveOutOfBook( int side )
\r
9192 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9193 int index = backwardMostMove;
\r
9194 int has_book_hit = 0;
\r
9196 if( (index % 2) != side ) {
\r
9200 while( index < forwardMostMove ) {
\r
9201 /* Check to see if engine is in book */
\r
9202 int depth = pvInfoList[index].depth;
\r
9203 int score = pvInfoList[index].score;
\r
9206 if( depth <= 2 ) {
\r
9209 else if( score == 0 && depth == 63 ) {
\r
9210 in_book = 1; /* Zappa */
\r
9212 else if( score == 2 && depth == 99 ) {
\r
9213 in_book = 1; /* Abrok */
\r
9216 has_book_hit += in_book;
\r
9232 void GetOutOfBookInfo( char * buf )
\r
9236 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9238 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9239 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9243 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9244 for( i=0; i<2; i++ ) {
\r
9248 if( i > 0 && oob[0] >= 0 ) {
\r
9249 strcat( buf, " " );
\r
9252 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9253 sprintf( buf+strlen(buf), "%s%.2f",
\r
9254 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9255 pvInfoList[idx].score / 100.0 );
\r
9261 /* Save game in PGN style and close the file */
\r
9266 int i, offset, linelen, newblock;
\r
9270 int movelen, numlen, blank;
\r
9271 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9273 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9275 tm = time((time_t *) NULL);
\r
9277 PrintPGNTags(f, &gameInfo);
\r
9279 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9280 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9281 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9282 fprintf(f, "\n{--------------\n");
\r
9283 PrintPosition(f, backwardMostMove);
\r
9284 fprintf(f, "--------------}\n");
\r
9288 /* [AS] Out of book annotation */
\r
9289 if( appData.saveOutOfBookInfo ) {
\r
9292 GetOutOfBookInfo( buf );
\r
9294 if( buf[0] != '\0' ) {
\r
9295 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9302 i = backwardMostMove;
\r
9306 while (i < forwardMostMove) {
\r
9307 /* Print comments preceding this move */
\r
9308 if (commentList[i] != NULL) {
\r
9309 if (linelen > 0) fprintf(f, "\n");
\r
9310 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9315 /* Format move number */
\r
9316 if ((i % 2) == 0) {
\r
9317 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9320 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9322 numtext[0] = NULLCHAR;
\r
9325 numlen = strlen(numtext);
\r
9328 /* Print move number */
\r
9329 blank = linelen > 0 && numlen > 0;
\r
9330 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9339 fprintf(f, numtext);
\r
9340 linelen += numlen;
\r
9343 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9346 blank = linelen > 0 && movelen > 0;
\r
9347 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9356 fprintf(f, parseList[i]);
\r
9357 linelen += movelen;
\r
9359 /* [AS] Add PV info if present */
\r
9360 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9361 /* [HGM] add time */
\r
9362 char buf[MSG_SIZ]; int seconds = 0;
\r
9365 if(i >= backwardMostMove) {
\r
9366 if(WhiteOnMove(i))
\r
9367 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9368 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9370 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9371 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9373 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9375 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9377 if (appData.debugMode,0) {
\r
9378 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9379 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9380 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9384 if( seconds <= 0) buf[0] = 0; else
\r
9385 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9386 seconds = (seconds + 4)/10; // round to full seconds
\r
9387 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9388 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9391 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9392 pvInfoList[i].score >= 0 ? "+" : "",
\r
9393 pvInfoList[i].score / 100.0,
\r
9394 pvInfoList[i].depth,
\r
9397 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9399 /* Print score/depth */
\r
9400 blank = linelen > 0 && movelen > 0;
\r
9401 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9410 fprintf(f, move_buffer);
\r
9411 linelen += movelen;
\r
9417 /* Start a new line */
\r
9418 if (linelen > 0) fprintf(f, "\n");
\r
9420 /* Print comments after last move */
\r
9421 if (commentList[i] != NULL) {
\r
9422 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9425 /* Print result */
\r
9426 if (gameInfo.resultDetails != NULL &&
\r
9427 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9428 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9429 PGNResult(gameInfo.result));
\r
9431 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9438 /* Save game in old style and close the file */
\r
9440 SaveGameOldStyle(f)
\r
9446 tm = time((time_t *) NULL);
\r
9448 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9449 PrintOpponents(f);
\r
9451 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9452 fprintf(f, "\n[--------------\n");
\r
9453 PrintPosition(f, backwardMostMove);
\r
9454 fprintf(f, "--------------]\n");
\r
9459 i = backwardMostMove;
\r
9460 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9462 while (i < forwardMostMove) {
\r
9463 if (commentList[i] != NULL) {
\r
9464 fprintf(f, "[%s]\n", commentList[i]);
\r
9467 if ((i % 2) == 1) {
\r
9468 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9471 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9473 if (commentList[i] != NULL) {
\r
9477 if (i >= forwardMostMove) {
\r
9481 fprintf(f, "%s\n", parseList[i]);
\r
9486 if (commentList[i] != NULL) {
\r
9487 fprintf(f, "[%s]\n", commentList[i]);
\r
9490 /* This isn't really the old style, but it's close enough */
\r
9491 if (gameInfo.resultDetails != NULL &&
\r
9492 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9493 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9494 gameInfo.resultDetails);
\r
9496 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9503 /* Save the current game to open file f and close the file */
\r
9505 SaveGame(f, dummy, dummy2)
\r
9510 if (gameMode == EditPosition) EditPositionDone();
\r
9511 if (appData.oldSaveStyle)
\r
9512 return SaveGameOldStyle(f);
\r
9514 return SaveGamePGN(f);
\r
9517 /* Save the current position to the given file */
\r
9519 SavePositionToFile(filename)
\r
9523 char buf[MSG_SIZ];
\r
9525 if (strcmp(filename, "-") == 0) {
\r
9526 return SavePosition(stdout, 0, NULL);
\r
9528 f = fopen(filename, "a");
\r
9530 sprintf(buf, "Can't open \"%s\"", filename);
\r
9531 DisplayError(buf, errno);
\r
9534 SavePosition(f, 0, NULL);
\r
9540 /* Save the current position to the given open file and close the file */
\r
9542 SavePosition(f, dummy, dummy2)
\r
9550 if (appData.oldSaveStyle) {
\r
9551 tm = time((time_t *) NULL);
\r
9553 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9554 PrintOpponents(f);
\r
9555 fprintf(f, "[--------------\n");
\r
9556 PrintPosition(f, currentMove);
\r
9557 fprintf(f, "--------------]\n");
\r
9559 fen = PositionToFEN(currentMove, 1);
\r
9560 fprintf(f, "%s\n", fen);
\r
9568 ReloadCmailMsgEvent(unregister)
\r
9572 static char *inFilename = NULL;
\r
9573 static char *outFilename;
\r
9575 struct stat inbuf, outbuf;
\r
9578 /* Any registered moves are unregistered if unregister is set, */
\r
9579 /* i.e. invoked by the signal handler */
\r
9581 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9582 cmailMoveRegistered[i] = FALSE;
\r
9583 if (cmailCommentList[i] != NULL) {
\r
9584 free(cmailCommentList[i]);
\r
9585 cmailCommentList[i] = NULL;
\r
9588 nCmailMovesRegistered = 0;
\r
9591 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9592 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9594 nCmailResults = 0;
\r
9596 if (inFilename == NULL) {
\r
9597 /* Because the filenames are static they only get malloced once */
\r
9598 /* and they never get freed */
\r
9599 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9600 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9602 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9603 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9606 status = stat(outFilename, &outbuf);
\r
9608 cmailMailedMove = FALSE;
\r
9610 status = stat(inFilename, &inbuf);
\r
9611 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9614 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9615 counts the games, notes how each one terminated, etc.
\r
9617 It would be nice to remove this kludge and instead gather all
\r
9618 the information while building the game list. (And to keep it
\r
9619 in the game list nodes instead of having a bunch of fixed-size
\r
9620 parallel arrays.) Note this will require getting each game's
\r
9621 termination from the PGN tags, as the game list builder does
\r
9622 not process the game moves. --mann
\r
9624 cmailMsgLoaded = TRUE;
\r
9625 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9627 /* Load first game in the file or popup game menu */
\r
9628 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9630 #endif /* !WIN32 */
\r
9638 char string[MSG_SIZ];
\r
9640 if ( cmailMailedMove
\r
9641 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9642 return TRUE; /* Allow free viewing */
\r
9645 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9646 /* with the move registered when the conditions for registering no */
\r
9648 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9649 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9650 nCmailMovesRegistered --;
\r
9652 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9654 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9655 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9659 if (cmailOldMove == -1) {
\r
9660 DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);
\r
9664 if (currentMove > cmailOldMove + 1) {
\r
9665 DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);
\r
9669 if (currentMove < cmailOldMove) {
\r
9670 DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);
\r
9674 if (forwardMostMove > currentMove) {
\r
9675 /* Silently truncate extra moves */
\r
9679 if ( (currentMove == cmailOldMove + 1)
\r
9680 || ( (currentMove == cmailOldMove)
\r
9681 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9682 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9683 if (gameInfo.result != GameUnfinished) {
\r
9684 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9687 if (commentList[currentMove] != NULL) {
\r
9688 cmailCommentList[lastLoadGameNumber - 1]
\r
9689 = StrSave(commentList[currentMove]);
\r
9691 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9693 if (appData.debugMode)
\r
9694 fprintf(debugFP, "Saving %s for game %d\n",
\r
9695 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9698 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9700 f = fopen(string, "w");
\r
9701 if (appData.oldSaveStyle) {
\r
9702 SaveGameOldStyle(f); /* also closes the file */
\r
9704 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9705 f = fopen(string, "w");
\r
9706 SavePosition(f, 0, NULL); /* also closes the file */
\r
9708 fprintf(f, "{--------------\n");
\r
9709 PrintPosition(f, currentMove);
\r
9710 fprintf(f, "--------------}\n\n");
\r
9712 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9715 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9716 nCmailMovesRegistered ++;
\r
9717 } else if (nCmailGames == 1) {
\r
9718 DisplayError("You have not made a move yet", 0);
\r
9729 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9730 FILE *commandOutput;
\r
9731 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9732 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9738 if (! cmailMsgLoaded) {
\r
9739 DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);
\r
9743 if (nCmailGames == nCmailResults) {
\r
9744 DisplayError("No unfinished games", 0);
\r
9748 #if CMAIL_PROHIBIT_REMAIL
\r
9749 if (cmailMailedMove) {
\r
9750 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
9751 DisplayError(msg, 0);
\r
9756 if (! (cmailMailedMove || RegisterMove())) return;
\r
9758 if ( cmailMailedMove
\r
9759 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9760 sprintf(string, partCommandString,
\r
9761 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9762 commandOutput = popen(string, "rb");
\r
9764 if (commandOutput == NULL) {
\r
9765 DisplayError("Failed to invoke cmail", 0);
\r
9767 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9768 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
9770 if (nBuffers > 1) {
\r
9771 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
9772 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
9773 nBytes = MSG_SIZ - 1;
\r
9775 (void) memcpy(msg, buffer, nBytes);
\r
9777 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
9779 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
9780 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
9783 for (i = 0; i < nCmailGames; i ++) {
\r
9784 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
9789 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
9791 sprintf(buffer, "%s/%s.%s.archive",
\r
9793 appData.cmailGameName,
\r
9795 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
9796 cmailMsgLoaded = FALSE;
\r
9800 DisplayInformation(msg);
\r
9801 pclose(commandOutput);
\r
9804 if ((*cmailMsg) != '\0') {
\r
9805 DisplayInformation(cmailMsg);
\r
9810 #endif /* !WIN32 */
\r
9819 int prependComma = 0;
\r
9821 char string[MSG_SIZ]; /* Space for game-list */
\r
9824 if (!cmailMsgLoaded) return "";
\r
9826 if (cmailMailedMove) {
\r
9827 sprintf(cmailMsg, "Waiting for reply from opponent\n");
\r
9829 /* Create a list of games left */
\r
9830 sprintf(string, "[");
\r
9831 for (i = 0; i < nCmailGames; i ++) {
\r
9832 if (! ( cmailMoveRegistered[i]
\r
9833 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
9834 if (prependComma) {
\r
9835 sprintf(number, ",%d", i + 1);
\r
9837 sprintf(number, "%d", i + 1);
\r
9841 strcat(string, number);
\r
9844 strcat(string, "]");
\r
9846 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
9847 switch (nCmailGames) {
\r
9850 "Still need to make move for game\n");
\r
9855 "Still need to make moves for both games\n");
\r
9860 "Still need to make moves for all %d games\n",
\r
9865 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
9868 "Still need to make a move for game %s\n",
\r
9873 if (nCmailResults == nCmailGames) {
\r
9874 sprintf(cmailMsg, "No unfinished games\n");
\r
9876 sprintf(cmailMsg, "Ready to send mail\n");
\r
9882 "Still need to make moves for games %s\n",
\r
9888 #endif /* WIN32 */
\r
9894 if (gameMode == Training)
\r
9895 SetTrainingModeOff();
\r
9897 Reset(TRUE, TRUE);
\r
9898 cmailMsgLoaded = FALSE;
\r
9899 if (appData.icsActive) {
\r
9900 SendToICS(ics_prefix);
\r
9901 SendToICS("refresh\n");
\r
9910 if (exiting > 2) {
\r
9911 /* Give up on clean exit */
\r
9914 if (exiting > 1) {
\r
9915 /* Keep trying for clean exit */
\r
9919 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
9921 if (telnetISR != NULL) {
\r
9922 RemoveInputSource(telnetISR);
\r
9924 if (icsPR != NoProc) {
\r
9925 DestroyChildProcess(icsPR, TRUE);
\r
9928 /* Save game if resource set and not already saved by GameEnds() */
\r
9929 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
9930 && forwardMostMove > 0) {
\r
9931 if (*appData.saveGameFile != NULLCHAR) {
\r
9932 SaveGameToFile(appData.saveGameFile, TRUE);
\r
9933 } else if (appData.autoSaveGames) {
\r
9936 if (*appData.savePositionFile != NULLCHAR) {
\r
9937 SavePositionToFile(appData.savePositionFile);
\r
9940 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
9942 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
9943 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
9945 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
9946 /* make sure this other one finishes before killing it! */
\r
9947 if(endingGame) { int count = 0;
\r
9948 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
9949 while(endingGame && count++ < 10) DoSleep(1);
\r
9950 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
9953 /* Kill off chess programs */
\r
9954 if (first.pr != NoProc) {
\r
9955 ExitAnalyzeMode();
\r
9957 DoSleep( appData.delayBeforeQuit );
\r
9958 SendToProgram("quit\n", &first);
\r
9959 DoSleep( appData.delayAfterQuit );
\r
9960 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
9962 if (second.pr != NoProc) {
\r
9963 DoSleep( appData.delayBeforeQuit );
\r
9964 SendToProgram("quit\n", &second);
\r
9965 DoSleep( appData.delayAfterQuit );
\r
9966 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
9968 if (first.isr != NULL) {
\r
9969 RemoveInputSource(first.isr);
\r
9971 if (second.isr != NULL) {
\r
9972 RemoveInputSource(second.isr);
\r
9975 ShutDownFrontEnd();
\r
9982 if (appData.debugMode)
\r
9983 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
9987 if (gameMode == MachinePlaysWhite ||
\r
9988 gameMode == MachinePlaysBlack) {
\r
9991 DisplayBothClocks();
\r
9993 if (gameMode == PlayFromGameFile) {
\r
9994 if (appData.timeDelay >= 0)
\r
9995 AutoPlayGameLoop();
\r
9996 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
9997 Reset(FALSE, TRUE);
\r
9998 SendToICS(ics_prefix);
\r
9999 SendToICS("refresh\n");
\r
10000 } else if (currentMove < forwardMostMove) {
\r
10001 ForwardInner(forwardMostMove);
\r
10003 pauseExamInvalid = FALSE;
\r
10005 switch (gameMode) {
\r
10008 case IcsExamining:
\r
10009 pauseExamForwardMostMove = forwardMostMove;
\r
10010 pauseExamInvalid = FALSE;
\r
10011 /* fall through */
\r
10012 case IcsObserving:
\r
10013 case IcsPlayingWhite:
\r
10014 case IcsPlayingBlack:
\r
10018 case PlayFromGameFile:
\r
10019 (void) StopLoadGameTimer();
\r
10023 case BeginningOfGame:
\r
10024 if (appData.icsActive) return;
\r
10025 /* else fall through */
\r
10026 case MachinePlaysWhite:
\r
10027 case MachinePlaysBlack:
\r
10028 case TwoMachinesPlay:
\r
10029 if (forwardMostMove == 0)
\r
10030 return; /* don't pause if no one has moved */
\r
10031 if ((gameMode == MachinePlaysWhite &&
\r
10032 !WhiteOnMove(forwardMostMove)) ||
\r
10033 (gameMode == MachinePlaysBlack &&
\r
10034 WhiteOnMove(forwardMostMove))) {
\r
10045 EditCommentEvent()
\r
10047 char title[MSG_SIZ];
\r
10049 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10050 strcpy(title, "Edit comment");
\r
10052 sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,
\r
10053 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10054 parseList[currentMove - 1]);
\r
10057 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10064 char *tags = PGNTags(&gameInfo);
\r
10065 EditTagsPopUp(tags);
\r
10070 AnalyzeModeEvent()
\r
10072 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10075 if (gameMode != AnalyzeFile) {
\r
10077 if (gameMode != EditGame) return;
\r
10078 ResurrectChessProgram();
\r
10079 SendToProgram("analyze\n", &first);
\r
10080 first.analyzing = TRUE;
\r
10081 /*first.maybeThinking = TRUE;*/
\r
10082 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10083 AnalysisPopUp("Analysis",
\r
10084 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
10086 gameMode = AnalyzeMode;
\r
10091 StartAnalysisClock();
\r
10092 GetTimeMark(&lastNodeCountTime);
\r
10093 lastNodeCount = 0;
\r
10097 AnalyzeFileEvent()
\r
10099 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10102 if (gameMode != AnalyzeMode) {
\r
10104 if (gameMode != EditGame) return;
\r
10105 ResurrectChessProgram();
\r
10106 SendToProgram("analyze\n", &first);
\r
10107 first.analyzing = TRUE;
\r
10108 /*first.maybeThinking = TRUE;*/
\r
10109 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10110 AnalysisPopUp("Analysis",
\r
10111 "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");
\r
10113 gameMode = AnalyzeFile;
\r
10118 StartAnalysisClock();
\r
10119 GetTimeMark(&lastNodeCountTime);
\r
10120 lastNodeCount = 0;
\r
10124 MachineWhiteEvent()
\r
10126 char buf[MSG_SIZ];
\r
10127 char *bookHit = NULL;
\r
10129 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10133 if (gameMode == PlayFromGameFile ||
\r
10134 gameMode == TwoMachinesPlay ||
\r
10135 gameMode == Training ||
\r
10136 gameMode == AnalyzeMode ||
\r
10137 gameMode == EndOfGame)
\r
10140 if (gameMode == EditPosition)
\r
10141 EditPositionDone();
\r
10143 if (!WhiteOnMove(currentMove)) {
\r
10144 DisplayError("It is not White's turn", 0);
\r
10148 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10149 ExitAnalyzeMode();
\r
10151 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10152 gameMode == AnalyzeFile)
\r
10155 ResurrectChessProgram(); /* in case it isn't running */
\r
10156 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10157 gameMode = MachinePlaysWhite;
\r
10160 gameMode = MachinePlaysWhite;
\r
10164 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10165 DisplayTitle(buf);
\r
10166 if (first.sendName) {
\r
10167 sprintf(buf, "name %s\n", gameInfo.black);
\r
10168 SendToProgram(buf, &first);
\r
10170 if (first.sendTime) {
\r
10171 if (first.useColors) {
\r
10172 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10174 SendTimeRemaining(&first, TRUE);
\r
10176 if (first.useColors) {
\r
10177 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10179 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10180 SetMachineThinkingEnables();
\r
10181 first.maybeThinking = TRUE;
\r
10184 if (appData.autoFlipView && !flipView) {
\r
10185 flipView = !flipView;
\r
10186 DrawPosition(FALSE, NULL);
\r
10189 if(bookHit) { // [HGM] book: simulate book reply
\r
10190 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10192 programStats.depth = programStats.nodes = programStats.time =
\r
10193 programStats.score = programStats.got_only_move = 0;
\r
10194 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
10196 strcpy(bookMove, "move ");
\r
10197 strcat(bookMove, bookHit);
\r
10198 HandleMachineMove(bookMove, &first);
\r
10203 MachineBlackEvent()
\r
10205 char buf[MSG_SIZ];
\r
10206 char *bookHit = NULL;
\r
10208 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10212 if (gameMode == PlayFromGameFile ||
\r
10213 gameMode == TwoMachinesPlay ||
\r
10214 gameMode == Training ||
\r
10215 gameMode == AnalyzeMode ||
\r
10216 gameMode == EndOfGame)
\r
10219 if (gameMode == EditPosition)
\r
10220 EditPositionDone();
\r
10222 if (WhiteOnMove(currentMove)) {
\r
10223 DisplayError("It is not Black's turn", 0);
\r
10227 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10228 ExitAnalyzeMode();
\r
10230 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10231 gameMode == AnalyzeFile)
\r
10234 ResurrectChessProgram(); /* in case it isn't running */
\r
10235 gameMode = MachinePlaysBlack;
\r
10239 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10240 DisplayTitle(buf);
\r
10241 if (first.sendName) {
\r
10242 sprintf(buf, "name %s\n", gameInfo.white);
\r
10243 SendToProgram(buf, &first);
\r
10245 if (first.sendTime) {
\r
10246 if (first.useColors) {
\r
10247 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10249 SendTimeRemaining(&first, FALSE);
\r
10251 if (first.useColors) {
\r
10252 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10254 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10255 SetMachineThinkingEnables();
\r
10256 first.maybeThinking = TRUE;
\r
10259 if (appData.autoFlipView && flipView) {
\r
10260 flipView = !flipView;
\r
10261 DrawPosition(FALSE, NULL);
\r
10263 if(bookHit) { // [HGM] book: simulate book reply
\r
10264 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10266 programStats.depth = programStats.nodes = programStats.time =
\r
10267 programStats.score = programStats.got_only_move = 0;
\r
10268 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
10270 strcpy(bookMove, "move ");
\r
10271 strcat(bookMove, bookHit);
\r
10272 HandleMachineMove(bookMove, &first);
\r
10278 DisplayTwoMachinesTitle()
\r
10280 char buf[MSG_SIZ];
\r
10281 if (appData.matchGames > 0) {
\r
10282 if (first.twoMachinesColor[0] == 'w') {
\r
10283 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10284 gameInfo.white, gameInfo.black,
\r
10285 first.matchWins, second.matchWins,
\r
10286 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10288 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10289 gameInfo.white, gameInfo.black,
\r
10290 second.matchWins, first.matchWins,
\r
10291 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10294 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10296 DisplayTitle(buf);
\r
10300 TwoMachinesEvent P((void))
\r
10303 char buf[MSG_SIZ];
\r
10304 ChessProgramState *onmove;
\r
10305 char *bookHit = NULL;
\r
10307 if (appData.noChessProgram) return;
\r
10309 switch (gameMode) {
\r
10310 case TwoMachinesPlay:
\r
10312 case MachinePlaysWhite:
\r
10313 case MachinePlaysBlack:
\r
10314 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10315 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
10318 /* fall through */
\r
10319 case BeginningOfGame:
\r
10320 case PlayFromGameFile:
\r
10323 if (gameMode != EditGame) return;
\r
10325 case EditPosition:
\r
10326 EditPositionDone();
\r
10328 case AnalyzeMode:
\r
10329 case AnalyzeFile:
\r
10330 ExitAnalyzeMode();
\r
10337 forwardMostMove = currentMove;
\r
10338 ResurrectChessProgram(); /* in case first program isn't running */
\r
10340 if (second.pr == NULL) {
\r
10341 StartChessProgram(&second);
\r
10342 if (second.protocolVersion == 1) {
\r
10343 TwoMachinesEventIfReady();
\r
10345 /* kludge: allow timeout for initial "feature" command */
\r
10347 DisplayMessage("", "Starting second chess program");
\r
10348 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10352 DisplayMessage("", "");
\r
10353 InitChessProgram(&second, FALSE);
\r
10354 SendToProgram("force\n", &second);
\r
10355 if (startedFromSetupPosition) {
\r
10356 SendBoard(&second, backwardMostMove);
\r
10357 if (appData.debugMode) {
\r
10358 fprintf(debugFP, "Two Machines\n");
\r
10361 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10362 SendMoveToProgram(i, &second);
\r
10365 gameMode = TwoMachinesPlay;
\r
10369 DisplayTwoMachinesTitle();
\r
10370 firstMove = TRUE;
\r
10371 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10374 onmove = &second;
\r
10377 SendToProgram(first.computerString, &first);
\r
10378 if (first.sendName) {
\r
10379 sprintf(buf, "name %s\n", second.tidy);
\r
10380 SendToProgram(buf, &first);
\r
10382 SendToProgram(second.computerString, &second);
\r
10383 if (second.sendName) {
\r
10384 sprintf(buf, "name %s\n", first.tidy);
\r
10385 SendToProgram(buf, &second);
\r
10389 if (!first.sendTime || !second.sendTime) {
\r
10390 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10391 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10393 if (onmove->sendTime) {
\r
10394 if (onmove->useColors) {
\r
10395 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10397 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10399 if (onmove->useColors) {
\r
10400 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10402 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10403 // SendToProgram("go\n", onmove);
\r
10404 onmove->maybeThinking = TRUE;
\r
10405 SetMachineThinkingEnables();
\r
10409 if(bookHit) { // [HGM] book: simulate book reply
\r
10410 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10412 programStats.depth = programStats.nodes = programStats.time =
\r
10413 programStats.score = programStats.got_only_move = 0;
\r
10414 sprintf(programStats.movelist, "%s (xbook)", bookMove);
\r
10416 strcpy(bookMove, "move ");
\r
10417 strcat(bookMove, bookHit);
\r
10418 HandleMachineMove(bookMove, &first);
\r
10425 if (gameMode == Training) {
\r
10426 SetTrainingModeOff();
\r
10427 gameMode = PlayFromGameFile;
\r
10428 DisplayMessage("", "Training mode off");
\r
10430 gameMode = Training;
\r
10431 animateTraining = appData.animate;
\r
10433 /* make sure we are not already at the end of the game */
\r
10434 if (currentMove < forwardMostMove) {
\r
10435 SetTrainingModeOn();
\r
10436 DisplayMessage("", "Training mode on");
\r
10438 gameMode = PlayFromGameFile;
\r
10439 DisplayError("Already at end of game", 0);
\r
10448 if (!appData.icsActive) return;
\r
10449 switch (gameMode) {
\r
10450 case IcsPlayingWhite:
\r
10451 case IcsPlayingBlack:
\r
10452 case IcsObserving:
\r
10454 case BeginningOfGame:
\r
10455 case IcsExamining:
\r
10461 case EditPosition:
\r
10462 EditPositionDone();
\r
10465 case AnalyzeMode:
\r
10466 case AnalyzeFile:
\r
10467 ExitAnalyzeMode();
\r
10475 gameMode = IcsIdle;
\r
10486 switch (gameMode) {
\r
10488 SetTrainingModeOff();
\r
10490 case MachinePlaysWhite:
\r
10491 case MachinePlaysBlack:
\r
10492 case BeginningOfGame:
\r
10493 SendToProgram("force\n", &first);
\r
10494 SetUserThinkingEnables();
\r
10496 case PlayFromGameFile:
\r
10497 (void) StopLoadGameTimer();
\r
10498 if (gameFileFP != NULL) {
\r
10499 gameFileFP = NULL;
\r
10502 case EditPosition:
\r
10503 EditPositionDone();
\r
10505 case AnalyzeMode:
\r
10506 case AnalyzeFile:
\r
10507 ExitAnalyzeMode();
\r
10508 SendToProgram("force\n", &first);
\r
10510 case TwoMachinesPlay:
\r
10511 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10512 ResurrectChessProgram();
\r
10513 SetUserThinkingEnables();
\r
10516 ResurrectChessProgram();
\r
10518 case IcsPlayingBlack:
\r
10519 case IcsPlayingWhite:
\r
10520 DisplayError("Warning: You are still playing a game", 0);
\r
10522 case IcsObserving:
\r
10523 DisplayError("Warning: You are still observing a game", 0);
\r
10525 case IcsExamining:
\r
10526 DisplayError("Warning: You are still examining a game", 0);
\r
10537 first.offeredDraw = second.offeredDraw = 0;
\r
10539 if (gameMode == PlayFromGameFile) {
\r
10540 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10541 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10542 DisplayTitle("");
\r
10545 if (gameMode == MachinePlaysWhite ||
\r
10546 gameMode == MachinePlaysBlack ||
\r
10547 gameMode == TwoMachinesPlay ||
\r
10548 gameMode == EndOfGame) {
\r
10549 i = forwardMostMove;
\r
10550 while (i > currentMove) {
\r
10551 SendToProgram("undo\n", &first);
\r
10554 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10555 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10556 DisplayBothClocks();
\r
10557 if (whiteFlag || blackFlag) {
\r
10558 whiteFlag = blackFlag = 0;
\r
10560 DisplayTitle("");
\r
10563 gameMode = EditGame;
\r
10570 EditPositionEvent()
\r
10572 if (gameMode == EditPosition) {
\r
10578 if (gameMode != EditGame) return;
\r
10580 gameMode = EditPosition;
\r
10583 if (currentMove > 0)
\r
10584 CopyBoard(boards[0], boards[currentMove]);
\r
10586 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10588 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10589 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10594 ExitAnalyzeMode()
\r
10596 if (first.analysisSupport && first.analyzing) {
\r
10597 SendToProgram("exit\n", &first);
\r
10598 first.analyzing = FALSE;
\r
10600 AnalysisPopDown();
\r
10601 thinkOutput[0] = NULLCHAR;
\r
10605 EditPositionDone()
\r
10607 startedFromSetupPosition = TRUE;
\r
10608 InitChessProgram(&first, FALSE);
\r
10609 SendToProgram("force\n", &first);
\r
10610 if (blackPlaysFirst) {
\r
10611 strcpy(moveList[0], "");
\r
10612 strcpy(parseList[0], "");
\r
10613 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10614 CopyBoard(boards[1], boards[0]);
\r
10615 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10617 epStatus[1] = epStatus[0];
\r
10618 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10621 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10623 SendBoard(&first, forwardMostMove);
\r
10624 if (appData.debugMode) {
\r
10625 fprintf(debugFP, "EditPosDone\n");
\r
10627 DisplayTitle("");
\r
10628 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10629 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10630 gameMode = EditGame;
\r
10632 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10633 ClearHighlights(); /* [AS] */
\r
10636 /* Pause for `ms' milliseconds */
\r
10637 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10644 GetTimeMark(&m1);
\r
10646 GetTimeMark(&m2);
\r
10647 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10650 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10652 SendMultiLineToICS(buf)
\r
10655 char temp[MSG_SIZ+1], *p;
\r
10658 len = strlen(buf);
\r
10659 if (len > MSG_SIZ)
\r
10662 strncpy(temp, buf, len);
\r
10667 if (*p == '\n' || *p == '\r')
\r
10672 strcat(temp, "\n");
\r
10674 SendToPlayer(temp, strlen(temp));
\r
10678 SetWhiteToPlayEvent()
\r
10680 if (gameMode == EditPosition) {
\r
10681 blackPlaysFirst = FALSE;
\r
10682 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10683 } else if (gameMode == IcsExamining) {
\r
10684 SendToICS(ics_prefix);
\r
10685 SendToICS("tomove white\n");
\r
10690 SetBlackToPlayEvent()
\r
10692 if (gameMode == EditPosition) {
\r
10693 blackPlaysFirst = TRUE;
\r
10694 currentMove = 1; /* kludge */
\r
10695 DisplayBothClocks();
\r
10697 } else if (gameMode == IcsExamining) {
\r
10698 SendToICS(ics_prefix);
\r
10699 SendToICS("tomove black\n");
\r
10704 EditPositionMenuEvent(selection, x, y)
\r
10705 ChessSquare selection;
\r
10708 char buf[MSG_SIZ];
\r
10709 ChessSquare piece = boards[0][y][x];
\r
10711 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10713 switch (selection) {
\r
10715 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10716 SendToICS(ics_prefix);
\r
10717 SendToICS("bsetup clear\n");
\r
10718 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10719 SendToICS(ics_prefix);
\r
10720 SendToICS("clearboard\n");
\r
10722 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10723 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10724 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10725 if (gameMode == IcsExamining) {
\r
10726 if (boards[currentMove][y][x] != EmptySquare) {
\r
10727 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10728 AAA + x, ONE + y);
\r
10732 boards[0][y][x] = p;
\r
10737 if (gameMode == EditPosition) {
\r
10738 DrawPosition(FALSE, boards[0]);
\r
10743 SetWhiteToPlayEvent();
\r
10747 SetBlackToPlayEvent();
\r
10750 case EmptySquare:
\r
10751 if (gameMode == IcsExamining) {
\r
10752 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10755 boards[0][y][x] = EmptySquare;
\r
10756 DrawPosition(FALSE, boards[0]);
\r
10760 case PromotePiece:
\r
10761 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
10762 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
10763 selection = (ChessSquare) (PROMOTED piece);
\r
10764 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
10765 else selection = (ChessSquare)((int)piece - 1);
\r
10766 goto defaultlabel;
\r
10768 case DemotePiece:
\r
10769 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
10770 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
10771 selection = (ChessSquare) (DEMOTED piece);
\r
10772 } else if(piece == EmptySquare) selection = BlackSilver;
\r
10773 else selection = (ChessSquare)((int)piece + 1);
\r
10774 goto defaultlabel;
\r
10778 if(gameInfo.variant == VariantShatranj ||
\r
10779 gameInfo.variant == VariantXiangqi ||
\r
10780 gameInfo.variant == VariantCourier )
\r
10781 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
10782 goto defaultlabel;
\r
10786 if(gameInfo.variant == VariantXiangqi)
\r
10787 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
10788 if(gameInfo.variant == VariantKnightmate)
\r
10789 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
10792 if (gameMode == IcsExamining) {
\r
10793 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
10794 PieceToChar(selection), AAA + x, ONE + y);
\r
10797 boards[0][y][x] = selection;
\r
10798 DrawPosition(FALSE, boards[0]);
\r
10806 DropMenuEvent(selection, x, y)
\r
10807 ChessSquare selection;
\r
10810 ChessMove moveType;
\r
10812 switch (gameMode) {
\r
10813 case IcsPlayingWhite:
\r
10814 case MachinePlaysBlack:
\r
10815 if (!WhiteOnMove(currentMove)) {
\r
10816 DisplayMoveError("It is Black's turn");
\r
10819 moveType = WhiteDrop;
\r
10821 case IcsPlayingBlack:
\r
10822 case MachinePlaysWhite:
\r
10823 if (WhiteOnMove(currentMove)) {
\r
10824 DisplayMoveError("It is White's turn");
\r
10827 moveType = BlackDrop;
\r
10830 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
10836 if (moveType == BlackDrop && selection < BlackPawn) {
\r
10837 selection = (ChessSquare) ((int) selection
\r
10838 + (int) BlackPawn - (int) WhitePawn);
\r
10840 if (boards[currentMove][y][x] != EmptySquare) {
\r
10841 DisplayMoveError("That square is occupied");
\r
10845 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
10851 /* Accept a pending offer of any kind from opponent */
\r
10853 if (appData.icsActive) {
\r
10854 SendToICS(ics_prefix);
\r
10855 SendToICS("accept\n");
\r
10856 } else if (cmailMsgLoaded) {
\r
10857 if (currentMove == cmailOldMove &&
\r
10858 commentList[cmailOldMove] != NULL &&
\r
10859 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10860 "Black offers a draw" : "White offers a draw")) {
\r
10862 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10863 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10865 DisplayError("There is no pending offer on this move", 0);
\r
10866 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10869 /* Not used for offers from chess program */
\r
10876 /* Decline a pending offer of any kind from opponent */
\r
10878 if (appData.icsActive) {
\r
10879 SendToICS(ics_prefix);
\r
10880 SendToICS("decline\n");
\r
10881 } else if (cmailMsgLoaded) {
\r
10882 if (currentMove == cmailOldMove &&
\r
10883 commentList[cmailOldMove] != NULL &&
\r
10884 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10885 "Black offers a draw" : "White offers a draw")) {
\r
10887 AppendComment(cmailOldMove, "Draw declined");
\r
10888 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
10889 #endif /*NOTDEF*/
\r
10891 DisplayError("There is no pending offer on this move", 0);
\r
10894 /* Not used for offers from chess program */
\r
10901 /* Issue ICS rematch command */
\r
10902 if (appData.icsActive) {
\r
10903 SendToICS(ics_prefix);
\r
10904 SendToICS("rematch\n");
\r
10911 /* Call your opponent's flag (claim a win on time) */
\r
10912 if (appData.icsActive) {
\r
10913 SendToICS(ics_prefix);
\r
10914 SendToICS("flag\n");
\r
10916 switch (gameMode) {
\r
10919 case MachinePlaysWhite:
\r
10922 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10925 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
10927 DisplayError("Your opponent is not out of time", 0);
\r
10930 case MachinePlaysBlack:
\r
10933 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
10936 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
10938 DisplayError("Your opponent is not out of time", 0);
\r
10948 /* Offer draw or accept pending draw offer from opponent */
\r
10950 if (appData.icsActive) {
\r
10951 /* Note: tournament rules require draw offers to be
\r
10952 made after you make your move but before you punch
\r
10953 your clock. Currently ICS doesn't let you do that;
\r
10954 instead, you immediately punch your clock after making
\r
10955 a move, but you can offer a draw at any time. */
\r
10957 SendToICS(ics_prefix);
\r
10958 SendToICS("draw\n");
\r
10959 } else if (cmailMsgLoaded) {
\r
10960 if (currentMove == cmailOldMove &&
\r
10961 commentList[cmailOldMove] != NULL &&
\r
10962 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
10963 "Black offers a draw" : "White offers a draw")) {
\r
10964 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
10965 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
10966 } else if (currentMove == cmailOldMove + 1) {
\r
10967 char *offer = WhiteOnMove(cmailOldMove) ?
\r
10968 "White offers a draw" : "Black offers a draw";
\r
10969 AppendComment(currentMove, offer);
\r
10970 DisplayComment(currentMove - 1, offer);
\r
10971 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
10973 DisplayError("You must make your move before offering a draw", 0);
\r
10974 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
10976 } else if (first.offeredDraw) {
\r
10977 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
10979 if (first.sendDrawOffers) {
\r
10980 SendToProgram("draw\n", &first);
\r
10981 userOfferedDraw = TRUE;
\r
10989 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
10991 if (appData.icsActive) {
\r
10992 SendToICS(ics_prefix);
\r
10993 SendToICS("adjourn\n");
\r
10995 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11003 /* Offer Abort or accept pending Abort offer from opponent */
\r
11005 if (appData.icsActive) {
\r
11006 SendToICS(ics_prefix);
\r
11007 SendToICS("abort\n");
\r
11009 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11016 /* Resign. You can do this even if it's not your turn. */
\r
11018 if (appData.icsActive) {
\r
11019 SendToICS(ics_prefix);
\r
11020 SendToICS("resign\n");
\r
11022 switch (gameMode) {
\r
11023 case MachinePlaysWhite:
\r
11024 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11026 case MachinePlaysBlack:
\r
11027 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11030 if (cmailMsgLoaded) {
\r
11032 if (WhiteOnMove(cmailOldMove)) {
\r
11033 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11035 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11037 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11048 StopObservingEvent()
\r
11050 /* Stop observing current games */
\r
11051 SendToICS(ics_prefix);
\r
11052 SendToICS("unobserve\n");
\r
11056 StopExaminingEvent()
\r
11058 /* Stop observing current game */
\r
11059 SendToICS(ics_prefix);
\r
11060 SendToICS("unexamine\n");
\r
11064 ForwardInner(target)
\r
11069 if (appData.debugMode)
\r
11070 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11071 target, currentMove, forwardMostMove);
\r
11073 if (gameMode == EditPosition)
\r
11076 if (gameMode == PlayFromGameFile && !pausing)
\r
11079 if (gameMode == IcsExamining && pausing)
\r
11080 limit = pauseExamForwardMostMove;
\r
11082 limit = forwardMostMove;
\r
11084 if (target > limit) target = limit;
\r
11086 if (target > 0 && moveList[target - 1][0]) {
\r
11087 int fromX, fromY, toX, toY;
\r
11088 toX = moveList[target - 1][2] - AAA;
\r
11089 toY = moveList[target - 1][3] - ONE;
\r
11090 if (moveList[target - 1][1] == '@') {
\r
11091 if (appData.highlightLastMove) {
\r
11092 SetHighlights(-1, -1, toX, toY);
\r
11095 fromX = moveList[target - 1][0] - AAA;
\r
11096 fromY = moveList[target - 1][1] - ONE;
\r
11097 if (target == currentMove + 1) {
\r
11098 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11100 if (appData.highlightLastMove) {
\r
11101 SetHighlights(fromX, fromY, toX, toY);
\r
11105 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11106 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11107 gameMode == AnalyzeFile) {
\r
11108 while (currentMove < target) {
\r
11109 SendMoveToProgram(currentMove++, &first);
\r
11112 currentMove = target;
\r
11115 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11116 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11117 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11119 DisplayBothClocks();
\r
11120 DisplayMove(currentMove - 1);
\r
11121 DrawPosition(FALSE, boards[currentMove]);
\r
11122 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11123 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11124 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11132 if (gameMode == IcsExamining && !pausing) {
\r
11133 SendToICS(ics_prefix);
\r
11134 SendToICS("forward\n");
\r
11136 ForwardInner(currentMove + 1);
\r
11143 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11144 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11145 * the remaining moves to the engine. Otherwise we get analysis output
\r
11146 * after each move.
\r
11148 if (first.analysisSupport) {
\r
11149 SendToProgram("exit\nforce\n", &first);
\r
11150 first.analyzing = FALSE;
\r
11154 if (gameMode == IcsExamining && !pausing) {
\r
11155 SendToICS(ics_prefix);
\r
11156 SendToICS("forward 999999\n");
\r
11158 ForwardInner(forwardMostMove);
\r
11161 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11162 /* we have fed all the moves, so reactivate analysis mode */
\r
11163 SendToProgram("analyze\n", &first);
\r
11164 first.analyzing = TRUE;
\r
11165 /*first.maybeThinking = TRUE;*/
\r
11166 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11171 BackwardInner(target)
\r
11174 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11176 if (appData.debugMode)
\r
11177 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11178 target, currentMove, forwardMostMove);
\r
11180 if (gameMode == EditPosition) return;
\r
11181 if (currentMove <= backwardMostMove) {
\r
11182 ClearHighlights();
\r
11183 DrawPosition(full_redraw, boards[currentMove]);
\r
11186 if (gameMode == PlayFromGameFile && !pausing)
\r
11189 if (moveList[target][0]) {
\r
11190 int fromX, fromY, toX, toY;
\r
11191 toX = moveList[target][2] - AAA;
\r
11192 toY = moveList[target][3] - ONE;
\r
11193 if (moveList[target][1] == '@') {
\r
11194 if (appData.highlightLastMove) {
\r
11195 SetHighlights(-1, -1, toX, toY);
\r
11198 fromX = moveList[target][0] - AAA;
\r
11199 fromY = moveList[target][1] - ONE;
\r
11200 if (target == currentMove - 1) {
\r
11201 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11203 if (appData.highlightLastMove) {
\r
11204 SetHighlights(fromX, fromY, toX, toY);
\r
11208 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11209 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11210 while (currentMove > target) {
\r
11211 SendToProgram("undo\n", &first);
\r
11215 currentMove = target;
\r
11218 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11219 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11220 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11222 DisplayBothClocks();
\r
11223 DisplayMove(currentMove - 1);
\r
11224 DrawPosition(full_redraw, boards[currentMove]);
\r
11225 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11226 // [HGM] PV info: routine tests if comment empty
\r
11227 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11233 if (gameMode == IcsExamining && !pausing) {
\r
11234 SendToICS(ics_prefix);
\r
11235 SendToICS("backward\n");
\r
11237 BackwardInner(currentMove - 1);
\r
11244 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11245 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11246 * all the moves. Otherwise we get analysis output after each undo.
\r
11248 if (first.analysisSupport) {
\r
11249 SendToProgram("exit\nforce\n", &first);
\r
11250 first.analyzing = FALSE;
\r
11254 if (gameMode == IcsExamining && !pausing) {
\r
11255 SendToICS(ics_prefix);
\r
11256 SendToICS("backward 999999\n");
\r
11258 BackwardInner(backwardMostMove);
\r
11261 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11262 /* we have fed all the moves, so reactivate analysis mode */
\r
11263 SendToProgram("analyze\n", &first);
\r
11264 first.analyzing = TRUE;
\r
11265 /*first.maybeThinking = TRUE;*/
\r
11266 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11271 ToNrEvent(int to)
\r
11273 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11274 if (to >= forwardMostMove) to = forwardMostMove;
\r
11275 if (to <= backwardMostMove) to = backwardMostMove;
\r
11276 if (to < currentMove) {
\r
11277 BackwardInner(to);
\r
11279 ForwardInner(to);
\r
11286 if (gameMode != IcsExamining) {
\r
11287 DisplayError("You are not examining a game", 0);
\r
11291 DisplayError("You can't revert while pausing", 0);
\r
11294 SendToICS(ics_prefix);
\r
11295 SendToICS("revert\n");
\r
11299 RetractMoveEvent()
\r
11301 switch (gameMode) {
\r
11302 case MachinePlaysWhite:
\r
11303 case MachinePlaysBlack:
\r
11304 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11305 DisplayError("Wait until your turn,\nor select Move Now", 0);
\r
11308 if (forwardMostMove < 2) return;
\r
11309 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11310 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11311 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11312 DisplayBothClocks();
\r
11313 DisplayMove(currentMove - 1);
\r
11314 ClearHighlights();/*!! could figure this out*/
\r
11315 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11316 SendToProgram("remove\n", &first);
\r
11317 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11320 case BeginningOfGame:
\r
11324 case IcsPlayingWhite:
\r
11325 case IcsPlayingBlack:
\r
11326 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11327 SendToICS(ics_prefix);
\r
11328 SendToICS("takeback 2\n");
\r
11330 SendToICS(ics_prefix);
\r
11331 SendToICS("takeback 1\n");
\r
11340 ChessProgramState *cps;
\r
11342 switch (gameMode) {
\r
11343 case MachinePlaysWhite:
\r
11344 if (!WhiteOnMove(forwardMostMove)) {
\r
11345 DisplayError("It is your turn", 0);
\r
11350 case MachinePlaysBlack:
\r
11351 if (WhiteOnMove(forwardMostMove)) {
\r
11352 DisplayError("It is your turn", 0);
\r
11357 case TwoMachinesPlay:
\r
11358 if (WhiteOnMove(forwardMostMove) ==
\r
11359 (first.twoMachinesColor[0] == 'w')) {
\r
11365 case BeginningOfGame:
\r
11369 SendToProgram("?\n", cps);
\r
11373 TruncateGameEvent()
\r
11376 if (gameMode != EditGame) return;
\r
11383 if (forwardMostMove > currentMove) {
\r
11384 if (gameInfo.resultDetails != NULL) {
\r
11385 free(gameInfo.resultDetails);
\r
11386 gameInfo.resultDetails = NULL;
\r
11387 gameInfo.result = GameUnfinished;
\r
11389 forwardMostMove = currentMove;
\r
11390 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11398 if (appData.noChessProgram) return;
\r
11399 switch (gameMode) {
\r
11400 case MachinePlaysWhite:
\r
11401 if (WhiteOnMove(forwardMostMove)) {
\r
11402 DisplayError("Wait until your turn", 0);
\r
11406 case BeginningOfGame:
\r
11407 case MachinePlaysBlack:
\r
11408 if (!WhiteOnMove(forwardMostMove)) {
\r
11409 DisplayError("Wait until your turn", 0);
\r
11414 DisplayError("No hint available", 0);
\r
11417 SendToProgram("hint\n", &first);
\r
11418 hintRequested = TRUE;
\r
11424 if (appData.noChessProgram) return;
\r
11425 switch (gameMode) {
\r
11426 case MachinePlaysWhite:
\r
11427 if (WhiteOnMove(forwardMostMove)) {
\r
11428 DisplayError("Wait until your turn", 0);
\r
11432 case BeginningOfGame:
\r
11433 case MachinePlaysBlack:
\r
11434 if (!WhiteOnMove(forwardMostMove)) {
\r
11435 DisplayError("Wait until your turn", 0);
\r
11439 case EditPosition:
\r
11440 EditPositionDone();
\r
11442 case TwoMachinesPlay:
\r
11447 SendToProgram("bk\n", &first);
\r
11448 bookOutput[0] = NULLCHAR;
\r
11449 bookRequested = TRUE;
\r
11455 char *tags = PGNTags(&gameInfo);
\r
11456 TagsPopUp(tags, CmailMsg());
\r
11460 /* end button procedures */
\r
11463 PrintPosition(fp, move)
\r
11469 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11470 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11471 char c = PieceToChar(boards[move][i][j]);
\r
11472 fputc(c == 'x' ? '.' : c, fp);
\r
11473 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11476 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11477 fprintf(fp, "white to play\n");
\r
11479 fprintf(fp, "black to play\n");
\r
11483 PrintOpponents(fp)
\r
11486 if (gameInfo.white != NULL) {
\r
11487 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11489 fprintf(fp, "\n");
\r
11493 /* Find last component of program's own name, using some heuristics */
\r
11495 TidyProgramName(prog, host, buf)
\r
11496 char *prog, *host, buf[MSG_SIZ];
\r
11499 int local = (strcmp(host, "localhost") == 0);
\r
11500 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11502 while (*p == ' ') p++;
\r
11505 if (*prog == '"' || *prog == '\'') {
\r
11506 q = strchr(prog + 1, *prog);
\r
11508 q = strchr(prog, ' ');
\r
11510 if (q == NULL) q = prog + strlen(prog);
\r
11512 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11514 if(p == prog && *p == '"') p++;
\r
11515 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11516 memcpy(buf, p, q - p);
\r
11517 buf[q - p] = NULLCHAR;
\r
11519 strcat(buf, "@");
\r
11520 strcat(buf, host);
\r
11525 TimeControlTagValue()
\r
11527 char buf[MSG_SIZ];
\r
11528 if (!appData.clockMode) {
\r
11529 strcpy(buf, "-");
\r
11530 } else if (movesPerSession > 0) {
\r
11531 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11532 } else if (timeIncrement == 0) {
\r
11533 sprintf(buf, "%ld", timeControl/1000);
\r
11535 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11537 return StrSave(buf);
\r
11543 /* This routine is used only for certain modes */
\r
11544 VariantClass v = gameInfo.variant;
\r
11545 ClearGameInfo(&gameInfo);
\r
11546 gameInfo.variant = v;
\r
11548 switch (gameMode) {
\r
11549 case MachinePlaysWhite:
\r
11550 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11551 gameInfo.site = StrSave(HostName());
\r
11552 gameInfo.date = PGNDate();
\r
11553 gameInfo.round = StrSave("-");
\r
11554 gameInfo.white = StrSave(first.tidy);
\r
11555 gameInfo.black = StrSave(UserName());
\r
11556 gameInfo.timeControl = TimeControlTagValue();
\r
11559 case MachinePlaysBlack:
\r
11560 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11561 gameInfo.site = StrSave(HostName());
\r
11562 gameInfo.date = PGNDate();
\r
11563 gameInfo.round = StrSave("-");
\r
11564 gameInfo.white = StrSave(UserName());
\r
11565 gameInfo.black = StrSave(first.tidy);
\r
11566 gameInfo.timeControl = TimeControlTagValue();
\r
11569 case TwoMachinesPlay:
\r
11570 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11571 gameInfo.site = StrSave(HostName());
\r
11572 gameInfo.date = PGNDate();
\r
11573 if (matchGame > 0) {
\r
11574 char buf[MSG_SIZ];
\r
11575 sprintf(buf, "%d", matchGame);
\r
11576 gameInfo.round = StrSave(buf);
\r
11578 gameInfo.round = StrSave("-");
\r
11580 if (first.twoMachinesColor[0] == 'w') {
\r
11581 gameInfo.white = StrSave(first.tidy);
\r
11582 gameInfo.black = StrSave(second.tidy);
\r
11584 gameInfo.white = StrSave(second.tidy);
\r
11585 gameInfo.black = StrSave(first.tidy);
\r
11587 gameInfo.timeControl = TimeControlTagValue();
\r
11591 gameInfo.event = StrSave("Edited game");
\r
11592 gameInfo.site = StrSave(HostName());
\r
11593 gameInfo.date = PGNDate();
\r
11594 gameInfo.round = StrSave("-");
\r
11595 gameInfo.white = StrSave("-");
\r
11596 gameInfo.black = StrSave("-");
\r
11599 case EditPosition:
\r
11600 gameInfo.event = StrSave("Edited position");
\r
11601 gameInfo.site = StrSave(HostName());
\r
11602 gameInfo.date = PGNDate();
\r
11603 gameInfo.round = StrSave("-");
\r
11604 gameInfo.white = StrSave("-");
\r
11605 gameInfo.black = StrSave("-");
\r
11608 case IcsPlayingWhite:
\r
11609 case IcsPlayingBlack:
\r
11610 case IcsObserving:
\r
11611 case IcsExamining:
\r
11614 case PlayFromGameFile:
\r
11615 gameInfo.event = StrSave("Game from non-PGN file");
\r
11616 gameInfo.site = StrSave(HostName());
\r
11617 gameInfo.date = PGNDate();
\r
11618 gameInfo.round = StrSave("-");
\r
11619 gameInfo.white = StrSave("?");
\r
11620 gameInfo.black = StrSave("?");
\r
11629 ReplaceComment(index, text)
\r
11635 while (*text == '\n') text++;
\r
11636 len = strlen(text);
\r
11637 while (len > 0 && text[len - 1] == '\n') len--;
\r
11639 if (commentList[index] != NULL)
\r
11640 free(commentList[index]);
\r
11643 commentList[index] = NULL;
\r
11646 commentList[index] = (char *) malloc(len + 2);
\r
11647 strncpy(commentList[index], text, len);
\r
11648 commentList[index][len] = '\n';
\r
11649 commentList[index][len + 1] = NULLCHAR;
\r
11662 if (ch == '\r') continue;
\r
11664 } while (ch != '\0');
\r
11668 AppendComment(index, text)
\r
11675 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11678 while (*text == '\n') text++;
\r
11679 len = strlen(text);
\r
11680 while (len > 0 && text[len - 1] == '\n') len--;
\r
11682 if (len == 0) return;
\r
11684 if (commentList[index] != NULL) {
\r
11685 old = commentList[index];
\r
11686 oldlen = strlen(old);
\r
11687 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11688 strcpy(commentList[index], old);
\r
11690 strncpy(&commentList[index][oldlen], text, len);
\r
11691 commentList[index][oldlen + len] = '\n';
\r
11692 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11694 commentList[index] = (char *) malloc(len + 2);
\r
11695 strncpy(commentList[index], text, len);
\r
11696 commentList[index][len] = '\n';
\r
11697 commentList[index][len + 1] = NULLCHAR;
\r
11701 static char * FindStr( char * text, char * sub_text )
\r
11703 char * result = strstr( text, sub_text );
\r
11705 if( result != NULL ) {
\r
11706 result += strlen( sub_text );
\r
11712 /* [AS] Try to extract PV info from PGN comment */
\r
11713 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11714 char *GetInfoFromComment( int index, char * text )
\r
11716 char * sep = text;
\r
11718 if( text != NULL && index > 0 ) {
\r
11721 int time = -1, sec = 0, deci;
\r
11722 char * s_eval = FindStr( text, "[%eval " );
\r
11723 char * s_emt = FindStr( text, "[%emt " );
\r
11725 if( s_eval != NULL || s_emt != NULL ) {
\r
11729 if( s_eval != NULL ) {
\r
11730 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11734 if( delim != ']' ) {
\r
11739 if( s_emt != NULL ) {
\r
11743 /* We expect something like: [+|-]nnn.nn/dd */
\r
11744 int score_lo = 0;
\r
11746 sep = strchr( text, '/' );
\r
11747 if( sep == NULL || sep < (text+4) ) {
\r
11751 time = -1; sec = -1; deci = -1;
\r
11752 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11753 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11754 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11755 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
11759 if( score_lo < 0 || score_lo >= 100 ) {
\r
11763 if(sec >= 0) time = 600*time + 10*sec; else
\r
11764 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
11766 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
11768 /* [HGM] PV time: now locate end of PV info */
\r
11769 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
11771 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
11773 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
11775 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
11776 while(*sep == ' ') sep++;
\r
11779 if( depth <= 0 ) {
\r
11787 pvInfoList[index-1].depth = depth;
\r
11788 pvInfoList[index-1].score = score;
\r
11789 pvInfoList[index-1].time = 10*time; // centi-sec
\r
11795 SendToProgram(message, cps)
\r
11797 ChessProgramState *cps;
\r
11799 int count, outCount, error;
\r
11800 char buf[MSG_SIZ];
\r
11802 if (cps->pr == NULL) return;
\r
11805 if (appData.debugMode) {
\r
11807 GetTimeMark(&now);
\r
11808 fprintf(debugFP, "%ld >%-6s: %s",
\r
11809 SubtractTimeMarks(&now, &programStartTime),
\r
11810 cps->which, message);
\r
11813 count = strlen(message);
\r
11814 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
11815 if (outCount < count && !exiting
\r
11816 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
11817 sprintf(buf, "Error writing to %s chess program", cps->which);
\r
11818 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11819 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11820 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11821 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11823 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11825 gameInfo.resultDetails = buf;
\r
11827 DisplayFatalError(buf, error, 1);
\r
11832 ReceiveFromProgram(isr, closure, message, count, error)
\r
11833 InputSourceRef isr;
\r
11834 VOIDSTAR closure;
\r
11840 char buf[MSG_SIZ];
\r
11841 ChessProgramState *cps = (ChessProgramState *)closure;
\r
11843 if (isr != cps->isr) return; /* Killed intentionally */
\r
11844 if (count <= 0) {
\r
11845 if (count == 0) {
\r
11847 "Error: %s chess program (%s) exited unexpectedly",
\r
11848 cps->which, cps->program);
\r
11849 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
11850 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
11851 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
11852 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
11854 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
11856 gameInfo.resultDetails = buf;
\r
11858 RemoveInputSource(cps->isr);
\r
11859 DisplayFatalError(buf, 0, 1);
\r
11862 "Error reading from %s chess program (%s)",
\r
11863 cps->which, cps->program);
\r
11864 RemoveInputSource(cps->isr);
\r
11866 /* [AS] Program is misbehaving badly... kill it */
\r
11867 if( count == -2 ) {
\r
11868 DestroyChildProcess( cps->pr, 9 );
\r
11869 cps->pr = NoProc;
\r
11872 DisplayFatalError(buf, error, 1);
\r
11877 if ((end_str = strchr(message, '\r')) != NULL)
\r
11878 *end_str = NULLCHAR;
\r
11879 if ((end_str = strchr(message, '\n')) != NULL)
\r
11880 *end_str = NULLCHAR;
\r
11882 if (appData.debugMode) {
\r
11883 TimeMark now; int print = 1;
\r
11884 char *quote = ""; char c; int i;
\r
11886 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
11887 char start = message[0];
\r
11888 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
11889 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
11890 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
11891 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
11892 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
11893 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
11894 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
11895 { quote = "# "; print = (appData.engineComments == 2); }
\r
11896 message[0] = start; // restore original message
\r
11899 GetTimeMark(&now);
\r
11900 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
11901 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
11906 HandleMachineMove(message, cps);
\r
11911 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
11912 ChessProgramState *cps;
\r
11913 int mps, inc, sd, st;
\r
11916 char buf[MSG_SIZ];
\r
11919 if( timeControl_2 > 0 ) {
\r
11920 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
11921 tc = timeControl_2;
\r
11924 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
11925 inc /= cps->timeOdds;
\r
11926 st /= cps->timeOdds;
\r
11928 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
11931 /* Set exact time per move, normally using st command */
\r
11932 if (cps->stKludge) {
\r
11933 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
11934 seconds = st % 60;
\r
11935 if (seconds == 0) {
\r
11936 sprintf(buf, "level 1 %d\n", st/60);
\r
11938 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
11941 sprintf(buf, "st %d\n", st);
\r
11944 /* Set conventional or incremental time control, using level command */
\r
11945 if (seconds == 0) {
\r
11946 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
11947 Fixed in later versions, but still avoid :seconds
\r
11948 when seconds is 0. */
\r
11949 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
11951 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
11952 seconds, inc/1000);
\r
11955 SendToProgram(buf, cps);
\r
11957 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
11958 /* Orthogonally, limit search to given depth */
\r
11960 if (cps->sdKludge) {
\r
11961 sprintf(buf, "depth\n%d\n", sd);
\r
11963 sprintf(buf, "sd %d\n", sd);
\r
11965 SendToProgram(buf, cps);
\r
11968 if(cps->nps > 0) { /* [HGM] nps */
\r
11969 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
11971 sprintf(buf, "nps %d\n", cps->nps);
\r
11972 SendToProgram(buf, cps);
\r
11977 ChessProgramState *WhitePlayer()
\r
11978 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
11980 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
11981 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
11987 SendTimeRemaining(cps, machineWhite)
\r
11988 ChessProgramState *cps;
\r
11989 int /*boolean*/ machineWhite;
\r
11991 char message[MSG_SIZ];
\r
11992 long time, otime;
\r
11994 /* Note: this routine must be called when the clocks are stopped
\r
11995 or when they have *just* been set or switched; otherwise
\r
11996 it will be off by the time since the current tick started.
\r
11998 if (machineWhite) {
\r
11999 time = whiteTimeRemaining / 10;
\r
12000 otime = blackTimeRemaining / 10;
\r
12002 time = blackTimeRemaining / 10;
\r
12003 otime = whiteTimeRemaining / 10;
\r
12005 /* [HGM] translate opponent's time by time-odds factor */
\r
12006 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12007 if (appData.debugMode) {
\r
12008 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12011 if (time <= 0) time = 1;
\r
12012 if (otime <= 0) otime = 1;
\r
12014 sprintf(message, "time %ld\n", time);
\r
12015 SendToProgram(message, cps);
\r
12017 sprintf(message, "otim %ld\n", otime);
\r
12018 SendToProgram(message, cps);
\r
12022 BoolFeature(p, name, loc, cps)
\r
12026 ChessProgramState *cps;
\r
12028 char buf[MSG_SIZ];
\r
12029 int len = strlen(name);
\r
12031 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12033 sscanf(*p, "%d", &val);
\r
12034 *loc = (val != 0);
\r
12035 while (**p && **p != ' ') (*p)++;
\r
12036 sprintf(buf, "accepted %s\n", name);
\r
12037 SendToProgram(buf, cps);
\r
12044 IntFeature(p, name, loc, cps)
\r
12048 ChessProgramState *cps;
\r
12050 char buf[MSG_SIZ];
\r
12051 int len = strlen(name);
\r
12052 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12054 sscanf(*p, "%d", loc);
\r
12055 while (**p && **p != ' ') (*p)++;
\r
12056 sprintf(buf, "accepted %s\n", name);
\r
12057 SendToProgram(buf, cps);
\r
12064 StringFeature(p, name, loc, cps)
\r
12068 ChessProgramState *cps;
\r
12070 char buf[MSG_SIZ];
\r
12071 int len = strlen(name);
\r
12072 if (strncmp((*p), name, len) == 0
\r
12073 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12075 sscanf(*p, "%[^\"]", loc);
\r
12076 while (**p && **p != '\"') (*p)++;
\r
12077 if (**p == '\"') (*p)++;
\r
12078 sprintf(buf, "accepted %s\n", name);
\r
12079 SendToProgram(buf, cps);
\r
12086 FeatureDone(cps, val)
\r
12087 ChessProgramState* cps;
\r
12090 DelayedEventCallback cb = GetDelayedEvent();
\r
12091 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12092 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12093 CancelDelayedEvent();
\r
12094 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12096 cps->initDone = val;
\r
12099 /* Parse feature command from engine */
\r
12101 ParseFeatures(args, cps)
\r
12103 ChessProgramState *cps;
\r
12108 char buf[MSG_SIZ];
\r
12111 while (*p == ' ') p++;
\r
12112 if (*p == NULLCHAR) return;
\r
12114 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12115 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12116 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12117 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12118 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12119 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12120 /* Engine can disable reuse, but can't enable it if user said no */
\r
12121 if (!val) cps->reuse = FALSE;
\r
12124 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12125 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12126 if (gameMode == TwoMachinesPlay) {
\r
12127 DisplayTwoMachinesTitle();
\r
12129 DisplayTitle("");
\r
12133 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12134 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12135 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12136 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12137 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12138 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12139 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12140 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12141 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12142 if (IntFeature(&p, "done", &val, cps)) {
\r
12143 FeatureDone(cps, val);
\r
12146 /* Added by Tord: */
\r
12147 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12148 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12149 /* End of additions by Tord */
\r
12151 /* [HGM] added features: */
\r
12152 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12153 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12154 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12155 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12156 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12157 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12158 /* End of additions by HGM */
\r
12160 /* unknown feature: complain and skip */
\r
12162 while (*q && *q != '=') q++;
\r
12163 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12164 SendToProgram(buf, cps);
\r
12168 if (*p == '\"') {
\r
12170 while (*p && *p != '\"') p++;
\r
12171 if (*p == '\"') p++;
\r
12173 while (*p && *p != ' ') p++;
\r
12181 PeriodicUpdatesEvent(newState)
\r
12184 if (newState == appData.periodicUpdates)
\r
12187 appData.periodicUpdates=newState;
\r
12189 /* Display type changes, so update it now */
\r
12190 DisplayAnalysis();
\r
12192 /* Get the ball rolling again... */
\r
12194 AnalysisPeriodicEvent(1);
\r
12195 StartAnalysisClock();
\r
12200 PonderNextMoveEvent(newState)
\r
12203 if (newState == appData.ponderNextMove) return;
\r
12204 if (gameMode == EditPosition) EditPositionDone();
\r
12206 SendToProgram("hard\n", &first);
\r
12207 if (gameMode == TwoMachinesPlay) {
\r
12208 SendToProgram("hard\n", &second);
\r
12211 SendToProgram("easy\n", &first);
\r
12212 thinkOutput[0] = NULLCHAR;
\r
12213 if (gameMode == TwoMachinesPlay) {
\r
12214 SendToProgram("easy\n", &second);
\r
12217 appData.ponderNextMove = newState;
\r
12221 NewSettingEvent(option, command, value)
\r
12223 int option, value;
\r
12225 char buf[MSG_SIZ];
\r
12227 if (gameMode == EditPosition) EditPositionDone();
\r
12228 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12229 SendToProgram(buf, &first);
\r
12230 if (gameMode == TwoMachinesPlay) {
\r
12231 SendToProgram(buf, &second);
\r
12236 ShowThinkingEvent()
\r
12237 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12239 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12240 int newState = appData.showThinking
\r
12241 // [HGM] thinking: other features now need thinking output as well
\r
12242 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12244 if (oldState == newState) return;
\r
12245 oldState = newState;
\r
12246 if (gameMode == EditPosition) EditPositionDone();
\r
12248 SendToProgram("post\n", &first);
\r
12249 if (gameMode == TwoMachinesPlay) {
\r
12250 SendToProgram("post\n", &second);
\r
12253 SendToProgram("nopost\n", &first);
\r
12254 thinkOutput[0] = NULLCHAR;
\r
12255 if (gameMode == TwoMachinesPlay) {
\r
12256 SendToProgram("nopost\n", &second);
\r
12259 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12263 AskQuestionEvent(title, question, replyPrefix, which)
\r
12264 char *title; char *question; char *replyPrefix; char *which;
\r
12266 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12267 if (pr == NoProc) return;
\r
12268 AskQuestion(title, question, replyPrefix, pr);
\r
12272 DisplayMove(moveNumber)
\r
12275 char message[MSG_SIZ];
\r
12276 char res[MSG_SIZ];
\r
12277 char cpThinkOutput[MSG_SIZ];
\r
12279 if (moveNumber == forwardMostMove - 1 ||
\r
12280 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12282 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12284 if (strchr(cpThinkOutput, '\n')) {
\r
12285 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12288 *cpThinkOutput = NULLCHAR;
\r
12291 /* [AS] Hide thinking from human user */
\r
12292 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12293 *cpThinkOutput = NULLCHAR;
\r
12294 if( thinkOutput[0] != NULLCHAR ) {
\r
12297 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12298 cpThinkOutput[i] = '.';
\r
12300 cpThinkOutput[i] = NULLCHAR;
\r
12301 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12305 if (moveNumber == forwardMostMove - 1 &&
\r
12306 gameInfo.resultDetails != NULL) {
\r
12307 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12308 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12310 sprintf(res, " {%s} %s",
\r
12311 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12314 res[0] = NULLCHAR;
\r
12317 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12318 DisplayMessage(res, cpThinkOutput);
\r
12320 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12321 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12322 parseList[moveNumber], res);
\r
12323 DisplayMessage(message, cpThinkOutput);
\r
12328 DisplayAnalysisText(text)
\r
12331 char buf[MSG_SIZ];
\r
12333 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12334 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12335 AnalysisPopUp(buf, text);
\r
12340 only_one_move(str)
\r
12343 while (*str && isspace(*str)) ++str;
\r
12344 while (*str && !isspace(*str)) ++str;
\r
12345 if (!*str) return 1;
\r
12346 while (*str && isspace(*str)) ++str;
\r
12347 if (!*str) return 1;
\r
12352 DisplayAnalysis()
\r
12354 char buf[MSG_SIZ];
\r
12355 char lst[MSG_SIZ / 2];
\r
12357 static char *xtra[] = { "", " (--)", " (++)" };
\r
12360 if (programStats.time == 0) {
\r
12361 programStats.time = 1;
\r
12364 if (programStats.got_only_move) {
\r
12365 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12367 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12369 nps = (((double)programStats.nodes) /
\r
12370 (((double)programStats.time)/100.0));
\r
12372 cs = programStats.time % 100;
\r
12373 s = programStats.time / 100;
\r
12374 h = (s / (60*60));
\r
12379 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12380 if (programStats.move_name[0] != NULLCHAR) {
\r
12381 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12382 programStats.depth,
\r
12383 programStats.nr_moves-programStats.moves_left,
\r
12384 programStats.nr_moves, programStats.move_name,
\r
12385 ((float)programStats.score)/100.0, lst,
\r
12386 only_one_move(lst)?
\r
12387 xtra[programStats.got_fail] : "",
\r
12388 programStats.nodes, (int)nps, h, m, s, cs);
\r
12390 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12391 programStats.depth,
\r
12392 programStats.nr_moves-programStats.moves_left,
\r
12393 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12395 only_one_move(lst)?
\r
12396 xtra[programStats.got_fail] : "",
\r
12397 programStats.nodes, (int)nps, h, m, s, cs);
\r
12400 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12401 programStats.depth,
\r
12402 ((float)programStats.score)/100.0,
\r
12404 only_one_move(lst)?
\r
12405 xtra[programStats.got_fail] : "",
\r
12406 programStats.nodes, (int)nps, h, m, s, cs);
\r
12409 DisplayAnalysisText(buf);
\r
12413 DisplayComment(moveNumber, text)
\r
12417 char title[MSG_SIZ];
\r
12418 char buf[8000]; // comment can be long!
\r
12419 int score, depth;
\r
12421 if( appData.autoDisplayComment ) {
\r
12422 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12423 strcpy(title, "Comment");
\r
12425 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12426 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12427 parseList[moveNumber]);
\r
12429 } else title[0] = 0;
\r
12431 // [HGM] PV info: display PV info together with (or as) comment
\r
12432 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12433 if(text == NULL) text = "";
\r
12434 score = pvInfoList[moveNumber].score;
\r
12435 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12436 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12437 CommentPopUp(title, buf);
\r
12439 if (text != NULL)
\r
12440 CommentPopUp(title, text);
\r
12443 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12444 * might be busy thinking or pondering. It can be omitted if your
\r
12445 * gnuchess is configured to stop thinking immediately on any user
\r
12446 * input. However, that gnuchess feature depends on the FIONREAD
\r
12447 * ioctl, which does not work properly on some flavors of Unix.
\r
12451 ChessProgramState *cps;
\r
12454 if (!cps->useSigint) return;
\r
12455 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12456 switch (gameMode) {
\r
12457 case MachinePlaysWhite:
\r
12458 case MachinePlaysBlack:
\r
12459 case TwoMachinesPlay:
\r
12460 case IcsPlayingWhite:
\r
12461 case IcsPlayingBlack:
\r
12462 case AnalyzeMode:
\r
12463 case AnalyzeFile:
\r
12464 /* Skip if we know it isn't thinking */
\r
12465 if (!cps->maybeThinking) return;
\r
12466 if (appData.debugMode)
\r
12467 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12468 InterruptChildProcess(cps->pr);
\r
12469 cps->maybeThinking = FALSE;
\r
12474 #endif /*ATTENTION*/
\r
12480 if (whiteTimeRemaining <= 0) {
\r
12481 if (!whiteFlag) {
\r
12482 whiteFlag = TRUE;
\r
12483 if (appData.icsActive) {
\r
12484 if (appData.autoCallFlag &&
\r
12485 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12486 SendToICS(ics_prefix);
\r
12487 SendToICS("flag\n");
\r
12491 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
12493 if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");
\r
12494 if (appData.autoCallFlag) {
\r
12495 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12502 if (blackTimeRemaining <= 0) {
\r
12503 if (!blackFlag) {
\r
12504 blackFlag = TRUE;
\r
12505 if (appData.icsActive) {
\r
12506 if (appData.autoCallFlag &&
\r
12507 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12508 SendToICS(ics_prefix);
\r
12509 SendToICS("flag\n");
\r
12513 if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");
\r
12515 if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");
\r
12516 if (appData.autoCallFlag) {
\r
12517 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12528 CheckTimeControl()
\r
12530 if (!appData.clockMode || appData.icsActive ||
\r
12531 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12534 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12536 if ( !WhiteOnMove(forwardMostMove) )
\r
12537 /* White made time control */
\r
12538 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12539 /* [HGM] time odds: correct new time quota for time odds! */
\r
12540 / WhitePlayer()->timeOdds;
\r
12542 /* Black made time control */
\r
12543 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12544 / WhitePlayer()->other->timeOdds;
\r
12548 DisplayBothClocks()
\r
12550 int wom = gameMode == EditPosition ?
\r
12551 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12552 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12553 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12557 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12558 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12559 to use other calls if you don't. Clocks will be less accurate if
\r
12560 you have neither ftime nor gettimeofday.
\r
12563 /* Get the current time as a TimeMark */
\r
12568 #if HAVE_GETTIMEOFDAY
\r
12570 struct timeval timeVal;
\r
12571 struct timezone timeZone;
\r
12573 gettimeofday(&timeVal, &timeZone);
\r
12574 tm->sec = (long) timeVal.tv_sec;
\r
12575 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12577 #else /*!HAVE_GETTIMEOFDAY*/
\r
12580 #include <sys/timeb.h>
\r
12581 struct timeb timeB;
\r
12584 tm->sec = (long) timeB.time;
\r
12585 tm->ms = (int) timeB.millitm;
\r
12587 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12588 tm->sec = (long) time(NULL);
\r
12594 /* Return the difference in milliseconds between two
\r
12595 time marks. We assume the difference will fit in a long!
\r
12598 SubtractTimeMarks(tm2, tm1)
\r
12599 TimeMark *tm2, *tm1;
\r
12601 return 1000L*(tm2->sec - tm1->sec) +
\r
12602 (long) (tm2->ms - tm1->ms);
\r
12607 * Code to manage the game clocks.
\r
12609 * In tournament play, black starts the clock and then white makes a move.
\r
12610 * We give the human user a slight advantage if he is playing white---the
\r
12611 * clocks don't run until he makes his first move, so it takes zero time.
\r
12612 * Also, we don't account for network lag, so we could get out of sync
\r
12613 * with GNU Chess's clock -- but then, referees are always right.
\r
12616 static TimeMark tickStartTM;
\r
12617 static long intendedTickLength;
\r
12620 NextTickLength(timeRemaining)
\r
12621 long timeRemaining;
\r
12623 long nominalTickLength, nextTickLength;
\r
12625 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12626 nominalTickLength = 100L;
\r
12628 nominalTickLength = 1000L;
\r
12629 nextTickLength = timeRemaining % nominalTickLength;
\r
12630 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12632 return nextTickLength;
\r
12635 /* Adjust clock one minute up or down */
\r
12637 AdjustClock(Boolean which, int dir)
\r
12639 if(which) blackTimeRemaining += 60000*dir;
\r
12640 else whiteTimeRemaining += 60000*dir;
\r
12641 DisplayBothClocks();
\r
12644 /* Stop clocks and reset to a fresh time control */
\r
12648 (void) StopClockTimer();
\r
12649 if (appData.icsActive) {
\r
12650 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12651 } else { /* [HGM] correct new time quote for time odds */
\r
12652 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12653 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12655 if (whiteFlag || blackFlag) {
\r
12656 DisplayTitle("");
\r
12657 whiteFlag = blackFlag = FALSE;
\r
12659 DisplayBothClocks();
\r
12662 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12664 /* Decrement running clock by amount of time that has passed */
\r
12666 DecrementClocks()
\r
12668 long timeRemaining;
\r
12669 long lastTickLength, fudge;
\r
12672 if (!appData.clockMode) return;
\r
12673 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12675 GetTimeMark(&now);
\r
12677 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12679 /* Fudge if we woke up a little too soon */
\r
12680 fudge = intendedTickLength - lastTickLength;
\r
12681 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12683 if (WhiteOnMove(forwardMostMove)) {
\r
12684 if(whiteNPS >= 0) lastTickLength = 0;
\r
12685 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12686 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12687 WhiteOnMove(currentMove));
\r
12689 if(blackNPS >= 0) lastTickLength = 0;
\r
12690 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
12691 DisplayBlackClock(blackTimeRemaining - fudge,
\r
12692 !WhiteOnMove(currentMove));
\r
12695 if (CheckFlags()) return;
\r
12697 tickStartTM = now;
\r
12698 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
12699 StartClockTimer(intendedTickLength);
\r
12701 /* if the time remaining has fallen below the alarm threshold, sound the
\r
12702 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
12703 * with increment) the time remaining has increased to a level above the
\r
12704 * threshold, reset the alarm so it can sound again.
\r
12707 if (appData.icsActive && appData.icsAlarm) {
\r
12709 /* make sure we are dealing with the user's clock */
\r
12710 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
12711 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
12714 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
12715 alarmSounded = FALSE;
\r
12716 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
12717 PlayAlarmSound();
\r
12718 alarmSounded = TRUE;
\r
12724 /* A player has just moved, so stop the previously running
\r
12725 clock and (if in clock mode) start the other one.
\r
12726 We redisplay both clocks in case we're in ICS mode, because
\r
12727 ICS gives us an update to both clocks after every move.
\r
12728 Note that this routine is called *after* forwardMostMove
\r
12729 is updated, so the last fractional tick must be subtracted
\r
12730 from the color that is *not* on move now.
\r
12735 long lastTickLength;
\r
12737 int flagged = FALSE;
\r
12739 GetTimeMark(&now);
\r
12741 if (StopClockTimer() && appData.clockMode) {
\r
12742 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12743 if (WhiteOnMove(forwardMostMove)) {
\r
12744 if(blackNPS >= 0) lastTickLength = 0;
\r
12745 blackTimeRemaining -= lastTickLength;
\r
12746 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12747 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
12748 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
12749 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
12751 if(whiteNPS >= 0) lastTickLength = 0;
\r
12752 whiteTimeRemaining -= lastTickLength;
\r
12753 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
12754 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
12755 pvInfoList[forwardMostMove-1].time =
\r
12756 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
12758 flagged = CheckFlags();
\r
12760 CheckTimeControl();
\r
12762 if (flagged || !appData.clockMode) return;
\r
12764 switch (gameMode) {
\r
12765 case MachinePlaysBlack:
\r
12766 case MachinePlaysWhite:
\r
12767 case BeginningOfGame:
\r
12768 if (pausing) return;
\r
12772 case PlayFromGameFile:
\r
12773 case IcsExamining:
\r
12780 tickStartTM = now;
\r
12781 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12782 whiteTimeRemaining : blackTimeRemaining);
\r
12783 StartClockTimer(intendedTickLength);
\r
12787 /* Stop both clocks */
\r
12791 long lastTickLength;
\r
12794 if (!StopClockTimer()) return;
\r
12795 if (!appData.clockMode) return;
\r
12797 GetTimeMark(&now);
\r
12799 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12800 if (WhiteOnMove(forwardMostMove)) {
\r
12801 if(whiteNPS >= 0) lastTickLength = 0;
\r
12802 whiteTimeRemaining -= lastTickLength;
\r
12803 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
12805 if(blackNPS >= 0) lastTickLength = 0;
\r
12806 blackTimeRemaining -= lastTickLength;
\r
12807 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
12812 /* Start clock of player on move. Time may have been reset, so
\r
12813 if clock is already running, stop and restart it. */
\r
12817 (void) StopClockTimer(); /* in case it was running already */
\r
12818 DisplayBothClocks();
\r
12819 if (CheckFlags()) return;
\r
12821 if (!appData.clockMode) return;
\r
12822 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12824 GetTimeMark(&tickStartTM);
\r
12825 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
12826 whiteTimeRemaining : blackTimeRemaining);
\r
12828 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
12829 whiteNPS = blackNPS = -1;
\r
12830 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
12831 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
12832 whiteNPS = first.nps;
\r
12833 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
12834 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
12835 blackNPS = first.nps;
\r
12836 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
12837 whiteNPS = second.nps;
\r
12838 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
12839 blackNPS = second.nps;
\r
12840 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
12842 StartClockTimer(intendedTickLength);
\r
12849 long second, minute, hour, day;
\r
12851 static char buf[32];
\r
12853 if (ms > 0 && ms <= 9900) {
\r
12854 /* convert milliseconds to tenths, rounding up */
\r
12855 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
12857 sprintf(buf, " %03.1f ", tenths/10.0);
\r
12861 /* convert milliseconds to seconds, rounding up */
\r
12862 /* use floating point to avoid strangeness of integer division
\r
12863 with negative dividends on many machines */
\r
12864 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
12866 if (second < 0) {
\r
12868 second = -second;
\r
12871 day = second / (60 * 60 * 24);
\r
12872 second = second % (60 * 60 * 24);
\r
12873 hour = second / (60 * 60);
\r
12874 second = second % (60 * 60);
\r
12875 minute = second / 60;
\r
12876 second = second % 60;
\r
12879 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
12880 sign, day, hour, minute, second);
\r
12881 else if (hour > 0)
\r
12882 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
12884 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
12891 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
12894 StrStr(string, match)
\r
12895 char *string, *match;
\r
12899 length = strlen(match);
\r
12901 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
12902 if (!strncmp(match, string, length))
\r
12909 StrCaseStr(string, match)
\r
12910 char *string, *match;
\r
12912 int i, j, length;
\r
12914 length = strlen(match);
\r
12916 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
12917 for (j = 0; j < length; j++) {
\r
12918 if (ToLower(match[j]) != ToLower(string[j]))
\r
12921 if (j == length) return string;
\r
12927 #ifndef _amigados
\r
12929 StrCaseCmp(s1, s2)
\r
12935 c1 = ToLower(*s1++);
\r
12936 c2 = ToLower(*s2++);
\r
12937 if (c1 > c2) return 1;
\r
12938 if (c1 < c2) return -1;
\r
12939 if (c1 == NULLCHAR) return 0;
\r
12948 return isupper(c) ? tolower(c) : c;
\r
12956 return islower(c) ? toupper(c) : c;
\r
12958 #endif /* !_amigados */
\r
12966 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
12973 StrSavePtr(s, savePtr)
\r
12974 char *s, **savePtr;
\r
12979 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
12980 strcpy(*savePtr, s);
\r
12982 return(*savePtr);
\r
12990 char buf[MSG_SIZ];
\r
12992 clock = time((time_t *)NULL);
\r
12993 tm = localtime(&clock);
\r
12994 sprintf(buf, "%04d.%02d.%02d",
\r
12995 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
12996 return StrSave(buf);
\r
13001 PositionToFEN(move, useFEN960)
\r
13005 int i, j, fromX, fromY, toX, toY;
\r
13010 ChessSquare piece;
\r
13012 whiteToPlay = (gameMode == EditPosition) ?
\r
13013 !blackPlaysFirst : (move % 2 == 0);
\r
13016 /* Piece placement data */
\r
13017 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13019 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13020 if (boards[move][i][j] == EmptySquare) {
\r
13022 } else { ChessSquare piece = boards[move][i][j];
\r
13023 if (emptycount > 0) {
\r
13024 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13025 *p++ = '0' + emptycount;
\r
13026 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13029 if(PieceToChar(piece) == '+') {
\r
13030 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13032 piece = (ChessSquare)(DEMOTED piece);
\r
13034 *p++ = PieceToChar(piece);
\r
13035 if(p[-1] == '~') {
\r
13036 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13037 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13042 if (emptycount > 0) {
\r
13043 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13044 *p++ = '0' + emptycount;
\r
13045 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13052 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13053 if( gameInfo.holdingsWidth ) {
\r
13054 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13056 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13057 piece = boards[move][i][BOARD_WIDTH-1];
\r
13058 if( piece != EmptySquare )
\r
13059 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13060 *p++ = PieceToChar(piece);
\r
13062 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13063 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13064 if( piece != EmptySquare )
\r
13065 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13066 *p++ = PieceToChar(piece);
\r
13069 if( q == p ) *p++ = '-';
\r
13074 /* Active color */
\r
13075 *p++ = whiteToPlay ? 'w' : 'b';
\r
13078 if(nrCastlingRights) {
\r
13080 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13081 /* [HGM] write directly from rights */
\r
13082 if(castlingRights[move][2] >= 0 &&
\r
13083 castlingRights[move][0] >= 0 )
\r
13084 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13085 if(castlingRights[move][2] >= 0 &&
\r
13086 castlingRights[move][1] >= 0 )
\r
13087 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13088 if(castlingRights[move][5] >= 0 &&
\r
13089 castlingRights[move][3] >= 0 )
\r
13090 *p++ = castlingRights[move][3] + AAA;
\r
13091 if(castlingRights[move][5] >= 0 &&
\r
13092 castlingRights[move][4] >= 0 )
\r
13093 *p++ = castlingRights[move][4] + AAA;
\r
13096 /* [HGM] write true castling rights */
\r
13097 if( nrCastlingRights == 6 ) {
\r
13098 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13099 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13100 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13101 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13102 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13103 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13104 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13105 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13108 if (q == p) *p++ = '-'; /* No castling rights */
\r
13112 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13113 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13114 /* En passant target square */
\r
13115 if (move > backwardMostMove) {
\r
13116 fromX = moveList[move - 1][0] - AAA;
\r
13117 fromY = moveList[move - 1][1] - ONE;
\r
13118 toX = moveList[move - 1][2] - AAA;
\r
13119 toY = moveList[move - 1][3] - ONE;
\r
13120 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13121 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13122 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13124 /* 2-square pawn move just happened */
\r
13125 *p++ = toX + AAA;
\r
13126 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13136 /* [HGM] find reversible plies */
\r
13137 { int i = 0, j=move;
\r
13139 if (appData.debugMode) { int k;
\r
13140 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13141 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13142 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13146 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13147 if( j == backwardMostMove ) i += initialRulePlies;
\r
13148 sprintf(p, "%d ", i);
\r
13149 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13151 /* Fullmove number */
\r
13152 sprintf(p, "%d", (move / 2) + 1);
\r
13154 return StrSave(buf);
\r
13158 ParseFEN(board, blackPlaysFirst, fen)
\r
13160 int *blackPlaysFirst;
\r
13166 ChessSquare piece;
\r
13170 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13171 if(gameInfo.holdingsWidth) {
\r
13172 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13173 board[i][0] = EmptySquare; /* black holdings */
\r
13174 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13175 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13176 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13180 /* Piece placement data */
\r
13181 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13184 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13185 if (*p == '/') p++;
\r
13186 emptycount = gameInfo.boardWidth - j;
\r
13187 while (emptycount--)
\r
13188 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13190 #if(BOARD_SIZE >= 10)
\r
13191 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13192 p++; emptycount=10;
\r
13193 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13194 while (emptycount--)
\r
13195 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13197 } else if (isdigit(*p)) {
\r
13198 emptycount = *p++ - '0';
\r
13199 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13200 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13201 while (emptycount--)
\r
13202 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13203 } else if (*p == '+' || isalpha(*p)) {
\r
13204 if (j >= gameInfo.boardWidth) return FALSE;
\r
13206 piece = CharToPiece(*++p);
\r
13207 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13208 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13209 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13210 } else piece = CharToPiece(*p++);
\r
13212 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13213 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13214 piece = (ChessSquare) (PROMOTED piece);
\r
13215 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13218 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13224 while (*p == '/' || *p == ' ') p++;
\r
13226 /* [HGM] look for Crazyhouse holdings here */
\r
13227 while(*p==' ') p++;
\r
13228 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13229 if(*p == '[') p++;
\r
13230 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13231 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13232 /* if we would allow FEN reading to set board size, we would */
\r
13233 /* have to add holdings and shift the board read so far here */
\r
13234 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13236 if((int) piece >= (int) BlackPawn ) {
\r
13237 i = (int)piece - (int)BlackPawn;
\r
13238 i = PieceToNumber((ChessSquare)i);
\r
13239 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13240 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13241 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13243 i = (int)piece - (int)WhitePawn;
\r
13244 i = PieceToNumber((ChessSquare)i);
\r
13245 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13246 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13247 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13251 if(*p == ']') *p++;
\r
13254 while(*p == ' ') p++;
\r
13256 /* Active color */
\r
13259 *blackPlaysFirst = FALSE;
\r
13262 *blackPlaysFirst = TRUE;
\r
13268 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13269 /* return the extra info in global variiables */
\r
13271 /* set defaults in case FEN is incomplete */
\r
13272 FENepStatus = EP_UNKNOWN;
\r
13273 for(i=0; i<nrCastlingRights; i++ ) {
\r
13274 FENcastlingRights[i] =
\r
13275 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13276 } /* assume possible unless obviously impossible */
\r
13277 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13278 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13279 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13280 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13281 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13282 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13283 FENrulePlies = 0;
\r
13285 while(*p==' ') p++;
\r
13286 if(nrCastlingRights) {
\r
13287 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13288 /* castling indicator present, so default becomes no castlings */
\r
13289 for(i=0; i<nrCastlingRights; i++ ) {
\r
13290 FENcastlingRights[i] = -1;
\r
13293 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13294 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13295 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13296 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13297 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13299 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13300 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13301 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13305 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13306 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13307 FENcastlingRights[2] = whiteKingFile;
\r
13310 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13311 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13312 FENcastlingRights[2] = whiteKingFile;
\r
13315 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13316 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13317 FENcastlingRights[5] = blackKingFile;
\r
13320 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13321 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13322 FENcastlingRights[5] = blackKingFile;
\r
13325 default: /* FRC castlings */
\r
13326 if(c >= 'a') { /* black rights */
\r
13327 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13328 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13329 if(i == BOARD_RGHT) break;
\r
13330 FENcastlingRights[5] = i;
\r
13332 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13333 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13335 FENcastlingRights[3] = c;
\r
13337 FENcastlingRights[4] = c;
\r
13338 } else { /* white rights */
\r
13339 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13340 if(board[0][i] == WhiteKing) break;
\r
13341 if(i == BOARD_RGHT) break;
\r
13342 FENcastlingRights[2] = i;
\r
13343 c -= AAA - 'a' + 'A';
\r
13344 if(board[0][c] >= WhiteKing) break;
\r
13346 FENcastlingRights[0] = c;
\r
13348 FENcastlingRights[1] = c;
\r
13352 if (appData.debugMode) {
\r
13353 fprintf(debugFP, "FEN castling rights:");
\r
13354 for(i=0; i<nrCastlingRights; i++)
\r
13355 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13356 fprintf(debugFP, "\n");
\r
13359 while(*p==' ') p++;
\r
13362 /* read e.p. field in games that know e.p. capture */
\r
13363 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13364 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13366 p++; FENepStatus = EP_NONE;
\r
13368 char c = *p++ - AAA;
\r
13370 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13371 if(*p >= '0' && *p <='9') *p++;
\r
13377 if(sscanf(p, "%d", &i) == 1) {
\r
13378 FENrulePlies = i; /* 50-move ply counter */
\r
13379 /* (The move number is still ignored) */
\r
13386 EditPositionPasteFEN(char *fen)
\r
13388 if (fen != NULL) {
\r
13389 Board initial_position;
\r
13391 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13392 DisplayError("Bad FEN position in clipboard", 0);
\r
13395 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13396 EditPositionEvent();
\r
13397 blackPlaysFirst = savedBlackPlaysFirst;
\r
13398 CopyBoard(boards[0], initial_position);
\r
13399 /* [HGM] copy FEN attributes as well */
\r
13401 initialRulePlies = FENrulePlies;
\r
13402 epStatus[0] = FENepStatus;
\r
13403 for( i=0; i<nrCastlingRights; i++ )
\r
13404 castlingRights[0][i] = FENcastlingRights[i];
\r
13406 EditPositionDone();
\r
13407 DisplayBothClocks();
\r
13408 DrawPosition(FALSE, boards[currentMove]);
\r